From 9eb21cf6c7fd0e0a056403b74436514b5ce60460 Mon Sep 17 00:00:00 2001 From: thedevnull <daniellage18@gmail.com> Date: Tue, 11 Jan 2011 23:54:37 -0200 Subject: [PATCH] bots Signed-off-by: thedevnull <daniellage18@gmail.com> --- sql/Bots/CMakeLists.txt | 4 + sql/Bots/characters_bots.sql | 6 + sql/Bots/world_bots.sql | 245 ++ sql/Bots/world_bots_update.sql | 13 + sql/CMakeLists.txt | 1 + src/server/game/AI/Bots/PlayerbotAI.cpp | 4498 ++++++++++++++++++++ src/server/game/AI/Bots/PlayerbotAI.h | 284 ++ src/server/game/AI/Bots/PlayerbotClassAI.cpp | 592 +++ src/server/game/AI/Bots/PlayerbotClassAI.h | 162 + src/server/game/AI/Bots/PlayerbotDeathKnightAI.cpp | 325 ++ src/server/game/AI/Bots/PlayerbotDeathKnightAI.h | 60 + src/server/game/AI/Bots/PlayerbotDruidAI.cpp | 674 +++ src/server/game/AI/Bots/PlayerbotDruidAI.h | 70 + src/server/game/AI/Bots/PlayerbotHunterAI.cpp | 561 +++ src/server/game/AI/Bots/PlayerbotHunterAI.h | 68 + src/server/game/AI/Bots/PlayerbotMageAI.cpp | 384 ++ src/server/game/AI/Bots/PlayerbotMageAI.h | 69 + src/server/game/AI/Bots/PlayerbotPaladinAI.cpp | 535 +++ src/server/game/AI/Bots/PlayerbotPaladinAI.h | 70 + src/server/game/AI/Bots/PlayerbotPriestAI.cpp | 400 ++ src/server/game/AI/Bots/PlayerbotPriestAI.h | 59 + src/server/game/AI/Bots/PlayerbotRogueAI.cpp | 266 ++ src/server/game/AI/Bots/PlayerbotRogueAI.h | 41 + src/server/game/AI/Bots/PlayerbotShamanAI.cpp | 555 +++ src/server/game/AI/Bots/PlayerbotShamanAI.h | 75 + src/server/game/AI/Bots/PlayerbotWarlockAI.cpp | 409 ++ src/server/game/AI/Bots/PlayerbotWarlockAI.h | 55 + src/server/game/AI/Bots/PlayerbotWarriorAI.cpp | 400 ++ src/server/game/AI/Bots/PlayerbotWarriorAI.h | 57 + src/server/game/AI/CoreAI/PetAI.cpp | 9 + src/server/game/CMakeLists.txt | 2 + src/server/game/Chat/Chat.cpp | 3 + src/server/game/Chat/Chat.h | 6 + src/server/game/Chat/Commands/Level0.cpp | 181 + src/server/game/Chat/Commands/Level1.cpp | 6 +- src/server/game/Entities/Creature/Creature.cpp | 3 + src/server/game/Entities/Creature/Creature.h | 15 + src/server/game/Entities/Creature/GossipDef.h | 1 + .../game/Entities/Creature/TemporarySummon.cpp | 8 + src/server/game/Entities/Object/Object.cpp | 27 +- src/server/game/Entities/Player/Player.cpp | 1279 ++++++- src/server/game/Entities/Player/Player.h | 113 +- src/server/game/Entities/Unit/Unit.cpp | 11 + src/server/game/Groups/Group.cpp | 150 +- src/server/game/Groups/Group.h | 19 + src/server/game/Maps/Map.cpp | 21 +- src/server/game/Quests/QuestDef.h | 4 +- src/server/game/Scripting/ScriptLoader.cpp | 23 + src/server/game/Scripting/ScriptMgr.cpp | 20 +- src/server/game/Scripting/ScriptMgr.h | 3 + .../Server/Protocol/Handlers/CharacterHandler.cpp | 178 + .../game/Server/Protocol/Handlers/ChatHandler.cpp | 33 +- .../game/Server/Protocol/Handlers/GroupHandler.cpp | 33 +- .../game/Server/Protocol/Handlers/NPCHandler.cpp | 6 +- .../game/Server/Protocol/Handlers/QuestHandler.cpp | 1 + src/server/game/Server/WorldSession.cpp | 120 +- src/server/game/Server/WorldSession.h | 13 + src/server/game/Weather/Weather.cpp | 2 +- src/server/game/World/World.cpp | 2 + src/server/game/World/World.h | 1 + src/server/scripts/Bots/CMakeLists.txt | 26 + src/server/scripts/Bots/bot_ai.cpp | 566 +++ src/server/scripts/Bots/bot_ai.h | 88 + src/server/scripts/Bots/bot_druid_ai.cpp | 563 +++ src/server/scripts/Bots/bot_druid_ai.h | 63 + src/server/scripts/Bots/bot_hunter_ai.cpp | 303 ++ src/server/scripts/Bots/bot_hunter_ai.h | 26 + src/server/scripts/Bots/bot_mage_ai.cpp | 524 +++ src/server/scripts/Bots/bot_mage_ai.h | 124 + src/server/scripts/Bots/bot_paladin_ai.cpp | 276 ++ src/server/scripts/Bots/bot_paladin_ai.h | 62 + src/server/scripts/Bots/bot_priest_ai.cpp | 276 ++ src/server/scripts/Bots/bot_priest_ai.h | 33 + src/server/scripts/Bots/bot_rogue_ai.cpp | 313 ++ src/server/scripts/Bots/bot_rogue_ai.h | 30 + src/server/scripts/Bots/bot_shaman_ai.cpp | 385 ++ src/server/scripts/Bots/bot_shaman_ai.h | 48 + src/server/scripts/Bots/bot_warlock_ai.cpp | 292 ++ src/server/scripts/Bots/bot_warlock_ai.h | 41 + src/server/scripts/Bots/bot_warrior_ai.cpp | 501 +++ src/server/scripts/Bots/bot_warrior_ai.h | 87 + src/server/scripts/Bots/script_bot_giver.cpp | 234 + src/server/scripts/CMakeLists.txt | 1 + src/server/shared/Common.h | 4 + src/server/worldserver/worldserver.conf.dist | 36 + 85 files changed, 18093 insertions(+), 40 deletions(-) create mode 100644 sql/Bots/CMakeLists.txt create mode 100644 sql/Bots/characters_bots.sql create mode 100644 sql/Bots/world_bots.sql create mode 100644 sql/Bots/world_bots_update.sql create mode 100644 src/server/game/AI/Bots/PlayerbotAI.cpp create mode 100644 src/server/game/AI/Bots/PlayerbotAI.h create mode 100644 src/server/game/AI/Bots/PlayerbotClassAI.cpp create mode 100644 src/server/game/AI/Bots/PlayerbotClassAI.h create mode 100644 src/server/game/AI/Bots/PlayerbotDeathKnightAI.cpp create mode 100644 src/server/game/AI/Bots/PlayerbotDeathKnightAI.h create mode 100644 src/server/game/AI/Bots/PlayerbotDruidAI.cpp create mode 100644 src/server/game/AI/Bots/PlayerbotDruidAI.h create mode 100644 src/server/game/AI/Bots/PlayerbotHunterAI.cpp create mode 100644 src/server/game/AI/Bots/PlayerbotHunterAI.h create mode 100644 src/server/game/AI/Bots/PlayerbotMageAI.cpp create mode 100644 src/server/game/AI/Bots/PlayerbotMageAI.h create mode 100644 src/server/game/AI/Bots/PlayerbotPaladinAI.cpp create mode 100644 src/server/game/AI/Bots/PlayerbotPaladinAI.h create mode 100644 src/server/game/AI/Bots/PlayerbotPriestAI.cpp create mode 100644 src/server/game/AI/Bots/PlayerbotPriestAI.h create mode 100644 src/server/game/AI/Bots/PlayerbotRogueAI.cpp create mode 100644 src/server/game/AI/Bots/PlayerbotRogueAI.h create mode 100644 src/server/game/AI/Bots/PlayerbotShamanAI.cpp create mode 100644 src/server/game/AI/Bots/PlayerbotShamanAI.h create mode 100644 src/server/game/AI/Bots/PlayerbotWarlockAI.cpp create mode 100644 src/server/game/AI/Bots/PlayerbotWarlockAI.h create mode 100644 src/server/game/AI/Bots/PlayerbotWarriorAI.cpp create mode 100644 src/server/game/AI/Bots/PlayerbotWarriorAI.h create mode 100644 src/server/scripts/Bots/CMakeLists.txt create mode 100644 src/server/scripts/Bots/bot_ai.cpp create mode 100644 src/server/scripts/Bots/bot_ai.h create mode 100644 src/server/scripts/Bots/bot_druid_ai.cpp create mode 100644 src/server/scripts/Bots/bot_druid_ai.h create mode 100644 src/server/scripts/Bots/bot_hunter_ai.cpp create mode 100644 src/server/scripts/Bots/bot_hunter_ai.h create mode 100644 src/server/scripts/Bots/bot_mage_ai.cpp create mode 100644 src/server/scripts/Bots/bot_mage_ai.h create mode 100644 src/server/scripts/Bots/bot_paladin_ai.cpp create mode 100644 src/server/scripts/Bots/bot_paladin_ai.h create mode 100644 src/server/scripts/Bots/bot_priest_ai.cpp create mode 100644 src/server/scripts/Bots/bot_priest_ai.h create mode 100644 src/server/scripts/Bots/bot_rogue_ai.cpp create mode 100644 src/server/scripts/Bots/bot_rogue_ai.h create mode 100644 src/server/scripts/Bots/bot_shaman_ai.cpp create mode 100644 src/server/scripts/Bots/bot_shaman_ai.h create mode 100644 src/server/scripts/Bots/bot_warlock_ai.cpp create mode 100644 src/server/scripts/Bots/bot_warlock_ai.h create mode 100644 src/server/scripts/Bots/bot_warrior_ai.cpp create mode 100644 src/server/scripts/Bots/bot_warrior_ai.h create mode 100644 src/server/scripts/Bots/script_bot_giver.cpp diff --git a/sql/Bots/CMakeLists.txt b/sql/Bots/CMakeLists.txt new file mode 100644 index 0000000..a615f58 --- /dev/null +++ b/sql/Bots/CMakeLists.txt @@ -0,0 +1,4 @@ +INSTALL(FILES +world_bots.sql +characters_bots.sql +DESTINATION share/trinity/sql/Bots) diff --git a/sql/Bots/characters_bots.sql b/sql/Bots/characters_bots.sql new file mode 100644 index 0000000..5277449 --- /dev/null +++ b/sql/Bots/characters_bots.sql @@ -0,0 +1,6 @@ +CREATE TABLE IF NOT EXISTS `character_npcbot` ( + `owner` int(11) default NULL, + `entry` int(11) default NULL, + `race` tinyint(4) default NULL, + `class` tinyint(4) default NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8; diff --git a/sql/Bots/world_bots.sql b/sql/Bots/world_bots.sql new file mode 100644 index 0000000..da400b1 --- /dev/null +++ b/sql/Bots/world_bots.sql @@ -0,0 +1,245 @@ + +-- Add command as level 0 +DELETE FROM command where name='bot'; +INSERT INTO command (name, security, help) VALUES ('bot',0,'Syntax: .bot $subcommand $Name'); + +delete from `creature_template` where entry >= 60000 && entry < 60239; + +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`,`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`,`Health_mod`,`Mana_mod`,`Armor_mod`,`RacialLeader`,`questItem1`,`questItem2`,`questItem3`,`questItem4`,`questItem5`,`questItem6`,`movementId`,`RegenHealth`,`equipment_id`,`mechanic_immune_mask`,`flags_extra`,`ScriptName`,`WDBVerified`) values +(60000,0,0,0,0,0,169,0,169,0,'Recruitment Officer','','',0,80,80,0,35,35,1,1.4,1.14286,1,0,228,298,0,1837,1,2000,0,1,0,0,0,0,0,0,0,0,0,0,4,0,0,0,0,0,0,300,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,0,0,0,0,0,0,0,0,1,0,0,0,'script_bot_giver',0), +(60001,0,0,0,0,0,5001,0,5001,0,'Khelden','Mage Bot','',0,80,80,2,12,12,1,0.93,1.14286,1,0,2,5,0,23,1,1500,0,8,4608,0,0,0,0,8,1,8.624,11.858,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,838,0,'mage_bot',0), +(60002,0,0,0,0,0,1294,0,1294,0,'Zaldimar','Mage Bot','',0,80,80,2,12,12,1,0.98,1.14286,1,0,5,10,0,54,1,1500,0,8,4608,0,0,0,0,8,1,21.5072,29.5724,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,85,0,'mage_bot',0), +(60003,0,0,0,0,0,1484,0,1484,0,'Maginor','Mage Bot','',0,80,80,2,12,12,1,1.26,1.14286,1,0,42,88,0,451,1,1500,0,8,4608,0,0,0,0,8,1,74.448,102.366,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,96,0,'mage_bot',0), +(60004,0,0,0,0,0,3344,0,3344,0,'Anetta','Priest Bot','',0,80,80,2,12,12,1,0.93,1.14286,1,0,2,5,0,23,1,1500,0,8,4608,0,0,0,0,5,1,8.624,11.858,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,838,0,'priest_bot',0), +(60005,0,0,0,0,0,1495,0,1495,0,'Laurena','Priest Bot','',0,80,80,2,12,12,1,1.26,1.14286,1,0,42,88,0,451,1,1500,0,8,4608,0,0,0,0,5,1,74.448,102.366,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,94,0,'priest_bot',0), +(60006,0,0,0,0,0,1295,0,1295,0,'Josetta','Priest Bot','',0,80,80,2,12,12,1,0.98,1.14286,1,0,5,10,0,54,1,1500,0,8,4608,0,0,0,0,5,1,21.5072,29.5724,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,85,0,'priest_bot',0), +(60007,0,0,0,0,0,3345,0,3345,0,'Drusilla','Warlock Bot','',0,80,80,2,12,12,1,0.93,1.14286,1,0,2,5,0,23,1,1500,0,8,4608,0,0,0,0,9,1,8.624,11.858,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1491,838,0,'warlock_bot',0), +(60008,0,0,0,0,0,1930,0,1930,0,'Alamar','Warlock Bot','',0,80,80,2,875,875,1,1.07,1.14286,1,0,2,5,0,23,1,1960,2156,8,4608,0,0,0,0,9,7,8.624,11.858,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1491,838,0,'warlock_bot',0), +(60009,0,0,0,0,0,1469,0,1469,0,'Demisette','Warlock Bot','',0,80,80,2,12,12,1,1.26,1.14286,1,0,42,88,0,451,1,1500,0,8,4608,0,0,0,0,9,1,74.448,102.366,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1491,110,0,'warlock_bot',0), +(60010,0,0,0,0,0,12749,0,12749,0,'Nalesette','Hunter Bot','',0,80,80,2,80,80,1,1.1,1.14286,1,0,23,48,0,247,1,1500,1500,2,4608,0,0,3,0,3,4,56.672,77.924,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1186,319,0,'hunter_bot',0), +(60011,0,0,0,0,0,3401,0,3401,0,'Branstock','Priest Bot','',0,80,80,2,55,55,1,0.93,1.14286,1,0,2,5,0,23,1,1960,2156,8,4608,0,0,0,0,5,3,8.624,11.858,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,838,0,'priest_bot',0), +(60012,0,0,0,0,0,3395,0,3395,0,'Thorgas','Hunter Bot','',0,80,80,2,55,55,1,0.93,1.14286,1,0,2,5,0,23,1,1960,2156,2,4608,0,0,0,0,3,3,8.624,11.858,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1186,10,0,'hunter_bot',0), +(60013,0,0,0,0,0,3343,0,3343,0,'Llane','Warrior Bot','',0,80,80,2,12,12,1,0.93,1.14286,1,0,2,65,0,364,1,1500,0,1,4608,0,0,0,0,1,1,8.624,11.858,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1362,702,0,'warrior_bot',0), +(60014,0,0,0,0,0,3399,0,3399,0,'Thran','Warrior Bot','',0,80,80,2,55,55,1,0.93,1.14286,1,0,2,55,0,267,1,1960,2156,1,4608,0,0,0,0,1,3,8.624,11.858,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1022,702,0,'warrior_bot',0), +(60015,0,0,0,0,0,1300,0,1300,0,'Lyria','Warrior Bot','',0,80,80,2,12,12,1,0.97,1.14286,1,0,5,90,0,543,1,1500,0,1,4608,0,0,0,0,1,1,19.9584,27.4428,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,110,647,0,'warrior_bot',0), +(60016,0,0,0,0,0,3351,0,3351,0,'Jorik','Rogue Bot','',0,80,80,2,12,12,1,0.93,1.14286,1,0,2,75,0,1489,1,1500,0,4,4608,0,0,0,0,4,1,8.624,11.858,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1204,397,0,'rogue_bot',0), +(60017,0,0,0,0,0,3407,0,3407,0,'Solm','Rogue Bot','',0,80,80,2,55,55,1,0.93,1.14286,1,0,2,74,0,1489,1,1960,2156,4,4608,0,0,0,0,4,3,8.624,11.858,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1204,397,0,'rogue_bot',0), +(60018,0,0,0,0,0,1297,0,1297,0,'Keryn','Rogue Bot','',0,80,80,2,12,12,1,0.97,1.14286,1,0,5,53,0,1489,1,2000,0,4,4608,0,0,0,0,4,1,18.392,25.289,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1206,730,0,'rogue_bot',0), +(60019,0,0,0,0,0,1507,0,1507,0,'Osborne','Rogue Bot','',0,80,80,2,12,12,1,1.26,1.14286,1,0,42,88,0,1489,1,1500,0,4,4608,0,0,0,0,4,1,74.448,102.366,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1204,392,0,'rogue_bot',0), +(60020,0,0,0,0,0,3346,0,3346,0,'Sammuel','Paladin Bot','',0,80,80,2,12,12,1,0.93,1.14286,1,0,2,5,0,23,1,1500,0,2,4608,0,0,0,0,2,1,8.624,11.858,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,204,429,0,'paladin_bot',0), +(60021,0,0,0,0,0,3393,0,3393,0,'Bob','Paladin Bot','',0,80,80,2,55,55,1,0.93,1.14286,1,0,2,5,0,23,1,1960,2156,2,4608,0,0,0,0,2,3,8.624,11.858,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,205,429,0,'paladin_bot',0), +(60022,0,0,0,0,0,1299,0,1299,0,'Wilhelm','Paladin Bot','',0,80,80,2,12,12,1,0.97,1.14286,1,0,5,9,0,45,1,1500,0,2,4608,0,0,0,0,2,1,18.392,25.289,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,204,426,0,'paladin_bot',0), +(60023,0,0,0,0,0,1499,0,1499,0,'Brisombre','Paladin Bot','',0,80,80,2,12,12,1,1.26,1.14286,1,0,42,88,0,451,1,1500,0,2,4608,0,0,0,0,2,1,74.448,102.366,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,205,105,0,'paladin_bot',0), +(60024,0,0,0,0,0,10216,0,10216,0,'Marry','Mage Bot','',0,80,80,2,875,875,1,1.07,1.14286,1,0,2,5,0,23,1,1960,2156,8,4608,0,0,0,0,8,7,8.624,11.858,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,3,0,0,0,0,0,0,0,0,127,838,0,'mage_bot',0), +(60025,0,0,0,0,0,4552,0,4552,0,'Haromm','Shaman Bot','',0,80,80,2,29,29,1,1.05,1.14286,1,0,32,200,0,345,1,2000,0,2,4608,0,0,0,0,7,2,66.44,91.355,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,93,1691,0,'shaman_bot',0), +(60026,0,0,0,0,0,4567,0,4567,0,'Kartosh','Warlock Bot','',0,80,80,2,29,29,1,1.05,1.14286,1,0,32,67,0,345,1,2000,0,8,4608,0,0,0,0,9,2,66.44,91.355,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1491,110,0,'warlock_bot',0), +(60027,0,0,0,0,0,3429,0,3429,0,'MaxanAnvol','Priest Bot','',0,80,80,2,55,55,1,0.96,1.14286,1,0,3,9,0,42,1,1500,0,8,4608,0,0,0,0,5,3,16.808,23.111,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,1315,0,'priest_bot',0), +(60028,0,0,0,0,0,10215,0,10215,0,'Magis','Mage Bot','',0,80,80,2,875,875,1,1.1,1.14286,1,0,3,9,0,42,1,1500,0,8,4608,0,0,0,0,8,7,16.808,23.111,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,0,0,'mage_bot',0), +(60029,0,0,0,0,0,3431,0,3431,0,'GranVivehache','Warrior Bot','',0,80,80,2,55,55,1,0.96,1.14286,1,0,3,75,0,422,1,1500,0,1,4608,0,0,0,0,1,3,16.808,23.111,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,394,1309,0,'warrior_bot',0), +(60030,0,0,0,0,0,1622,0,1622,0,'Azar','Paladin Bot','',0,80,80,2,55,55,1,1.26,1.14286,1,0,42,88,0,451,1,2000,0,2,4608,0,0,0,0,2,3,74.448,102.366,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,204,105,0,'paladin_bot',0), +(60031,0,0,0,0,0,3436,0,3436,0,'Hogral','Rogue Bot','',0,80,80,2,55,55,1,0.97,1.14286,1,0,5,56,0,1489,1,2000,0,4,4608,0,0,0,0,4,3,19.9584,27.4428,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1204,397,0,'rogue_bot',0), +(60032,0,0,0,0,0,3053,0,3053,0,'Kelstrum','Warrior Bot','',0,80,80,2,55,55,1,1.17,1.14286,1,0,27,57,0,294,1,1500,0,1,4608,0,0,0,0,1,3,61.776,84.942,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1022,1853,0,'warrior_bot',0), +(60033,0,0,0,0,0,1578,0,1578,0,'Dannal','Warrior Bot','',0,80,80,2,68,68,1,0.9,1.14286,1,0,2,67,0,364,1,2000,0,1,4608,0,0,0,0,1,5,8.624,11.858,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1362,639,0,'warrior_bot',0), +(60034,0,0,0,0,0,1579,0,1579,0,'SombreDuesten','Priest Bot','',0,80,80,2,68,68,1,0.9,1.14286,1,0,2,5,0,23,1,2000,0,8,4608,0,0,0,0,5,5,8.624,11.858,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,604,0,'priest_bot',0), +(60035,0,0,0,0,0,1592,0,1592,0,'Isabella','Mage Bot','',0,80,80,2,68,68,1,0.9,1.14286,1,0,2,5,0,23,1,2000,0,8,4608,0,0,0,0,8,5,8.624,11.858,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,1022,0,'mage_bot',0), +(60036,0,0,0,0,0,1581,0,1581,0,'Maximillion','Warlock Bot','',0,80,80,2,68,68,1,0.9,1.14286,1,0,2,5,0,23,1,2000,0,8,4608,0,0,0,0,9,5,8.624,11.858,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1491,143,0,'warlock_bot',0), +(60037,0,0,0,0,0,1604,0,1604,0,'Rupert','Warlock Bot','',0,80,80,2,68,68,1,1.1,1.14286,1,0,6,13,0,65,1,2000,0,8,4608,0,0,0,0,9,5,24.552,33.759,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1491,1022,0,'warlock_bot',0), +(60038,0,0,0,0,0,1600,0,1600,0,'Cain','Mage Bot','',0,80,80,2,68,68,1,1.1,1.14286,1,0,6,12,0,59,1,2000,0,8,4608,0,0,0,0,8,5,23.0384,31.6778,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,173,0,'mage_bot',0), +(60039,0,0,0,0,0,1602,0,1602,0,'SombreBeryl','Priest Bot','',0,80,80,2,68,68,1,1.1,1.14286,1,0,7,14,0,75,1,2000,0,8,4608,0,0,0,0,5,5,27.5264,37.8488,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,604,0,'priest_bot',0), +(60041,0,0,0,0,0,10548,0,10548,0,'Milituus','Mage Bot','',0,80,80,2,55,55,1,1.35,1.14286,1,0,27,57,0,294,1,2000,0,8,4608,0,0,0,0,8,3,61.776,84.942,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,96,0,'mage_bot',0), +(60042,0,0,0,0,0,2810,0,2810,0,'Lexington','Mage Bot','',0,80,80,2,68,68,1,1.1,1.14286,1,0,19,40,0,205,1,2000,0,8,4608,0,0,0,0,8,5,51.128,70.301,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,96,0,'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.14286,1,0,32,200,0,345,1,2000,0,2,4608,0,0,0,0,7,6,66.44,91.355,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,93,586,0,'shaman_bot',0), +(60044,0,0,0,0,0,19598,0,19598,0,'Umbrua','Shaman Bot','',0,80,80,2,1640,1640,1,1.125,1.14286,1,0,176,200,0,1235,1,2000,0,2,4608,0,0,0,0,7,11,0,0,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,93,1627,0,'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.14286,1,0,23,200,0,247,1,2000,0,2,4608,0,0,0,0,7,6,56.672,77.924,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,93,145,0,'shaman_bot',0), +(60046,0,0,0,0,0,2082,0,2082,0,'Beram','Shaman Bot','',0,80,80,2,104,104,1,1.1,1.14286,1,0,42,200,0,451,1,2000,0,2,4608,0,0,0,0,7,6,74.448,102.366,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,93,157,0,'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.14286,1,0,32,200,0,345,1,2000,0,2,4608,0,0,0,0,11,6,66.44,91.355,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,93,1069,0,'druid_bot',0), +(60048,0,0,0,0,0,2121,0,2121,0,'Sheal','Druid Bot','',0,80,80,2,104,104,1,1.1,1.14286,1,0,42,200,0,1034,1,2000,0,2,4608,0,0,0,0,11,6,74.448,102.366,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,93,1252,0,'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.14286,1,0,123,248,0,1034,1,2000,0,2,4608,0,0,0,0,11,6,56.672,77.924,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,93,1034,0,'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.14286,1,0,32,67,0,345,1,2000,1200,2,4608,0,0,0,0,3,6,66.44,91.355,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1186,920,0,'hunter_bot',0), +(60051,0,0,0,0,0,2087,0,2087,0,'Holt','Hunter Bot','',0,80,80,2,104,104,1,1.1,1.14286,1,0,42,88,0,451,1,2000,1000,2,4608,0,0,0,0,3,6,74.448,102.366,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1186,613,0,'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.14286,1,0,23,48,0,247,1,2000,1300,2,4608,0,0,0,0,3,6,56.672,77.924,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1186,6,0,'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.14286,1,0,32,67,0,345,1,2000,0,1,4608,0,0,0,0,1,6,66.44,91.355,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1362,916,0,'warrior_bot',0), +(60054,0,0,0,0,0,2096,0,2096,0,'Sark','Warrior Bot','',0,80,80,2,104,104,1,1.1,1.14286,1,0,42,88,0,451,1,2000,0,1,4608,0,0,0,0,1,6,74.448,102.366,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1022,133,0,'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.14286,1,0,23,48,0,247,1,2000,0,1,4608,0,0,0,0,1,6,56.672,77.924,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,491,18,0,'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.14286,1,0,23,48,0,247,1,2000,0,8,4608,0,0,0,0,5,5,56.672,77.924,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,40,0,'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.14286,1,0,32,67,0,345,1,2000,0,8,4608,0,0,0,0,5,5,66.44,91.355,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,65,0,'priest_bot',0), +(60058,0,0,0,0,0,2137,0,2137,0,'Cobb','Priest Bot','',0,80,80,2,68,68,1,1.1,1.14286,1,0,42,88,0,451,1,2000,0,8,4608,0,0,0,0,5,5,74.448,102.366,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,1096,0,'priest_bot',0), +(60059,0,0,0,0,0,2134,0,2134,0,'Shymm','Mage Bot','',0,80,80,2,68,68,1,1.1,1.14286,1,0,42,88,0,451,1,2000,0,8,4608,0,0,0,0,8,5,74.448,102.366,100,7,1,0,0,0,0,0,0,0,0,0,0,143,145,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,108,0,'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.14286,1,0,23,48,0,247,1,2000,0,8,4608,0,0,0,0,8,5,56.672,77.924,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,96,0,'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.14286,1,0,32,67,0,345,1,2000,0,8,4608,0,0,0,0,8,5,66.44,91.355,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,129,0,'mage_bot',0), +(60062,0,0,0,0,0,3793,0,3793,0,'Harutt','Warrior Bot','',0,80,80,2,104,104,1,0.97,1.14286,1,0,40,99,0,637,1,2000,0,1,4608,0,0,0,0,1,6,16.808,23.111,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,394,1977,0,'warrior_bot',0), +(60063,0,0,0,0,0,3819,0,3819,0,'Gart','Druid Bot','',0,80,80,2,104,104,1,0.97,1.14286,1,0,3,200,0,1034,1,2000,0,2,4608,0,0,0,0,11,6,15.2064,20.9088,100,7,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,93,1073,0,'druid_bot',0), +(60064,0,0,0,0,0,3810,0,3810,0,'Lanka','Hunter Bot','',0,80,80,2,104,104,1,0.97,1.14286,1,0,5,9,0,45,1,2000,1460,2,4608,0,0,0,0,3,6,18.392,25.289,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1186,138,0,'hunter_bot',0), +(60065,0,0,0,0,0,10180,0,10180,0,'Meela','Shaman Bot','',0,80,80,2,104,104,1,0.97,1.14286,1,0,3,200,0,42,1,2000,0,2,4608,0,0,0,0,7,6,16.808,23.111,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,93,157,0,'shaman_bot',0), +(60066,0,0,0,0,0,3794,0,3794,0,'Krang','Warrior Bot','',0,80,80,2,104,104,1,0.98,1.14286,1,0,40,68,0,575,1,2000,0,1,4608,0,0,0,0,1,6,23.0384,31.6778,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1362,1977,0,'warrior_bot',0), +(60067,0,0,0,0,0,10734,0,10734,0,'Gennia','Druid Bot','',0,80,80,2,104,104,1,0.98,1.14286,1,0,5,200,0,1034,1,2000,0,2,4608,0,0,0,0,11,6,19.9584,27.4428,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,93,1069,0,'druid_bot',0), +(60068,0,0,0,0,0,3811,0,3811,0,'Yaw','Hunter Bot','',0,80,80,2,104,104,1,0.97,1.14286,1,0,5,9,0,45,1,2000,1200,2,4608,0,0,0,0,3,6,18.392,25.289,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1186,127,0,'hunter_bot',0), +(60069,0,0,0,0,0,3816,0,3816,0,'Narm','Shaman Bot','',0,80,80,2,104,104,1,0.98,1.14286,1,0,5,200,0,54,1,2000,0,2,4608,0,0,0,0,7,6,21.5072,29.5724,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,93,446,0,'shaman_bot',0), +(60070,0,0,0,0,0,1880,0,1880,0,'Frang','Warrior Bot','',0,80,80,2,29,29,1,1.1,1.14286,1,0,30,99,0,670,1,2000,2090,1,4608,0,0,0,0,1,2,18.392,25.289,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1022,1977,0,'warrior_bot',0), +(60071,0,0,0,0,0,1882,0,1882,0,'Jenshan','Hunter Bot','',0,80,80,2,126,126,1,0.95,1.14286,1,0,2,7,0,33,1,2000,2123,2,4608,0,0,0,0,3,8,13.5872,18.6824,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1186,193,0,'hunter_bot',0), +(60072,0,0,0,0,0,1884,0,1884,0,'Nartok','Warlock Bot','',0,80,80,2,29,29,1,0.96,1.14286,1,0,3,9,0,38,1,2000,2112,8,4608,0,0,0,0,9,2,15.2064,20.9088,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1491,1022,0,'warlock_bot',0), +(60073,0,0,0,0,0,1878,0,1878,0,'Shikrik','Shaman Bot','',0,80,80,2,29,29,1,1.1,1.14286,1,0,3,200,0,42,1,2000,2101,2,4608,0,0,0,0,7,2,16.808,23.111,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,93,604,0,'shaman_bot',0), +(60074,0,0,0,0,0,3743,0,3743,0,'Tarshaw','Warrior Bot','',0,80,80,2,29,29,1,1.1,1.14286,1,0,25,53,0,273,1,2000,1738,1,4608,0,0,0,0,1,2,59.7872,82.2074,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1362,642,0,'warrior_bot',0), +(60075,0,0,0,0,0,3744,0,3744,0,'Thotar','Hunter Bot','',0,80,80,2,29,29,1,1.1,1.14286,1,0,6,15,0,72,1,2000,2035,2,4608,0,0,0,0,3,2,26.048,35.816,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1186,920,0,'hunter_bot',0), +(60076,0,0,0,0,0,3745,0,3745,0,'Dhugru','Warlock Bot','',0,80,80,2,29,29,1,1.1,1.14286,1,0,21,43,0,220,1,2000,1804,8,4608,0,0,0,0,9,2,53.3984,73.4228,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1491,838,0,'warlock_bot',0), +(60077,0,0,0,0,0,3746,0,3746,0,'Swart','Shaman Bot','',0,80,80,2,29,29,1,1.1,1.14286,1,0,6,200,0,65,1,2000,2046,2,4608,0,0,0,0,7,2,24.552,33.759,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,93,861,0,'shaman_bot',0), +(60078,0,0,0,0,0,1324,0,1324,0,'Groldar','Warlock Bot','',0,80,80,2,29,29,1,1.1,1.14286,1,0,32,67,0,345,1,2000,0,8,4608,0,0,0,0,9,2,66.44,91.355,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1491,173,0,'warlock_bot',0), +(60079,0,0,0,0,0,1325,0,1325,0,'Mirket','Warlock Bot','',0,80,80,2,29,29,1,1.1,1.14286,1,0,32,67,0,345,1,2000,0,8,4608,0,0,0,0,9,2,66.44,91.355,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1491,173,0,'warlock_bot',0), +(60080,0,0,0,0,0,1326,0,1326,0,'Zevrost','Warlock Bot','',0,80,80,2,29,29,1,1.1,1.14286,1,0,32,67,0,345,1,2000,0,8,4608,0,0,0,0,9,2,66.44,91.355,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1491,173,0,'warlock_bot',0), +(60081,0,0,0,0,0,1360,0,1360,0,'Kardris','Shaman Bot','',0,80,80,2,29,29,1,1.1,1.14286,1,0,42,200,0,451,1,2000,0,2,4608,0,0,0,0,7,2,74.448,102.366,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,93,586,0,'shaman_bot',0), +(60082,0,0,0,0,0,1373,0,1373,0,'Ormak','Hunter Bot','',0,80,80,2,29,29,1,1.1,1.14286,1,0,42,88,0,451,1,2000,1551,2,4608,0,0,0,0,3,2,74.448,102.366,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1186,8,0,'hunter_bot',0), +(60083,0,0,0,0,0,1374,0,1374,0,'Grezz','Warrior Bot','',0,80,80,2,29,29,1,1.1,1.14286,1,0,42,88,0,451,1,2000,0,1,4608,0,0,0,0,1,2,74.448,102.366,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,110,1865,0,'warrior_bot',0), +(60084,0,0,0,0,0,1375,0,1375,0,'Sorek','Warrior Bot','',0,80,80,2,29,29,1,1.1,1.14286,1,0,32,67,0,345,1,2000,0,1,4608,0,0,0,0,1,2,66.44,91.355,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1362,725,0,'warrior_bot',0), +(60085,0,0,0,0,0,4231,0,4231,0,'Siantsu','Shaman Bot','',0,80,80,2,29,29,1,1.1,1.14286,1,0,23,200,0,247,1,2000,0,2,4608,0,0,0,0,7,2,56.672,77.924,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,93,170,0,'shaman_bot',0), +(60086,0,0,0,0,0,4239,0,4239,0,'Xorjuul','Hunter Bot','',0,80,80,2,29,29,1,1.1,1.14286,1,0,32,67,0,345,1,2000,1661,2,4608,0,0,0,0,3,2,66.44,91.355,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1186,613,0,'hunter_bot',0), +(60087,0,0,0,0,0,4241,0,4241,0,'Siandur','Hunter Bot','',0,80,80,2,29,29,1,1.1,1.14286,1,0,23,48,0,247,1,2000,1771,2,4608,0,0,0,0,3,2,56.672,77.924,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1186,613,0,'hunter_bot',0), +(60088,0,0,0,0,0,4242,0,4242,0,'Zelmak','Warrior Bot','',0,80,80,2,29,29,1,1.1,1.14286,1,0,23,48,0,247,1,2000,0,1,4608,0,0,0,0,1,2,56.672,77.924,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1022,1867,0,'warrior_bot',0), +(60089,0,0,0,0,0,7915,0,7915,0,'ClaudeErksine','Hunter Bot','',0,80,80,2,55,55,1,1.14,1.14286,1,0,23,48,0,247,1,1610,1771,2,4608,0,0,3,0,3,3,56.672,77.924,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1186,0,0,'hunter_bot',0), +(60090,0,0,0,0,0,1721,0,1721,0,'Alyissia','Warrior Bot','',0,80,80,2,80,80,1,0.97,1.14286,1,0,50,87,0,322,1,2000,0,1,4608,0,0,0,0,1,4,18.392,25.289,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,394,639,0,'warrior_bot',0), +(60091,0,0,0,0,0,1725,0,1725,0,'FrahunMurmombre','Rogue Bot','',0,80,80,2,80,80,1,0.97,1.14286,1,0,5,43,0,1489,1,2000,0,4,4608,0,0,0,0,4,4,18.392,25.289,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1206,220,0,'rogue_bot',0), +(60092,0,0,0,0,0,1733,0,1733,0,'Shanda','Priest Bot','',0,80,80,2,80,80,1,0.96,1.14286,1,0,3,9,0,42,1,2000,0,8,4608,0,0,0,0,5,4,16.808,23.111,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,604,0,'priest_bot',0), +(60093,0,0,0,0,0,1732,0,1732,0,'Mardant','Druid Bot','',0,80,80,2,80,80,1,0.97,1.14286,1,0,5,367,0,45,1,2000,0,2,4608,0,0,0,0,11,4,18.392,25.289,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,93,114,0,'druid_bot',0), +(60094,0,0,0,0,0,1707,0,1707,0,'Kyra','Warrior Bot','',0,80,80,2,80,80,1,1.01,1.14286,1,0,25,87,0,422,1,2000,0,1,4608,0,0,0,0,1,4,30.4304,41.8418,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1362,1913,0,'warrior_bot',0), +(60095,0,0,0,0,0,1704,0,1704,0,'Jannok','Rogue Bot','',0,80,80,2,80,80,1,1.02,1.14286,1,0,9,73,0,1489,1,2000,0,4,4608,0,0,0,0,4,4,31.856,43.802,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1204,628,0,'rogue_bot',0), +(60096,0,0,0,0,0,1708,0,1708,0,'Laurna','Priest Bot','',0,80,80,2,80,80,1,1.03,1.14286,1,0,9,20,0,100,1,2000,0,8,4608,0,0,0,0,5,4,33.264,45.738,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,94,0,'priest_bot',0), +(60097,0,0,0,0,0,1706,0,1706,0,'Kal','Druid Bot','',0,80,80,2,80,80,1,1.03,1.14286,1,0,10,214,0,107,1,2000,0,2,4608,0,0,0,0,11,4,34.6544,47.6498,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,93,85,0,'druid_bot',0), +(60098,0,0,0,0,0,4296,0,4296,0,'Harruk','Hunter Bot','',0,80,80,2,29,29,1,1.1,1.14286,1,0,23,48,0,247,1,2000,1771,2,4608,0,0,3,0,3,2,56.672,77.924,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1186,344,0,'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.14286,1,0,23,48,0,247,1,2000,1100,2,4608,0,0,3,0,3,6,56.672,77.924,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1186,865,0,'hunter_bot',0), +(60100,0,0,0,0,0,4304,0,4304,0,'Bolyun','Hunter Bot','',0,80,80,2,80,80,1,1.05,1.14286,1,0,23,48,0,247,1,2000,1235,2,4608,0,0,3,0,3,4,56.672,77.924,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1186,0,0,'hunter_bot',0), +(60101,0,0,0,0,0,1897,0,1897,0,'Taijin','Priest Bot','',0,80,80,2,126,126,1,1.1,1.14286,1,0,8,16,0,84,1,2000,2013,8,4608,0,0,0,0,5,8,28.9872,39.8574,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,94,0,'priest_bot',0), +(60102,0,0,0,0,0,4068,0,4068,0,'Kenjai','Priest Bot','',0,80,80,2,126,126,1,1.1,1.14286,1,0,3,9,0,42,1,2000,2101,8,4608,0,0,0,0,5,8,16.808,23.111,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,458,0,'priest_bot',0), +(60103,0,0,0,0,0,2066,0,2066,0,'Danlaar','Hunter Bot','',0,80,80,2,80,80,1,1.05,1.14286,1,0,19,40,0,205,1,2000,1382,2,4608,0,0,0,0,3,4,51.128,70.301,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1186,1,0,'hunter_bot',0), +(60104,0,0,0,0,0,2196,0,2196,0,'Ariasta','Warrior Bot','',0,80,80,2,80,80,1,1.26,1.14286,1,0,42,88,0,451,1,2000,0,1,4608,0,0,0,0,1,4,74.448,102.366,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,110,822,0,'warrior_bot',0), +(60105,0,0,0,0,0,2198,0,2198,0,'Sildanair','Warrior Bot','',0,80,80,2,80,80,1,1.14,1.14286,1,0,23,48,0,247,1,2000,0,1,4608,0,0,0,0,1,4,56.672,77.924,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1362,281,0,'warrior_bot',0), +(60106,0,0,0,0,0,2200,0,2200,0,'Astarii','Priest Bot','',0,80,80,2,80,80,1,1.26,1.14286,1,0,42,88,0,451,1,2000,0,8,4608,0,0,0,0,5,4,74.448,102.366,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,94,0,'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.14286,1,0,32,67,0,345,1,2000,0,8,4608,0,0,0,0,5,4,66.44,91.355,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,173,0,'priest_bot',0), +(60108,0,0,0,0,0,2202,0,2202,0,'Lariia','Priest Bot','',0,80,80,2,80,80,1,1.14,1.14286,1,0,23,48,0,247,1,2000,0,8,4608,0,0,0,0,5,4,56.672,77.924,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,838,0,'priest_bot',0), +(60109,0,0,0,0,0,2231,0,2231,0,'Syurna','Rogue Bot','',0,80,80,2,80,80,1,1.14,1.14286,1,0,23,48,0,1489,1,2000,0,4,4608,0,0,0,0,4,4,56.672,77.924,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1204,220,0,'rogue_bot',0), +(60110,0,0,0,0,0,7669,0,7669,0,'Elissa','Mage Bot','',0,80,80,2,80,80,1,1.11,1.14286,1,0,19,40,0,205,1,2000,0,8,4608,0,0,0,0,8,4,51.128,70.301,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,96,0,'mage_bot',0), +(60111,0,0,0,0,0,2252,0,2252,0,'Erion','Rogue Bot','',0,80,80,2,80,80,1,1.26,1.14286,1,0,42,88,0,1489,1,2000,0,4,4608,0,0,0,0,4,4,74.448,102.366,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1206,277,0,'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.14286,1,0,32,67,0,1489,1,2000,0,4,4608,0,0,0,0,4,4,66.44,91.355,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1204,1913,0,'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.14286,1,0,32,267,0,345,1,2000,0,2,4608,0,0,0,0,11,4,66.44,91.355,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,93,258,0,'druid_bot',0), +(60114,0,0,0,0,0,2255,0,2255,0,'Fylerian','Druid Bot','',0,80,80,2,80,80,1,1.14,1.14286,1,0,23,348,0,247,1,2000,0,2,4608,0,0,0,0,11,4,56.672,77.924,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,93,358,0,'druid_bot',0), +(60115,0,0,0,0,0,2416,0,2416,0,'Caelyb','Hunter Bot','',0,80,80,2,80,80,1,1.05,1.14286,1,0,23,48,0,247,1,2000,1186,2,4608,0,0,3,0,3,4,56.672,77.924,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1186,1073,0,'hunter_bot',0), +(60116,0,0,0,0,0,2675,0,2675,0,'Kaal','Warlock Bot','',0,80,80,2,68,68,1,1.1,1.14286,1,0,42,88,0,451,1,2000,0,8,4608,0,0,0,0,9,5,74.448,102.366,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1491,110,0,'warlock_bot',0), +(60117,0,0,0,0,0,16800,0,16800,0,'Lana','Warlock Bot','',0,80,80,2,68,68,1,1.1,1.14286,1,0,32,67,0,345,1,2000,0,8,4608,0,0,0,0,9,5,66.44,91.355,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1491,1784,0,'warlock_bot',0), +(60118,0,0,0,0,0,2646,0,2646,0,'Richard','Warlock Bot','',0,80,80,2,68,68,1,1.1,1.14286,1,0,23,48,0,247,1,2000,0,8,4608,0,0,0,0,9,5,56.672,77.924,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1491,1217,0,'warlock_bot',0), +(60119,0,0,0,0,0,10214,0,10214,0,'Kaelystia','Mage Bot','',0,80,80,2,68,68,1,1.1,1.14286,1,0,42,88,0,451,1,2000,0,8,4608,0,0,0,0,8,5,74.448,102.366,100,6,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,96,8388624,'mage_bot',0), +(60120,0,0,0,0,0,2644,0,2644,0,'Pierce','Mage Bot','',0,80,80,2,68,68,1,1.1,1.14286,1,0,32,67,0,345,1,2000,0,8,4608,0,0,0,0,8,5,66.44,91.355,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,173,0,'mage_bot',0), +(60121,0,0,0,0,0,2657,0,2657,0,'Anastasia','Mage Bot','',0,80,80,2,68,68,1,1.1,1.14286,1,0,23,48,0,247,1,2000,0,8,4608,0,0,0,0,8,5,56.672,77.924,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,173,0,'mage_bot',0), +(60122,0,0,0,0,0,2620,0,2620,0,'Chris','Warrior Bot','',0,80,80,2,68,68,1,1.1,1.14286,1,0,42,88,0,451,1,2000,0,1,4608,0,0,0,0,1,5,74.448,102.366,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1022,801,0,'warrior_bot',0), +(60123,0,0,0,0,0,2658,0,2658,0,'Angela','Warrior Bot','',0,80,80,2,68,68,1,1.1,1.14286,1,0,32,67,0,345,1,2000,0,1,4608,0,0,0,0,1,5,66.44,91.355,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1362,1881,0,'warrior_bot',0), +(60124,0,0,0,0,0,2614,0,2614,0,'Baltus','Warrior Bot','',0,80,80,2,68,68,1,1.1,1.14286,1,0,23,48,0,247,1,2000,0,1,4608,0,0,0,0,1,5,56.672,77.924,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,110,1882,0,'warrior_bot',0), +(60125,0,0,0,0,0,3054,0,3054,0,'Kelv','Warrior Bot','',0,80,80,2,55,55,1,1.17,1.14286,1,0,27,57,0,294,1,1560,1716,1,4608,0,0,0,0,1,3,61.776,84.942,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1362,136,0,'warrior_bot',0), +(60126,0,0,0,0,0,3055,0,3055,0,'Bilban','Warrior Bot','',0,80,80,2,875,875,1,1.35,1.14286,1,0,27,57,0,294,1,1500,0,1,4608,0,0,0,0,1,7,61.776,84.942,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,394,675,0,'warrior_bot',0), +(60127,0,0,0,0,0,3056,0,3056,0,'Daera','Hunter Bot','',0,80,80,2,55,55,1,1.26,1.14286,1,0,42,88,0,451,1,1410,1551,2,4608,0,0,0,0,3,3,74.448,102.366,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1186,613,0,'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.14286,1,0,32,67,0,345,1,2000,1033,2,4608,0,0,0,0,3,3,66.44,91.355,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1186,10,0,'hunter_bot',0), +(60129,0,0,0,0,0,3073,0,3073,0,'Regnus','Hunter Bot','',0,80,80,2,55,55,1,1.14,1.14286,1,0,23,48,0,247,1,2000,1012,2,4608,0,0,0,0,3,3,56.672,77.924,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1186,10,0,'hunter_bot',0), +(60130,0,0,0,0,0,3086,0,3086,0,'Theodrus','Priest Bot','',0,80,80,2,55,55,1,1.26,1.14286,1,0,42,88,0,451,1,2000,0,8,4608,0,0,0,0,5,3,74.448,102.366,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,94,0,'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.14286,1,0,32,67,0,345,1,2000,0,8,4608,0,0,0,0,5,3,66.44,91.355,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,173,0,'priest_bot',0), +(60132,0,0,0,0,0,3085,0,3085,0,'Toldren','Priest Bot','',0,80,80,2,55,55,1,1.14,1.14286,1,0,23,48,0,247,1,2000,0,8,4608,0,0,0,0,5,3,56.672,77.924,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,838,0,'priest_bot',0), +(60134,0,0,0,0,0,3108,0,3108,0,'Bink','Mage Bot','',0,80,80,2,875,875,1,1.31,1.14286,1,0,23,48,0,247,1,2000,0,8,4608,0,0,0,0,8,7,56.672,77.924,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,838,0,'mage_bot',0), +(60135,0,0,0,0,0,10214,0,10214,0,'Juli','Mage Bot','',0,80,80,2,875,875,1,1.38,1.14286,1,0,32,67,0,345,1,2000,0,8,4608,0,0,0,0,8,7,66.44,91.355,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,173,0,'mage_bot',0), +(60136,0,0,0,0,0,3109,0,3109,0,'Nittegousse','Mage Bot','',0,80,80,2,55,55,1,1.45,1.14286,1,0,42,88,0,451,1,2000,0,8,4608,0,0,0,0,8,3,74.448,102.366,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,96,0,'mage_bot',0), +(60137,0,0,0,0,0,3089,0,3089,0,'Valgar','Paladin Bot','',0,80,80,2,55,55,1,1.26,1.14286,1,0,42,88,0,451,1,2000,0,2,4608,0,0,0,0,2,3,74.448,102.366,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,205,105,0,'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.14286,1,0,32,67,0,345,1,2000,0,2,4608,0,0,0,0,2,3,66.44,91.355,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,204,799,0,'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.14286,1,0,32,67,0,345,1,2000,0,2,4608,0,0,0,0,2,3,66.44,91.355,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,205,799,0,'paladin_bot',0), +(60140,0,0,0,0,0,3101,0,3101,0,'Hulfdan','Rogue Bot','',0,80,80,2,55,55,1,1.26,1.14286,1,0,42,88,0,1489,1,2000,0,4,4608,0,0,0,0,4,3,74.448,102.366,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1206,1886,0,'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.14286,1,0,32,67,0,1489,1,2000,0,4,4608,0,0,0,0,4,3,66.44,91.355,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1204,220,0,'rogue_bot',0), +(60142,0,0,0,0,0,3113,0,3113,0,'Phenwick','Rogue Bot','',0,80,80,2,875,875,1,1.31,1.14286,1,0,23,48,0,1489,1,2000,0,4,4608,0,0,0,0,4,7,56.672,77.924,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1206,220,0,'rogue_bot',0), +(60143,0,0,0,0,0,3115,0,3115,0,'Coeurdechardon','Warlock Bot','',0,80,80,2,55,55,1,1.31,1.14286,1,0,23,48,0,247,1,2000,0,8,4608,0,0,0,0,9,3,56.672,77.924,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1491,838,0,'warlock_bot',0), +(60144,0,0,0,0,0,3116,0,3116,0,'Eglantin','Warlock Bot','',0,80,80,2,875,875,1,1.38,1.14286,1,0,32,67,0,345,1,2000,0,8,4608,0,0,0,0,9,7,66.44,91.355,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1491,173,0,'warlock_bot',0), +(60145,0,0,0,0,0,3122,0,3122,0,'Alexander','Warlock Bot','',0,80,80,2,55,55,1,1.26,1.14286,1,0,42,88,0,451,1,2000,0,8,4608,0,0,0,0,9,3,74.448,102.366,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1491,110,0,'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.14286,1,0,32,67,0,345,1,1500,0,1,4608,0,0,0,0,1,1,66.44,91.355,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1022,727,0,'warrior_bot',0), +(60147,0,0,0,0,0,3287,0,3287,0,'Ilsa','Warrior Bot','',0,80,80,2,12,12,1,1.14,1.14286,1,0,23,48,0,247,1,1500,0,1,4608,0,0,0,0,1,1,56.672,77.924,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1362,613,0,'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.14286,1,0,32,67,0,345,1,2000,0,8,4608,0,0,0,0,5,1,66.44,91.355,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,173,0,'priest_bot',0), +(60149,0,0,0,0,0,3284,0,3284,0,'Arthur','Paladin Bot','',0,80,80,2,12,12,1,1.14,1.14286,1,0,23,48,0,247,1,2000,0,2,4608,0,0,0,0,2,1,56.672,77.924,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,204,799,0,'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.14286,1,0,32,67,0,345,1,2000,0,2,4608,0,0,0,0,2,1,66.44,91.355,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,205,799,0,'paladin_bot',0), +(60151,0,0,0,0,0,3291,0,3291,0,'Deline','Warlock Bot','',0,80,80,2,12,12,1,1.14,1.14286,1,0,23,48,0,247,1,2000,0,8,4608,0,0,0,0,9,1,56.672,77.924,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1491,838,0,'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.14286,1,0,32,67,0,345,1,2000,0,8,4608,0,0,0,0,9,1,66.44,91.355,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1491,173,0,'warlock_bot',0), +(60153,0,0,0,0,0,3292,0,3292,0,'Jennea','Mage Bot','',0,80,80,2,12,12,1,1.14,1.14286,1,0,23,48,0,247,1,2000,0,8,4608,0,0,0,0,8,1,56.672,77.924,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,0,0,'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.14286,1,0,32,67,0,345,1,2000,0,8,4608,0,0,0,0,8,1,66.44,91.355,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,173,0,'mage_bot',0), +(60155,0,0,0,0,0,3299,0,3299,0,'Kaerbrus','Hunter Bot','',0,80,80,2,80,80,1,1.1,1.14286,1,0,39,80,0,418,1,2000,1263,2,4608,0,0,0,0,3,4,72.2304,99.3168,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1186,613,0,'hunter_bot',0), +(60156,0,0,0,0,0,3300,0,3300,0,'Sheldras','Druid Bot','',0,80,80,2,12,12,1,1.26,1.14286,1,0,42,288,0,451,1,2000,0,2,4608,0,0,0,0,11,4,74.448,102.366,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,93,1071,0,'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.14286,1,0,32,467,0,345,1,2000,0,2,4608,0,0,0,0,11,4,66.44,91.355,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,93,258,0,'druid_bot',0), +(60158,0,0,0,0,0,3312,0,3312,0,'Einris','Hunter Bot','',0,80,80,2,12,12,1,1.26,1.14286,1,0,42,88,0,451,1,2000,1157,2,4608,0,0,0,0,3,1,74.448,102.366,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1186,613,0,'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.14286,1,0,32,67,0,345,1,2000,1379,2,4608,0,0,0,0,3,1,66.44,91.355,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1186,10,0,'hunter_bot',0), +(60160,0,0,0,0,0,3310,0,3310,0,'Thorfin','Hunter Bot','',0,80,80,2,12,12,1,1.14,1.14286,1,0,23,48,0,247,1,2000,1175,2,4608,0,0,0,0,3,1,56.672,77.924,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1186,298,0,'hunter_bot',0), +(60161,0,0,0,0,0,10171,0,10171,0,'UnThuwa','Mage Bot','',0,80,80,2,126,126,1,1.1,1.14286,1,0,6,12,0,59,1,2000,2057,8,4608,0,0,0,0,8,8,23.0384,31.6778,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,143,0,'mage_bot',0), +(60162,0,0,0,0,0,4524,0,4524,0,'Pephredo','Mage Bot','',0,80,80,2,29,29,1,1.1,1.14286,1,0,42,88,0,451,1,2000,1551,8,4608,0,0,0,0,8,2,74.448,102.366,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,559,0,'mage_bot',0), +(60163,0,0,0,0,0,4522,0,4522,0,'Enyo','Mage Bot','',0,80,80,2,29,29,1,1.1,1.14286,1,0,32,67,0,345,1,2000,1661,8,4608,0,0,0,0,8,2,66.44,91.355,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,99,0,'mage_bot',0), +(60164,0,0,0,0,0,4526,0,4526,0,'Mai','Mage Bot','',0,80,80,2,126,126,1,1.1,1.14286,1,0,3,4,0,26,1,2000,2101,8,4608,0,0,0,0,8,8,16.808,23.111,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,143,0,'mage_bot',0), +(60165,0,0,0,0,0,4523,0,4523,0,'Deino','Mage Bot','',0,80,80,2,29,29,1,1.1,1.14286,1,0,23,48,0,247,1,2000,1771,8,4608,0,0,0,0,8,2,56.672,77.924,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,21,0,'mage_bot',0), +(60166,0,0,0,0,0,4665,0,4665,0,'Birgitte','Mage Bot','',0,80,80,2,68,68,1,1.1,1.14286,1,0,27,57,0,294,1,1000,0,8,4608,0,0,0,0,8,5,61.776,84.942,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,173,0,'mage_bot',0), +(60167,0,0,0,0,0,12849,0,12849,0,'Thuul','Mage Bot','',0,80,80,2,29,29,1,1.1,1.14286,1,0,27,57,0,294,1,1000,1716,8,4608,0,0,0,0,8,2,61.776,84.942,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,173,0,'mage_bot',0), +(60168,0,0,0,0,0,4690,0,4690,0,'Zayus','Priest Bot','',0,80,80,2,29,29,1,1.1,1.14286,1,0,62,131,0,677,1,2000,1551,8,4608,0,0,0,0,5,2,74.448,102.366,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,143,0,'priest_bot',0), +(60169,0,0,0,0,0,10473,0,10473,0,'Xyera','Priest Bot','',0,80,80,2,29,29,1,1.1,1.14286,1,0,32,67,0,345,1,2000,1661,8,4608,0,0,0,0,5,2,66.44,91.355,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,228,0,'priest_bot',0), +(60170,0,0,0,0,0,4711,0,4711,0,'Urkyo','Priest Bot','',0,80,80,2,29,29,1,1.1,1.14286,1,0,23,48,0,247,1,2000,1771,8,4608,0,0,0,0,5,2,56.672,77.924,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,85,0,'priest_bot',0), +(60171,0,0,0,0,0,6060,0,6060,0,'Uthelnay','Mage Bot','',0,80,80,2,126,126,1,1.1,1.14286,1,0,23,48,0,247,1,2000,1771,8,4608,0,0,0,0,8,8,56.672,77.924,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,1071,0,'mage_bot',0), +(60172,0,0,0,0,0,6072,0,6072,0,'Dink','Mage Bot','',0,80,80,2,875,875,1,1.31,1.14286,1,0,23,48,0,247,1,2000,0,8,4608,0,0,0,0,8,7,56.672,77.924,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,838,0,'mage_bot',0), +(60173,0,0,0,0,0,6071,0,6071,0,'Darnath','Warrior Bot','',0,80,80,2,80,80,1,1.23,1.14286,1,0,36,77,0,394,1,2000,0,1,4608,0,0,0,0,1,4,70.664,97.163,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,394,277,0,'warrior_bot',0), +(60174,0,0,0,0,0,7356,0,7356,0,'Karman','Paladin Bot','',0,80,80,2,894,894,1,1.25,1.14286,1,0,27,57,0,294,1,2000,0,2,4608,0,0,0,0,2,1,61.776,84.942,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,204,117,0,'paladin_bot',0), +(60175,0,0,0,0,0,11037,0,11037,0,'Evencane','Warrior Bot','',0,80,80,2,894,894,1,1.25,1.14286,1,0,27,57,0,294,1,2000,0,1,4608,0,0,0,0,1,1,61.776,84.942,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,110,639,0,'warrior_bot',0), +(60176,0,0,0,0,0,7357,0,7357,0,'Jannos','Druid Bot','',0,80,80,2,104,104,1,1.1,1.14286,1,0,32,200,0,1034,1,2000,0,2,4608,0,0,0,0,11,6,66.44,91.355,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,93,119,0,'druid_bot',0), +(60177,0,0,0,0,0,7538,0,7538,0,'Alenndaar','Hunter Bot','',0,80,80,2,1076,1076,1,1.05,1.14286,1,0,14,28,0,143,1,2000,1012,2,4608,0,0,0,0,3,4,41.3424,56.8458,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1186,886,0,'hunter_bot',0), +(60178,0,0,0,0,0,10738,0,10738,0,'Golhine','Druid Bot','',0,80,80,2,80,80,1,1.1,1.14286,1,0,42,288,0,451,1,2000,0,2,4608,0,0,0,0,11,4,74.448,102.366,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,93,1252,0,'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.14286,1,0,23,48,0,247,1,2000,1178,2,4608,0,0,3,0,3,6,56.672,77.924,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1186,6,0,'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.1,1.14286,1,0,23,48,0,247,1,2000,1771,2,4608,0,0,3,0,3,2,56.672,77.924,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1186,886,0,'hunter_bot',0), +(60181,0,0,0,0,0,9338,0,9338,0,'Belia','Hunter Bot','',0,80,80,2,55,55,1,1.14,1.14286,1,0,23,48,0,247,1,2000,1426,2,4608,0,0,3,0,3,3,56.672,77.924,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1186,1081,0,'hunter_bot',0), +(60182,0,0,0,0,0,10245,0,10245,0,'Dargh','Hunter Bot','',0,80,80,2,55,55,1,1.05,1.14286,1,0,12,25,0,128,1,1760,1936,2,4608,0,0,0,0,3,3,38.72,53.24,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1186,19,0,'hunter_bot',0), +(60183,0,0,0,0,0,11044,0,11044,0,'Meideros','Priest Bot','',0,80,80,2,80,80,1,1.08,1.14286,1,0,16,32,0,164,1,2000,0,8,4608,0,0,0,0,5,4,45.144,62.073,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,0,0,'priest_bot',0), +(60184,0,0,0,0,0,11048,0,11048,0,'Presse','Priest Bot','',0,80,80,2,1076,1076,1,1.26,1.14286,1,0,42,88,0,451,1,2000,0,8,4608,0,0,0,0,5,4,74.448,102.366,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,0,0,'priest_bot',0), +(60185,0,0,0,0,0,11053,0,11053,0,'Rohan','Priest Bot','',0,80,80,2,122,122,1,1.26,1.14286,1,0,42,88,0,451,1,2000,0,8,4608,0,0,0,0,5,3,74.448,102.366,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,127,0,0,'priest_bot',0), +(60186,0,0,0,0,0,12053,0,12053,0,'Loganaar','Druid Bot','',0,80,80,2,994,994,1,1.1,1.14286,1,0,33,269,0,353,1,2000,0,2,4608,0,0,0,0,11,4,67.32,92.565,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,93,1252,0,'druid_bot',0), +(60187,0,0,0,0,0,13171,0,13171,0,'Romano','Rogue Bot','',0,80,80,2,12,12,1,1.26,1.14286,1,0,42,88,0,1489,1,2000,0,4,4608,0,0,0,0,4,1,74.448,102.366,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,1204,734,0,'rogue_bot',0), +(60188,0,0,0,0,0,13341,0,13341,0,'Sagorne','Shaman Bot','',0,80,80,2,104,104,1,1.1,1.14286,1,0,32,200,0,345,1,2000,0,2,4608,0,0,0,0,7,6,66.44,91.355,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,93,458,0,'shaman_bot',0), +(60189,0,0,0,0,0,15522,0,15522,0,'Julia','Mage Bot','',0,80,80,2,1604,1604,1,1.125,1.14286,1,0,2,5,0,23,1,1500,0,8,4608,0,0,0,0,8,10,23.4783,39.2769,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,1,1,1,1,0,0,0,0,0,0,0,0,0,127,1022,0,'mage_bot',0), +(60190,0,0,0,0,0,15511,0,15511,0,'Jesthenis','Paladin Bot','',0,80,80,2,1604,1604,1,1.125,1.14286,1,0,2,5,0,23,1,1500,0,2,4608,0,0,0,0,2,10,23.4783,32.7308,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,1229,0,'paladin_bot',0), +(60191,0,0,0,0,0,15524,0,15524,0,'Invocateur','Warlock Bot','',0,80,80,2,1604,1604,1,1.125,1.14286,1,0,2,5,0,23,1,1500,0,8,4608,0,0,0,0,9,10,23.4783,39.2769,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,1,1,1,1,0,0,0,0,0,0,0,0,0,1491,1455,0,'warlock_bot',0), +(60192,0,0,0,0,0,15518,0,15518,0,'Matrone','Priest Bot','',0,80,80,2,1604,1604,1,1.125,1.14286,1,0,2,5,0,23,1,1500,0,8,4608,0,0,0,0,5,10,23.4783,39.2769,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,1,1,1,1,0,0,0,0,0,0,0,0,0,127,1306,0,'priest_bot',0), +(60193,0,0,0,0,0,2659,0,2659,0,'Eclaireur','Rogue Bot','',0,80,80,2,68,68,1,1.1,1.14286,1,0,62,99,0,1489,1,2000,0,4,4608,0,0,0,0,4,5,28.1739,39.2769,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,1,1,1,1,0,0,0,0,0,0,0,0,0,1204,0,0,'rogue_bot',0), +(60194,0,0,0,0,0,15520,0,15520,0,'Sallina','Hunter Bot','',0,80,80,2,1604,1604,1,1.125,1.14286,1,0,2,5,0,23,1,1500,1543,2,4608,0,0,0,0,3,10,23.4783,39.2769,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,1,1,1,1,0,0,0,0,0,0,0,0,0,1186,27,0,'hunter_bot',0), +(60195,0,0,0,0,0,16685,0,16685,0,'Noellene','Paladin Bot','',0,80,80,2,1604,1604,1,1.125,1.14286,1,0,2,7,0,33,1,1500,0,2,4608,0,0,0,0,2,10,56.3478,78.5538,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,1,1,1,1,0,0,0,0,0,0,0,0,0,204,725,0,'paladin_bot',0), +(60196,0,0,0,0,0,16707,0,16707,0,'Ponaris','Priest Bot','',0,80,80,2,1604,1604,1,1.125,1.14286,1,0,2,7,0,33,1,1500,0,8,4608,0,0,0,0,5,10,0,0,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,1,1,1,1,0,0,0,0,0,0,0,0,0,127,1096,0,'priest_bot',0), +(60197,0,0,0,0,0,16222,0,16222,0,'Keilnei','Hunter Bot','',0,80,80,2,1638,1638,1,1.125,1.14286,1,0,5,18,0,79,1,2000,1180,2,4608,0,0,0,0,3,11,28.1739,39.2769,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,1,1,1,1,0,0,0,0,0,0,0,0,0,1186,1619,0,'hunter_bot',0), +(60198,0,0,0,0,0,16223,0,16223,0,'Valaatu','Mage Bot','',0,80,80,2,1638,1638,1,1.125,1.14286,1,0,5,18,0,79,1,2000,0,8,4608,0,0,0,0,8,11,28.1739,39.2769,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,1,1,1,1,0,0,0,0,0,0,0,0,0,127,1624,0,'mage_bot',0), +(60199,0,0,0,0,0,16224,0,16224,0,'Aurelon','Paladin Bot','',0,80,80,2,1638,1638,1,1.125,1.14286,1,0,5,18,0,79,1,2000,0,2,4608,0,0,0,0,2,11,28.1739,39.2769,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,1,1,1,1,0,0,0,0,0,0,0,0,0,205,1610,0,'paladin_bot',0), +(60200,0,0,0,0,0,16225,0,16225,0,'Zalduun','Priest Bot','',0,80,80,2,1638,1638,1,1.25,1.14286,1,0,5,18,0,79,1,2000,0,8,4608,0,0,0,0,5,11,28.1739,39.2769,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,1,1,1,1,0,0,0,0,0,0,0,0,0,127,1626,0,'priest_bot',0), +(60201,0,0,0,0,0,16226,0,16226,0,'Kore','Warrior Bot','',0,80,80,2,1638,1638,1,1.125,1.14286,1,0,5,54,0,322,1,2000,0,1,4608,0,0,0,0,1,11,28.1739,39.2769,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,1,1,1,1,0,0,0,0,0,0,0,0,0,1022,1618,0,'warrior_bot',0), +(60202,0,0,0,0,0,16787,0,16787,0,'Alamma','Warlock Bot','',0,80,80,2,1604,1604,1,1.125,1.14286,1,0,14,53,0,236,1,2000,0,8,4608,0,0,0,0,9,10,0,0,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,1,1,1,1,0,0,0,0,0,0,0,0,0,1491,0,0,'warlock_bot',0), +(60203,0,0,0,0,0,16800,0,16800,0,'Talionia','Warlock Bot','',0,80,80,2,1604,1604,1,1.125,1.14286,1,0,12,42,0,185,1,2000,0,8,4608,0,0,0,0,9,10,0,0,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,1,1,1,1,0,0,0,0,0,0,0,0,0,1491,0,0,'warlock_bot',0), +(60204,0,0,0,0,0,16831,0,16831,0,'Zanien','Hunter Bot','',0,80,80,2,1604,1604,1,1.125,1.14286,1,0,18,66,0,290,1,2000,1180,2,4608,0,0,0,0,9,10,0,0,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,1,1,1,1,0,0,0,0,0,0,0,0,0,1186,0,0,'hunter_bot',0), +(60205,0,0,0,0,0,16781,0,16781,0,'Zaedana','Mage Bot','',0,80,80,2,1604,1604,1,1.125,1.14286,1,0,12,42,0,185,1,2000,0,8,4608,0,0,0,0,8,10,0,0,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,1,1,1,1,0,0,0,0,0,0,0,0,0,127,0,0,'mage_bot',0), +(60206,0,0,0,0,0,16824,0,16824,0,'Quithas','Mage Bot','',0,80,80,2,1604,1604,1,1.125,1.14286,1,0,14,53,0,236,1,2000,0,8,4608,0,0,0,0,8,10,0,0,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,1,1,1,1,0,0,0,0,0,0,0,0,0,127,0,0,'mage_bot',0), +(60207,0,0,0,0,0,16739,0,16739,0,'Harene','Druid Bot','',0,80,80,2,1604,1604,1,1.125,1.14286,1,0,18,366,0,290,1,2000,0,2,4608,0,0,0,0,11,6,0,0,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,1,1,1,1,0,0,0,0,0,0,0,0,0,93,0,0,'druid_bot',0), +(60208,0,0,0,0,0,16778,0,16778,0,'Tana','Hunter Bot','',0,80,80,2,1604,1604,1,1.125,1.14286,1,0,12,42,0,185,1,2000,1168,2,4608,0,0,0,0,3,10,0,0,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,1,1,1,1,0,0,0,0,0,0,0,0,0,1186,0,0,'hunter_bot',0), +(60209,0,0,0,0,0,16816,0,16816,0,'Oninath','Hunter Bot','',0,80,80,2,1604,1604,1,1.125,1.14286,1,0,14,53,0,236,1,1000,1084,2,4608,0,0,0,0,3,10,0,0,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,1,1,1,1,0,0,0,0,0,0,0,0,0,1186,0,0,'hunter_bot',0), +(60210,0,0,0,0,0,16829,0,16829,0,'Bachi','Paladin Bot','',0,80,80,2,1604,1604,1,0.93,1.14286,1,0,38,68,0,367,1,2000,0,2,4608,0,0,0,0,2,10,8.624,11.858,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,3,1,1,1,0,0,0,0,0,0,0,0,0,204,0,0,'paladin_bot',0), +(60211,0,0,0,0,0,16767,0,16767,0,'Zelanis','Rogue Bot','',0,80,80,2,1604,1604,1,1.125,1.14286,1,0,62,80,0,1489,1,2000,0,4,4608,0,0,0,0,4,10,0,0,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,1,1,1,1,0,0,0,0,0,0,0,0,0,1206,0,0,'rogue_bot',0), +(60212,0,0,0,0,0,16798,0,16798,0,'Elara','Rogue Bot','',0,80,80,2,1604,1604,1,1.125,1.14286,1,0,52,77,0,1489,1,2000,0,4,4608,0,0,0,0,4,10,0,0,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,1,1,1,1,0,0,0,0,0,0,0,0,0,1204,0,0,'rogue_bot',0), +(60213,0,0,0,0,0,16858,0,16858,0,'Shalannius','Druid Bot','',0,80,80,2,1638,1638,1,1.125,1.14286,1,0,100,300,0,290,1,2000,0,2,4608,0,0,0,0,11,6,0,0,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,1,1,1,1,0,0,0,0,0,0,0,0,0,93,0,0,'druid_bot',0), +(60214,0,0,0,0,0,17434,0,17434,0,'Deremiis','Hunter Bot','',0,80,80,2,1638,1638,1,1.125,1.14286,1,0,14,53,0,236,1,2000,1076,2,4608,0,0,0,0,3,11,0,0,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,1,1,1,1,0,0,0,0,0,0,0,0,0,1186,0,0,'hunter_bot',0), +(60215,0,0,0,0,0,17247,0,17247,0,'Caedmos','Priest Bot','',0,80,80,2,1638,1638,1,1.125,1.14286,1,0,18,66,0,290,1,2000,0,8,4608,0,0,0,0,5,11,0,0,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,1,1,1,1,0,0,0,0,0,0,0,0,0,127,0,0,'priest_bot',0), +(60216,0,0,0,0,0,17225,0,17225,0,'Baatun','Paladin Bot','',0,80,80,2,1638,1638,1,1.125,1.14286,1,0,18,66,0,290,1,2000,0,2,4608,0,0,0,0,2,11,0,0,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,1,1,1,1,0,0,0,0,0,0,0,0,0,205,0,0,'paladin_bot',0), +(60217,0,0,0,0,0,17212,0,17212,0,'Ahonan','Warrior Bot','',0,80,80,2,1638,1638,1,1.125,1.14286,1,0,12,42,0,453,1,2000,0,1,4608,0,0,0,0,1,11,0,0,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,1,1,1,1,0,0,0,0,0,0,0,0,0,394,0,0,'warrior_bot',0), +(60218,0,0,0,0,0,17598,0,17598,0,'Firmanvaar','Shaman Bot','',0,80,80,2,1638,1638,1,1.125,1.14286,1,0,5,200,0,79,1,2000,0,2,4608,0,0,0,0,7,11,28.1739,39.2769,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,1,1,1,1,0,0,0,0,0,0,0,0,0,93,1622,0,'shaman_bot',0), +(60219,0,0,0,0,0,16860,0,16860,0,'Actron','Hunter Bot','',0,80,80,2,1638,1638,1,1.125,1.14286,1,0,2,5,0,23,1,2000,1125,2,4608,0,0,0,0,3,11,46.9565,65.4615,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,1,1,1,1,0,0,0,0,0,0,0,0,0,1186,0,0,'hunter_bot',0), +(60220,0,0,0,0,0,17213,0,17213,0,'Behomat','Warrior Bot','',0,80,80,2,1638,1638,1,1.125,1.14286,1,0,18,66,0,290,1,2000,0,1,4608,0,0,0,0,1,11,0,0,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,1,1,1,1,0,0,0,0,0,0,0,0,0,1362,0,0,'warrior_bot',0), +(60221,0,0,0,0,0,17600,0,17600,0,'Nobundo','Shaman Bot','',0,80,80,2,1638,1638,1,1.125,1.14286,1,0,21,200,0,345,1,2000,0,2,4608,0,0,0,0,7,11,346.02,481.06,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,1,1,1,1,0,0,0,0,0,0,0,0,0,93,85,0,'shaman_bot',0), +(60222,0,0,0,0,0,17599,0,17599,0,'Tuluun','Shaman Bot','',0,80,80,2,1638,1638,1,1.125,1.14286,1,0,41,200,0,393,1,2000,0,2,4608,0,0,0,0,7,11,70.4348,98.1923,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,1,1,1,1,0,0,0,0,0,0,0,0,0,93,1086,0,'shaman_bot',0), +(60223,0,0,0,0,0,16914,0,16914,0,'Sulaa','Shaman Bot','',0,80,80,2,1638,1638,1,1.125,1.14286,1,0,14,200,0,236,1,2000,0,2,4608,0,0,0,0,7,11,0,0,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,1,1,1,1,0,0,0,0,0,0,0,0,0,93,85,0,'shaman_bot',0), +(60224,0,0,0,0,0,17215,0,17215,0,'Ruada','Warrior Bot','',0,80,80,2,1638,1638,1,1.125,1.14286,1,0,2,76,0,643,1,2000,0,1,4608,0,0,0,0,1,11,0,0,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,1,1,1,1,0,0,0,0,0,0,0,0,0,1022,797,0,'warrior_bot',0), +(60225,0,0,0,0,0,17233,0,17233,0,'Semid','Mage Bot','',0,80,80,2,1638,1638,1,1.125,1.14286,1,0,2,5,0,23,1,2000,0,8,4608,0,0,0,0,8,11,0,0,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,1,1,1,1,0,0,0,0,0,0,0,0,0,127,1304,0,'mage_bot',0), +(60226,0,0,0,0,0,17232,0,17232,0,'Guvan','Priest Bot','',0,80,80,2,1638,1638,1,1.125,1.14286,1,0,2,5,0,26,1,2000,0,8,4608,0,0,0,0,5,11,0,0,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,1,1,1,1,0,0,0,0,0,0,0,0,0,127,94,0,'priest_bot',0), +(60227,0,0,0,0,0,17234,0,17234,0,'Tullas','Paladin Bot','',0,80,80,2,1638,1638,1,1.125,1.14286,1,0,2,5,0,23,1,2000,0,2,4608,0,0,0,0,2,11,0,0,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,1,1,1,1,0,0,0,0,0,0,0,0,0,204,422,0,'paladin_bot',0), +(60228,0,0,0,0,0,17488,0,17488,0,'Killac','Hunter bot','',0,80,80,2,1638,1638,1,1.125,1.14286,1,0,12,42,0,185,1,2000,0,2,4608,0,0,0,0,3,11,0,0,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,'hunter_bot',0), +(60229,0,0,0,0,0,17226,0,17226,0,'Jol','Paladin Bot','',0,80,80,2,1638,1638,1,1.125,1.14286,1,0,14,53,0,236,1,2000,0,2,4608,0,0,0,0,2,11,0,0,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,1,1,1,1,0,0,0,0,0,0,0,0,0,205,0,0,'paladin_bot',0), +(60230,0,0,0,0,0,17248,0,17248,0,'Fallat','Priest Bot','',0,80,80,2,1638,1638,1,1.125,1.14286,1,0,14,53,0,236,1,2000,0,8,4608,0,0,0,0,5,11,0,0,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,1,1,1,1,0,0,0,0,0,0,0,0,0,127,0,0,'priest_bot',0), +(60231,0,0,0,0,0,17243,0,17243,0,'Harnan','Mage Bot','',0,80,80,2,1638,1638,1,1.125,1.14286,1,0,14,53,0,236,1,2000,0,8,4608,0,0,0,0,8,11,0,0,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,1,1,1,1,0,0,0,0,0,0,0,0,0,127,0,0,'mage_bot',0), +(60232,0,0,0,0,0,17241,0,17241,0,'Bati','Mage Bot','',0,80,80,2,1638,1638,1,1.125,1.14286,1,0,12,42,0,185,1,2000,0,8,4608,0,0,0,0,8,11,0,0,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,1,1,1,1,0,0,0,0,0,0,0,0,0,127,0,0,'mage_bot',0), +(60233,0,0,0,0,0,17792,0,17792,0,'Hobahken','Shaman Bot','',0,80,80,2,1638,1638,1,1.08,1.14286,1,0,18,200,0,290,1,2000,0,2,4608,0,0,0,0,7,11,0,0,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,1,1,1,1,0,0,0,0,0,0,0,0,0,93,85,0,'shaman_bot',0), +(60234,0,0,0,0,0,6820,0,6820,0,'Gurrag','Shaman Bot','',0,80,80,2,1638,1638,1,1.08,1.14286,1,0,12,200,0,185,1,2000,0,2,4608,0,0,0,0,7,11,0,0,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,1,1,1,1,0,0,0,0,0,0,0,0,0,93,85,0,'shaman_bot',0), +(60235,0,0,0,0,0,19596,0,19596,0,'Auberose','Paladin Bot','',0,80,80,2,1602,1602,1,1.1,1.14286,1,0,176,176,0,367,1,2000,0,2,4608,0,0,0,0,2,10,0,0,100,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0',0,1,1,1,1,0,0,0,0,0,0,0,0,0,897,1552,0,'paladin_bot',0), +(60236,0,0,0,0,0,10335,10335,10335,10335,'Afina','Priest Bot','',0,80,80,2,35,35,1,0.95,1.14286,1,0,12,25,0,128,1,10000,0,8,4608,0,0,0,0,5,2,40,53,100,7,1,0,0,0,100,100,100,100,100,100,0,0,0,0,0,0,0,0,0,0,0,0,'',0,3,1,1,1,0,0,0,0,0,0,0,0,1,1370,0,0,'priest_bot',0), +(60237,0,0,0,0,0,1132,0,1132,0,'Voidwalker',NULL,NULL,0,80,80,2,14,14,0,1.1,1.14286,1,0,50,50,0,100,1,2000,0,1,0,0,16,0,0,1,0,23.0384,31.6778,100,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'PetAI',0,1,1.1,0.77,1,0,0,0,0,0,0,0,0,1,0,0,0,'',0), +(60238,0,0,0,0,0,1105,0,0,0,'Hunter\'s Pet',NULL,NULL,0,80,80,0,14,14,1,1.1,1.14286,1,0,87,117,0,214,1,2000,0,1,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,'PetAI',0,3,1,1,1,0,0,0,0,0,0,0,149,1,0,0,0,'',0); diff --git a/sql/Bots/world_bots_update.sql b/sql/Bots/world_bots_update.sql new file mode 100644 index 0000000..e312de0 --- /dev/null +++ b/sql/Bots/world_bots_update.sql @@ -0,0 +1,13 @@ +-- OPTIONAL UPDATE +-- Updated values I thought should be closer to the bots values at least for my server. +-- You can create your own values to be in line with your own server if these are not acceptable. + +UPDATE `creature_template` SET mindmg:=150, maxdmg:=350, attackpower:=300, baseattacktime:=2000, rangeattacktime:=2000, minrangedmg:=0, maxrangedmg:=0, rangedattackpower:=0, speed_walk:=1.5, speed_run:=1.5, resistance1:=100, resistance2:=100, resistance3:=100, resistance4:=100, resistance5:=100, resistance6:=100, InhabitType:=3, health_mod:=2, mana_mod:=2.7, armor_mod:=2, mechanic_immune_mask:=0, flags_extra:=0, AIName='' where entry >= 60000 && entry < 60239 and subname='Druid Bot'; +UPDATE `creature_template` SET mindmg:=700, maxdmg:=900, attackpower:=2700, baseattacktime:=2000, rangeattacktime:=2000, minrangedmg:=950, maxrangedmg:=1050, rangedattackpower:=3000, speed_walk:=1.5, speed_run:=1.5, resistance1:=100, resistance2:=100, resistance3:=100, resistance4:=100, resistance5:=100, resistance6:=100, InhabitType:=3, health_mod:=2, mana_mod:=2.7, armor_mod:=2, mechanic_immune_mask:=0, flags_extra:=0, AIName='' where entry >= 60000 && entry < 60239 and subname='Hunter Bot'; +UPDATE `creature_template` SET mindmg:=125, maxdmg:=350, attackpower:=70, baseattacktime:=2000, rangeattacktime:=2000, minrangedmg:=500, maxrangedmg:=900, rangedattackpower:=50, resistance1:=100, speed_walk:=1.5, speed_run:=1.5, resistance2:=100, resistance3:=100, resistance4:=100, resistance5:=100, resistance6:=100, InhabitType:=3, health_mod:=2, mana_mod:=2.7, armor_mod:=2, mechanic_immune_mask:=0, flags_extra:=0, AIName='' where entry >= 60000 && entry < 60239 and subname='Mage Bot'; +UPDATE `creature_template` SET mindmg:=200, maxdmg:=400, attackpower:=550, baseattacktime:=20000, rangeattacktime:=2000, minrangedmg:=0, maxrangedmg:=0, rangedattackpower:=80, resistance1:=100, speed_walk:=1.5, speed_run:=1.5, resistance2:=100, resistance3:=100, resistance4:=100, resistance5:=100, resistance6:=100, InhabitType:=3, health_mod:=2, mana_mod:=2.7, armor_mod:=2, mechanic_immune_mask:=0, flags_extra:=0, AIName='' where entry >= 60000 && entry < 60239 and subname='Paladin Bot'; +UPDATE `creature_template` SET mindmg:=100, maxdmg:=300, attackpower:=60, baseattacktime:=2000, rangeattacktime:=2000, minrangedmg:=300, maxrangedmg:=600, rangedattackpower:=50, resistance1:=100, speed_walk:=1.5, speed_run:=1.5, resistance2:=100, resistance3:=100, resistance4:=100, resistance5:=100, resistance6:=100, InhabitType:=3, health_mod:=2, mana_mod:=2.7, armor_mod:=2, mechanic_immune_mask:=0, flags_extra:=0, AIName='' where entry >= 60000 && entry < 60239 and subname='Priest Bot'; +UPDATE `creature_template` SET mindmg:=1200, maxdmg:=1500, attackpower:=4500, baseattacktime:=2000, rangeattacktime:=2000, minrangedmg:=900, maxrangedmg:=1100, rangedattackpower:=4100, speed_walk:=1.5, speed_run:=1.5, resistance1:=100, resistance2:=100, resistance3:=100, resistance4:=100, resistance5:=100, resistance6:=100, InhabitType:=3, health_mod:=2, mana_mod:=2.7, armor_mod:=2, mechanic_immune_mask:=0, flags_extra:=0, AIName='' where entry >= 60000 && entry < 60239 and subname='Rogue Bot'; +UPDATE `creature_template` SET mindmg:=150, maxdmg:=350, attackpower:=350, baseattacktime:=2000, rangeattacktime:=2000, minrangedmg:=0, maxrangedmg:=0, rangedattackpower:=70, speed_walk:=1.5, speed_run:=1.5, resistance1:=100, resistance2:=100, resistance3:=100, resistance4:=100, resistance5:=100, resistance6:=100, InhabitType:=3, health_mod:=2, mana_mod:=2.7, armor_mod:=2, mechanic_immune_mask:=0, flags_extra:=0, AIName='' where entry >= 60000 && entry < 60239 and subname='Shaman Bot'; +UPDATE `creature_template` SET mindmg:=225, maxdmg:=375, attackpower:=90, baseattacktime:=2000, rangeattacktime:=2000, minrangedmg:=500, maxrangedmg:=900, rangedattackpower:=60, speed_walk:=1.5, speed_run:=1.5, resistance1:=100, resistance2:=100, resistance3:=100, resistance4:=100, resistance5:=100, resistance6:=100, InhabitType:=3, health_mod:=2, mana_mod:=2.7, armor_mod:=2, mechanic_immune_mask:=0, flags_extra:=0, AIName='' where entry >= 60000 && entry < 60239 and subname='Warlock Bot'; +UPDATE `creature_template` SET mindmg:=800, maxdmg:=1000, attackpower:=4000, baseattacktime:=2000, rangeattacktime:=2000, minrangedmg:=425, maxrangedmg:=625, rangedattackpower:=250, speed_walk:=1.5, speed_run:=1.5, resistance1:=100, resistance2:=100, resistance3:=100, resistance4:=100, resistance5:=100, resistance6:=100, InhabitType:=3, health_mod:=2, mana_mod:=2.7, armor_mod:=2, mechanic_immune_mask:=0, flags_extra:=0, AIName='' where entry >= 60000 && entry < 60239 and subname='Warrior Bot'; diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt index e1b0921..7cd8acf 100644 --- a/sql/CMakeLists.txt +++ b/sql/CMakeLists.txt @@ -15,6 +15,7 @@ if( WITH_SQL ) scripts base create + Bots DESTINATION shared/trinity/sql ) diff --git a/src/server/game/AI/Bots/PlayerbotAI.cpp b/src/server/game/AI/Bots/PlayerbotAI.cpp new file mode 100644 index 0000000..1b944d7 --- /dev/null +++ b/src/server/game/AI/Bots/PlayerbotAI.cpp @@ -0,0 +1,4498 @@ +#include "Common.h" +#include "Database/DatabaseEnv.h" +#include "DBCStores.h" +#include "World.h" +#include "SpellMgr.h" +#include "PlayerbotAI.h" +#include "PlayerbotPriestAI.h" +#include "PlayerbotWarriorAI.h" +#include "PlayerbotShamanAI.h" +#include "PlayerbotRogueAI.h" +#include "PlayerbotPaladinAI.h" +#include "PlayerbotMageAI.h" +#include "PlayerbotDruidAI.h" +#include "PlayerbotWarlockAI.h" +#include "PlayerbotHunterAI.h" +#include "PlayerbotDeathKnightAI.h" +#include "Player.h" +#include "ObjectMgr.h" +#include "Chat.h" +#include "WorldPacket.h" +#include "Spell.h" +#include "Unit.h" +#include "Group.h" +#include "Player.h" +#include "SpellAuras.h" +#include "SpellAuraEffects.h" +#include "SharedDefines.h" +#include "GossipDef.h" +#include "Config.h" +#include <ctime> + +bool ChatHandler::BotHandleQuestAdd(const char *args) +{ + Player* player = getSelectedPlayer(); + if (!player) + { + SendSysMessage(LANG_NO_CHAR_SELECTED); + SetSentErrorMessage(true); + return false; + } + + // .addquest #entry' + // number or [name] Shift-click form |color|Hquest:quest_id:quest_level|h[name]|h|r + char* cId = extractKeyFromLink((char*)args,"Hquest"); + if (!cId) + return false; + + uint32 entry = atol(cId); + + Quest const* pQuest = sObjectMgr->GetQuestTemplate(entry); + + if (!pQuest) + { + PSendSysMessage(LANG_COMMAND_QUEST_NOTFOUND,entry); + SetSentErrorMessage(true); + return false; + } + + // check item starting quest (it can work incorrectly if added without item in inventory) + for (uint32 id = 0; id < sItemStorage.MaxEntry; id++) + { + ItemPrototype const *pProto = sItemStorage.LookupEntry<ItemPrototype>(id); + if (!pProto) + continue; + + if (pProto->StartQuest == entry) + { + PSendSysMessage(LANG_COMMAND_QUEST_STARTFROMITEM, entry, pProto->ItemId); + SetSentErrorMessage(true); + return false; + } + } + + // ok, normal (creature/GO starting) quest + if (player->CanAddQuest(pQuest, true)) + { + player->AddQuest(pQuest, NULL); + + if (player->CanCompleteQuest(entry)) + player->CompleteQuest(entry); + } + + return true; +} + +bool ChatHandler::BotHandleQuestRemove(const char *args) +{ + Player* player = getSelectedPlayer(); + if (!player) + { + SendSysMessage(LANG_NO_CHAR_SELECTED); + SetSentErrorMessage(true); + return false; + } + + // .removequest #entry' + // number or [name] Shift-click form |color|Hquest:quest_id:quest_level|h[name]|h|r + char* cId = extractKeyFromLink((char*)args,"Hquest"); + if (!cId) + return false; + + uint32 entry = atol(cId); + + Quest const* pQuest = sObjectMgr->GetQuestTemplate(entry); + + if (!pQuest) + { + PSendSysMessage(LANG_COMMAND_QUEST_NOTFOUND, entry); + SetSentErrorMessage(true); + return false; + } + + // remove all quest entries for 'entry' from quest log + for (uint8 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot) + { + uint32 quest = player->GetQuestSlotQuestId(slot); + if (quest == entry) + { + player->SetQuestSlot(slot,0); + + // we ignore unequippable quest items in this case, its' still be equipped + player->TakeQuestSourceItem(quest, false); + } + } + + // set quest status to not started (will updated in DB at next save) + player->SetQuestStatus(entry, QUEST_STATUS_NONE); + + SendSysMessage(LANG_COMMAND_QUEST_REMOVED); + return true; +} + + +/* +* Packets often compress the GUID (global unique identifier) +* This function extracts the guid from the packet and decompresses it. +* The first word (8 bits) in the packet represents how many words in the following packet(s) are part of +* the guid and what weight they hold. I call it the mask. For example) if mask is 01001001, +* there will be only 3 words. The first word is shifted to the left 0 times, +* the second is shifted 3 times, and the third is shifted 6. +*/ +uint64 extractGuid(WorldPacket &packet) +{ + uint8 mask; packet >> mask; + uint64 guid = 0; + uint8 bit = 0; + uint8 testMask = 1; + while(true) + { + if(mask & testMask) + { + uint8 word; packet >> word; + guid += (word << bit); + } + if(bit == 7) break; + ++bit; + testMask <<= 1; + } + return guid; +} + +//ChatHandler already implements some useful commands the master can call on bots +//These commands are protected inside the ChatHandler class so this class provides access to the commands +//we'd like to call on our bots +class PlayerbotChatHandler : protected ChatHandler +{ + public: + explicit PlayerbotChatHandler(Player *pMasterPlayer) : ChatHandler(pMasterPlayer){} + + bool revive(const Player &botPlayer){ return HandleReviveCommand(botPlayer.GetName()); } + bool teleport(const Player &botPlayer){ return HandleSummonCommand(botPlayer.GetName()); } + bool teleport(Player &botPlayer, const WorldLocation &loc){ return botPlayer.TeleportTo(loc,TELE_TO_GM_MODE); } + bool uninvite(const char *str){ return HandlePlayerbotCommand(str); } + void sysmessage(const char *str){ SendSysMessage(str); } + bool acceptQuest(const char *str){ return BotHandleQuestAdd(str); } + bool abandonQuest(const char *str) { return BotHandleQuestRemove(str); } +}; + +PlayerbotAI::PlayerbotAI(Player *const master, Player *const bot): m_master(master), m_bot(bot), +m_ignoreAIUpdatesUntilTime(0), m_combatOrder(ORDERS_NONE), m_ScenarioType(SCENARIO_PVEEASY), +m_TimeDoneEating(0), m_TimeDoneDrinking(0), m_CurrentlyCastingSpellId(0), m_IsFollowingMaster(true), +m_spellIdCommand(0), m_targetGuidCommand(0), m_classAI(0), isLooting(false), m_TimeRessurect(0), +m_FeastSpamTimer(0) +{ + + //If the player have a group, it's possible to add the bot. + if(master->GetGroup()) + { + Group *m_group = master->GetGroup(); + bool inGroup = false; + Group::MemberSlotList members = m_group->GetMemberSlots(); + + if(!m_group->IsFull() || + m_group->IsMember(bot->GetGUID()) ) + { + //check that bot is not already in the group, ie from a server crash + Group::MemberSlotList const &groupSlot = master->GetGroup()->GetMemberSlots(); + for(Group::member_citerator itr = groupSlot.begin(); itr != groupSlot.end(); itr++) + { + Player *tPlayer = master->GetObjPlayer(itr->guid); + if(itr->guid == bot->GetGUID()) inGroup = true; + } + if(!inGroup) m_group->AddMember(bot->GetGUID(), bot->GetName()); + } else { + //group is full so can't add bot + bot->Say("Group is full!", LANG_UNIVERSAL); + } + } else { + Group *m_group = new Group; + if(!m_group->Create(master->GetGUID(), master->GetName())) + { + delete m_group; + return; + } + sObjectMgr->AddGroup(m_group); + if(!m_group->IsFull()) m_group->AddMember(bot->GetGUID(), bot->GetName()); + } + + //get class specific AI + switch(m_bot->getClass()) + { + case CLASS_PRIEST: + m_classAI = (PlayerbotClassAI *)new PlayerbotPriestAI(master, m_bot, this); + break; + case CLASS_WARRIOR: + m_classAI = (PlayerbotClassAI *)new PlayerbotWarriorAI(master, m_bot, this); + break; + case CLASS_SHAMAN: + m_classAI = (PlayerbotClassAI *)new PlayerbotShamanAI(master, m_bot, this); + break; + case CLASS_ROGUE: + m_classAI = (PlayerbotClassAI *)new PlayerbotRogueAI(master, m_bot, this); + break; + case CLASS_PALADIN: + m_classAI = (PlayerbotClassAI *)new PlayerbotPaladinAI(master, m_bot, this); + break; + case CLASS_MAGE: + m_classAI = (PlayerbotClassAI *)new PlayerbotMageAI(master, m_bot, this); + break; + case CLASS_DRUID: + m_classAI = (PlayerbotClassAI *)new PlayerbotDruidAI(master, m_bot, this); + break; + case CLASS_WARLOCK: + m_classAI = (PlayerbotClassAI *)new PlayerbotWarlockAI(master, m_bot, this); + break; + case CLASS_HUNTER: + m_classAI = (PlayerbotClassAI *)new PlayerbotHunterAI(master, m_bot, this); + break; + case CLASS_DEATH_KNIGHT: + m_classAI = (PlayerbotClassAI *)new PlayerbotDeathKnightAI(master, m_bot, this); + break; + } + + //load config variables + m_followDistanceMin = sConfig->GetFloatDefault("Bot.FollowDistanceMin", 0.5f); + m_followDistanceMax = sConfig->GetFloatDefault("Bot.FollowDistanceMax", 3.0f); + m_playerBotsFly = sConfig->GetIntDefault("Bot.PlayerBotsFly", 0); + + SetQuestNeedItems(); + m_needEmblemList.clear(); + m_needEmblemList[29434] = 200; // Badge of Justice + m_needEmblemList[40752] = 200; // Emblem of Heroism + m_needEmblemList[40753] = 200; // Emblem of Valor + m_needEmblemList[45624] = 200; // Emblem of Conquest + m_needEmblemList[47241] = 200; // Emblem of Triumph + m_needEmblemList[49426] = 200; // Emblem of Frost + m_needEmblemList[44990] = 200; // Champion's Seal + HandleCommand("help", *m_master); +} +PlayerbotAI::~PlayerbotAI(){} + +//finds spell ID for matching substring args +//in priority of full text match, spells not taking reagents, and highest rank +uint32 PlayerbotAI::getSpellId(const char *args, bool master) const +{ + if(!*args) return 0; + + std::string namepart = args; + std::wstring wnamepart; + + if(!Utf8toWStr(namepart, wnamepart)) return 0; + + //converting string that we try to find to lower case + wstrToLower(wnamepart); + + int loc = 0; + if(master) loc = m_master->GetSession()->GetSessionDbcLocale(); + else loc = m_bot->GetSession()->GetSessionDbcLocale(); + + uint32 foundSpellId = 0; + bool foundExactMatch = false; + bool foundMatchUsesNoReagents = false; + + for(PlayerSpellMap::iterator itr = m_bot->GetSpellMap().begin(); itr != m_bot->GetSpellMap().end(); ++itr) + { + uint32 spellId = itr->first; + + if(itr->second->state == PLAYERSPELL_REMOVED || itr->second->disabled || IsPassiveSpell(spellId)) continue; + + const SpellEntry *pSpellInfo = sSpellStore.LookupEntry(spellId); + if(!pSpellInfo) continue; + + const std::string name = pSpellInfo->SpellName[loc]; + if(name.empty() || !Utf8FitTo(name, wnamepart)) continue; + + bool isExactMatch = (name.length() == wnamepart.length()) ? true : false; + bool usesNoReagents = (pSpellInfo->Reagent[0] <= 0) ? true : false; + + //if we already found a spell + bool useThisSpell = true; + if(foundSpellId > 0) + { + if(isExactMatch && !foundExactMatch){} + else if(usesNoReagents && !foundMatchUsesNoReagents){} + else if(spellId > foundSpellId){} + else useThisSpell = false; + } + if(useThisSpell) + { + foundSpellId = spellId; + foundExactMatch = isExactMatch; + foundMatchUsesNoReagents = usesNoReagents; + } + } + return foundSpellId; +} + +uint32 PlayerbotAI::getSpellIdExact(const char *args, bool includePassive, bool master) +{ + if(!*args) return 0; + std::string namepart = args; + int loc = 0; + if(master) loc = m_master->GetSession()->GetSessionDbcLocale(); + else loc = m_bot->GetSession()->GetSessionDbcLocale(); + uint32 foundSpellId = (uint32) 0; + bool foundMatchUsesNoReagents = false; + + for(PlayerSpellMap::iterator itr = m_bot->GetSpellMap().begin(); itr != m_bot->GetSpellMap().end(); ++itr) + { + uint32 spellId = itr->first; + if(itr->second->state == PLAYERSPELL_REMOVED || itr->second->disabled || ( !includePassive && IsPassiveSpell(spellId))) continue; + const SpellEntry *pSpellInfo = sSpellStore.LookupEntry(spellId); + if(!pSpellInfo) continue; + if(pSpellInfo->Effect[0] == SPELL_EFFECT_LEARN_SPELL) continue; //This is a learn spell + + const std::string name = pSpellInfo->SpellName[loc]; + if(name.empty()) continue; + if(strcmp(name.c_str(),namepart.c_str())) continue; + if(pSpellInfo->Reagent[0] <= 0 && !foundMatchUsesNoReagents){ foundSpellId = spellId; foundMatchUsesNoReagents = true; } + else if(spellId > foundSpellId) { foundSpellId = spellId; } + } + //sLog->outDebug("PBot Class %u - Found in Search - [%u/%s]", m_bot->getClass(), foundSpellId, namepart.c_str()); + if (foundSpellId > 70000) { sLog->outDebug("CRITICAL: PBot Class %u - Weird Spell in Search - [%u/%s]", m_bot->getClass(), foundSpellId, namepart.c_str()); } + return foundSpellId; +} + +// finds quest ID for matching substring args +uint32 PlayerbotAI::getQuestId(const char* args, bool remove) const +{ + if (!*args) + return 0; + + std::string namepart = args; + std::wstring wnamepart; + + if (!Utf8toWStr(namepart,wnamepart)) + return 0; + + // converting string that we try to find to lower case + wstrToLower(wnamepart); + uint32 questId = 0; + uint32 foundQuestId = 0; + bool foundExactMatch = false; + if (!m_questsSeen.empty() && !remove) + { + for (BotQuestsSeen::const_iterator iter = m_questsSeen.begin(); iter != m_questsSeen.end(); ++iter) + { + uint32 questId = iter->first; + const std::string name = iter->second; + if (name.empty() || !Utf8FitTo(name, wnamepart)) + continue; + bool isExactMatch = (name.length() == wnamepart.length()) ? true : false; + // if we already found a quest + bool useThisQuest = true; + if (foundQuestId > 0) + { + if (isExactMatch && ! foundExactMatch) + { + } + else if (questId > foundQuestId) + { + } + else + useThisQuest = false; + } + if (useThisQuest) + { + foundQuestId = questId; + foundExactMatch = isExactMatch; + } + } + } + else if (remove) + { + for (QuestStatusMap::iterator iter=m_bot->getQuestStatusMap().begin(); iter!=m_bot->getQuestStatusMap().end(); ++iter) + { + const Quest *qInfo = sObjectMgr->GetQuestTemplate(iter->first); + if (!qInfo) continue; + + uint32 questId = qInfo->GetQuestId(); + const std::string name = qInfo->GetTitle(); + if (name.empty() || !Utf8FitTo(name, wnamepart)) + continue; + + bool isExactMatch = (name.length() == wnamepart.length()) ? true : false; + // if we already found a quest + bool useThisQuest = true; + if (foundQuestId > 0) + { + if (isExactMatch && ! foundExactMatch) + { + } + else if (questId > foundQuestId) + { + } + else + useThisQuest = false; + } + if (useThisQuest) + { + foundQuestId = questId; + foundExactMatch = isExactMatch; + } + } + } + return foundQuestId; +} + + +/* +* Send a list of equipment that is in bot's inventor that is currently unequipped. +* This is called when the master is inspecting the bot. +*/ +void PlayerbotAI::SendNotEquipList(Player &player) +{ + //find all unequipped items and put them in + //a vector of dynamically created lists where the vector index is from 0-18 + //and the list contains Item *that can be equipped to that slot + //Note: each dynamically created list in the vector must be deleted at end + //so NO EARLY RETURNS! + //see enum EquipmentSlots in Player.h to see what equipment slot each index in vector + //is assigned to. (The first is EQUIPMENT_SLOT_HEAD = 0, and last is EQUIPMENT_SLOT_TABARD = 18) + + std::list<Item *> *equip[19]; + for(uint8 i = 0; i < 19; ++i) equip[i] = NULL; + + //list out items in main backpack + for(uint8 slot = INVENTORY_SLOT_ITEM_START; slot < INVENTORY_SLOT_ITEM_END; slot++) + { + Item *const pItem = m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot); + if(!pItem) + continue; + + uint16 dest; + uint8 msg = m_bot->CanEquipItem(NULL_SLOT, dest, pItem, !pItem->IsBag()); + if(msg != EQUIP_ERR_OK) continue; + + //the dest looks like it includes the old loc in the 8 higher bits + //so casting it to a uint8 strips them + uint8 equipSlot = uint8(dest); + if(!(equipSlot >= 0 && equipSlot < 19)) continue; + + //create a list if one doesn't already exist + if(equip[equipSlot] == NULL) + equip[equipSlot] = new std::list<Item *>; + + std::list<Item *> *itemListForEqSlot = equip[equipSlot]; + + itemListForEqSlot->push_back(pItem); + } + + //list out items in other removable backpacks + for(uint8 bag = INVENTORY_SLOT_BAG_START; bag < INVENTORY_SLOT_BAG_END; ++bag) + { + const Bag *const pBag = (Bag *)m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, bag); + if(pBag) + { + for(uint8 slot = 0; slot < pBag->GetBagSize(); ++slot) + { + Item *const pItem = m_bot->GetItemByPos(bag, slot); + if(!pItem) + continue; + + uint16 equipSlot; + uint8 msg = m_bot->CanEquipItem(NULL_SLOT, equipSlot, pItem, !pItem->IsBag()); + if(msg != EQUIP_ERR_OK) + continue; + if(!(equipSlot >= 0 && equipSlot < 19)) + continue; + + //create a list if one doesn't already exist + if(equip[equipSlot] == NULL) + equip[equipSlot] = new std::list<Item *>; + + std::list<Item *> *itemListForEqSlot = equip[equipSlot]; + itemListForEqSlot->push_back(pItem); + } + } + } + + TellMaster("Here's all the items in my inventory that I can equip:"); + ChatHandler ch(m_master); + + const std::string descr[] = { "head", "neck", "shoulders", "body", "chest", + "waist", "legs", "feet", "wrists", "hands", "finger1", "finger2", + "trinket1", "trinket2", "back", "mainhand", "offhand", "ranged", "tabard" }; + + //now send client all items that can be equipped by slot + for(uint8 equipSlot = 0; equipSlot < 19; ++equipSlot) + { + if(equip[equipSlot] == NULL) continue; + std::list<Item *> *itemListForEqSlot = equip[equipSlot]; + std::ostringstream out; + out << descr[equipSlot] << ": "; + for(std::list<Item *>::iterator it = itemListForEqSlot->begin(); it != itemListForEqSlot->end(); ++it) + { + const ItemPrototype *const pItemProto = (*it)->GetProto(); + out << " |cffffffff|Hitem:" << pItemProto->ItemId + << ":0:0:0:0:0:0:0" << "|h[" << pItemProto->Name1 + << "]|h|r"; + } + ch.SendSysMessage(out.str().c_str()); + delete itemListForEqSlot; //delete list of Item * + } +} + +void PlayerbotAI::HandleMasterOutgoingPacket(const WorldPacket &packet, WorldSession &masterSession) +{ + /* + const char *oc = LookupOpcodeName(packet.GetOpcode()); + + std::ostringstream out; + out << "HandleMasterOutgoingPacket: " << oc; + sLog->outError(out.str().c_str()); + */ +} + +void PlayerbotAI::HandleMasterIncomingPacket(const WorldPacket &packet, WorldSession &masterSession) +{ + switch(packet.GetOpcode()) + { + case CMSG_SET_SELECTION: + { + //sLog->outError("cmsg_set_selection"); + return; + } + + //If master inspects one of his bots, give the master useful info in chat window + //such as inventory that can be equipped + case CMSG_INSPECT: + { + WorldPacket p(packet); + p.rpos(0); //reset reader + uint64 guid; p >> guid; + Player *const bot = masterSession.GetPlayerBot(guid); + if(!bot) return; + bot->GetPlayerbotAI()->SendNotEquipList(*bot); + } + + case CMSG_PUSHQUESTTOPARTY: + { + WorldPacket p(packet); + p.rpos(0); //reset reader + uint32 quest; p >> quest; + Player *pPlayer = masterSession.GetPlayer(); + Quest const *pQuest = sObjectMgr->GetQuestTemplate(quest); + + if(pQuest) + for(PlayerBotMap::const_iterator it = masterSession.GetPlayerBotsBegin(); it != masterSession.GetPlayerBotsEnd(); ++it) + { + Player *const bot = it->second; + uint64 guid = it->first; + uint32 unk1 = 0; + + WorldPacket data(MSG_QUEST_PUSH_RESULT, (8+4+4)); + //data << guid; + data << pPlayer->GetGUID(); + data << quest; + data << unk1; + + bot->GetSession()->HandleQuestgiverAcceptQuestOpcode(data); + bot->GetPlayerbotAI()->SetQuestNeedItems(); + } + return; + } + + //handle emotes from the master + //case CMSG_EMOTE: + case CMSG_TEXT_EMOTE: + { + WorldPacket p(packet); + p.rpos(0); //reset reader + uint32 emoteNum; +// uint64 guid; + p >> emoteNum; + + switch(emoteNum) + { + case TEXTEMOTE_BONK: + { + Player *const pPlayer = masterSession.GetPlayerBot(masterSession.GetPlayer()->GetSelection()); + if(!pPlayer || !pPlayer->GetPlayerbotAI()) return; + PlayerbotAI *const pBot = pPlayer->GetPlayerbotAI(); + + ChatHandler ch(masterSession.GetPlayer()); + + { + std::ostringstream out; + out << "clock(): " << (getMSTime()) + << " 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 << "m_IsFollowingMaster: " << pBot->m_IsFollowingMaster; + ch.SendSysMessage(out.str().c_str()); + }{ + std::ostringstream out; + out << "IsBeingTeleported(): " << pBot->m_bot->IsBeingTeleported(); + ch.SendSysMessage(out.str().c_str()); + }{ + std::ostringstream out; + bool tradeActive = (pBot->m_bot->GetTrader()) ? true : false; + out << "tradeActive: " << tradeActive; + ch.SendSysMessage(out.str().c_str()); + }{ + std::ostringstream out; + out << "IsCharmed(): " << pBot->m_bot->isCharmed(); + ch.SendSysMessage(out.str().c_str()); + }{ + std::ostringstream out; + out << "IsInCombat(): " << pBot->m_bot->isInCombat(); + ch.SendSysMessage(out.str().c_str()); + }{ + std::ostringstream out; + out << "isLooting: " << pBot->isLooting; + ch.SendSysMessage(out.str().c_str()); + } + { + std::ostringstream out; + out << "isPulling: " << pBot->GetClassAI()->isPulling(); + ch.SendSysMessage(out.str().c_str()); + } + return; + } + + case TEXTEMOTE_EAT: + case TEXTEMOTE_DRINK: + { + Player *const bot = masterSession.GetPlayerBot(masterSession.GetPlayer()->GetSelection()); + if(bot) bot->GetPlayerbotAI()->Stay(); + else { + for(PlayerBotMap::const_iterator it = masterSession.GetPlayerBotsBegin(); it != masterSession.GetPlayerBotsEnd(); ++it) + { + Player *const bot = it->second; + bot->GetPlayerbotAI()->Feast(); + } + } + return; + } + + //emote to stay + case TEXTEMOTE_STAND: + { + Player *const bot = masterSession.GetPlayerBot(masterSession.GetPlayer()->GetSelection()); + if(bot) bot->GetPlayerbotAI()->Stay(); + else { + for(PlayerBotMap::const_iterator it = masterSession.GetPlayerBotsBegin(); it != masterSession.GetPlayerBotsEnd(); ++it) + { + Player *const bot = it->second; + bot->GetPlayerbotAI()->Stay(); + } + } + return; + } + + //324 is the followme emote (not defined in enum) + //if master has bot selected then only bot follows, else all bots follow + case 324: + case TEXTEMOTE_WAVE: + { + Player *const bot = masterSession.GetPlayerBot(masterSession.GetPlayer()->GetSelection()); + if(bot) + { + bot->GetPlayerbotAI()->Follow(*masterSession.GetPlayer()); + bot->GetPlayerbotAI()->SetLooting(false); + } else { + for(PlayerBotMap::const_iterator it = masterSession.GetPlayerBotsBegin(); it != masterSession.GetPlayerBotsEnd(); ++it) + { + Player *const bot = it->second; + bot->GetPlayerbotAI()->Follow(*masterSession.GetPlayer()); + bot->GetPlayerbotAI()->SetLooting(false); + } + } + return; + } + + default: return; + } + } //end CMSG_TEXT_EMOTE + + case CMSG_GROUP_UNINVITE: + { + WorldPacket p(packet); + p.rpos(0); //reset reader + std::string member; p >> member; + p.clear(); + + WorldPacket data(CMSG_GROUP_UNINVITE, 1); + PlayerbotChatHandler ch(masterSession.GetPlayer()); + std::ostringstream out; + out << "remove " << member; + ch.uninvite(out.str().c_str()); + return; + } + + case CMSG_REPAIR_ITEM: + { + WorldPacket p(packet); + p.rpos(0); //reset reader + uint64 npcGUID; + p >> npcGUID; + + Object *const pNpc = ObjectAccessor::GetObjectByTypeMask(*masterSession.GetPlayer(), npcGUID, TYPEMASK_UNIT | TYPEMASK_GAMEOBJECT); + if(!pNpc) + return; + + //for all master's bots + for(PlayerBotMap::const_iterator it = masterSession.GetPlayerBotsBegin(); it != masterSession.GetPlayerBotsEnd(); ++it) + { + Player *const bot = it->second; + if(!bot->IsInMap((WorldObject*) pNpc)) + { + bot->GetPlayerbotAI()->TellMaster("I'm too far away to repair items!"); + continue; + } else { + bot->GetPlayerbotAI()->TellMaster("Repairing my items."); + bot->DurabilityRepairAll(false, 0.0, false); + } + + } + return; + } + + case CMSG_ACTIVATETAXIEXPRESS: + { + WorldPacket incP(packet); + //for all master's bots + for(PlayerBotMap::const_iterator it = masterSession.GetPlayerBotsBegin(); it != masterSession.GetPlayerBotsEnd(); ++it) + { + + Player *const bot = it->second; + if (!bot->GetPlayerbotAI()->CanBotsFly()) return; + + if(!bot->IsInMap((WorldObject*) masterSession.GetPlayer())) + { + bot->GetPlayerbotAI()->TellMaster("I'm too far away to fly!"); + continue; + } else { + WorldPacket p; + // p << guid << _totalcost << node_count; + bot->GetPlayerbotAI()->Stay(); // clear any movement + incP.rpos(0); + bot->GetSession()->HandleActivateTaxiExpressOpcode(incP); + } + + } + return; + } + + case CMSG_ACTIVATETAXI: + { + WorldPacket incP(packet); + //for all master's bots + for(PlayerBotMap::const_iterator it = masterSession.GetPlayerBotsBegin(); it != masterSession.GetPlayerBotsEnd(); ++it) + { + Player *const bot = it->second; + if (!bot->GetPlayerbotAI()->CanBotsFly()) return; + + if(!bot->IsInMap((WorldObject*) masterSession.GetPlayer())) + { + bot->GetPlayerbotAI()->TellMaster("I'm too far away to fly!"); + continue; + } else { + WorldPacket p; + // p << guid << nodes[0] << nodes[1]; + bot->GetPlayerbotAI()->Stay(); // clear any movement + incP.rpos(0); + bot->GetSession()->HandleActivateTaxiOpcode(incP); + } + + } + return; + } + + // when landing from a flight path + case CMSG_MOVE_SPLINE_DONE: + { + WorldPacket p(packet); + //p.rpos(0); // reset reader + + //for all master's bots + for(PlayerBotMap::const_iterator it = masterSession.GetPlayerBotsBegin(); it != masterSession.GetPlayerBotsEnd(); ++it) + { + Player *const bot = it->second; + + if(!bot->IsInMap((WorldObject*) masterSession.GetPlayer())) + { + bot->GetPlayerbotAI()->TellMaster("I'm too far away to land!"); + continue; + } else { + p.rpos(0); // reset reader + p.appendPackGUID(bot->GetGUID()); + bot->GetSession()->HandleMoveSplineDoneOpcode(p); + uint32 sourcenode = bot->m_taxi.GetTaxiSource(); + uint32 mountDisplayId = sObjectMgr->GetTaxiMountDisplayId(sourcenode, bot->GetTeam()); + + if (mountDisplayId==0) { + bot->CleanupAfterTaxiFlight(); + bot->GetPlayerbotAI()->Follow(*masterSession.GetPlayer()); + } + } + + } + return; + } + case CMSG_LOOT: + { + WorldPacket p(packet); + p.rpos(0); // reset reader + uint64 cGUID; + p >> cGUID; + + Player *m_master = masterSession.GetPlayer(); + Creature *cToLoot = m_master->GetMap()->GetCreature(cGUID); + if (!cToLoot) + return; + /* for all master's bots */ + for (PlayerBotMap::const_iterator it = masterSession.GetPlayerBotsBegin(); + it != masterSession.GetPlayerBotsEnd(); ++it) + { + Player* const bot = it->second; + if (!bot->IsInMap((WorldObject*) cToLoot)) + { + bot->GetPlayerbotAI()->TellMaster("I'm too far away to check for needed Quest Items!"); + continue; + } + else + { + //bot->GetPlayerbotAI()->TellMaster("Checking for needed Quest Items."); + bot->GetPlayerbotAI()->AddLootGUID(cGUID); + bot->GetPlayerbotAI()->DoLoot(); + } + } + return; + + } + break; + + case CMSG_GAMEOBJ_USE: + { + WorldPacket p(packet); + p.rpos(0); // reset reader + uint64 objGUID; + p >> objGUID; + + Player *m_master = masterSession.GetPlayer(); + GameObject *obj = m_master->GetMap()->GetGameObject( objGUID ); + if( !obj ) + return; + + for(PlayerBotMap::const_iterator it = masterSession.GetPlayerBotsBegin(); it != masterSession.GetPlayerBotsEnd(); ++it) + { + Player* const bot = it->second; +//sLog->outError ("gameobject type = %u", obj->GetGoType()); + if( obj->GetGoType() == GAMEOBJECT_TYPE_QUESTGIVER ) + { + bot->GetPlayerbotAI()->TurnInQuests( obj ); + } + // add other go types here, i.e.: + // GAMEOBJECT_TYPE_CHEST - loot quest items of chest + } + } + break; + + case CMSG_GAMEOBJ_REPORT_USE: + { + WorldPacket p(packet); + p.rpos(0); // reset reader + uint64 objGUID; + p >> objGUID; + + Player *m_master = masterSession.GetPlayer(); + GameObject *obj = m_master->GetMap()->GetGameObject( objGUID ); + if( !obj ) + return; + + //Object* const pNpc = ObjectAccessor::GetObjectByTypeMask(*masterSession.GetPlayer(), npcGUID, TYPEMASK_UNIT|TYPEMASK_GAMEOBJECT); + //if (!pNpc) return; + /* for all master's bots */ + for (PlayerBotMap::const_iterator it = masterSession.GetPlayerBotsBegin(); + it != masterSession.GetPlayerBotsEnd(); ++it) + { + Player* const bot = it->second; + if (!bot->IsInMap((WorldObject*) obj)) + { + bot->GetPlayerbotAI()->TellMaster("I'm too far away to check for needed Quest Items!"); + continue; + } + else + { + //bot->GetPlayerbotAI()->TellMaster("Checking for needed Quest Items."); + bot->GetPlayerbotAI()->AddLootGUID(objGUID); + bot->GetPlayerbotAI()->DoLoot(); + } + } + return; + } + + //if master talks to an NPC + case CMSG_GOSSIP_HELLO: + case CMSG_QUESTGIVER_HELLO: + { + WorldPacket p(packet); + p.rpos(0); //reset reader + uint64 npcGUID; + p >> npcGUID; + + Player *m_master = masterSession.GetPlayer(); + //Object *const pNpc = ObjectAccessor::GetObjectByTypeMask(*masterSession.GetPlayer(), npcGUID, TYPEMASK_UNIT | TYPEMASK_GAMEOBJECT); + WorldObject* pNpc = ObjectAccessor::GetWorldObject( *m_master, npcGUID ); + if(!pNpc) + return; + + // if its a flight master + if(pNpc->HasFlag( UNIT_NPC_FLAGS, UNIT_NPC_FLAG_FLIGHTMASTER )) + { + for(PlayerBotMap::const_iterator it = masterSession.GetPlayerBotsBegin(); it != masterSession.GetPlayerBotsEnd(); ++it) + { + Player *const bot = it->second; + if (bot->GetSession()->SendLearnNewTaxiNode((Creature*)pNpc)) + bot->GetPlayerbotAI()->TellMaster("Learned a new path."); + } + return; + } + + // for all master's bots + for(PlayerBotMap::const_iterator it = masterSession.GetPlayerBotsBegin(); it != masterSession.GetPlayerBotsEnd(); ++it) + { + Player* const bot = it->second; + bot->GetPlayerbotAI()->TurnInQuests( pNpc ); + bot->GetPlayerbotAI()->SetQuestNeedItems(); + + bot->TalkedToCreature(pNpc->GetEntry(), pNpc->GetGUID()); + } + + return; + } + + case CMSG_QUESTGIVER_STATUS_MULTIPLE_QUERY: + { + WorldPacket p(packet); + p.rpos(0); // reset reader + if (!masterSession.GetPlayer()->GetSelection()) return; + + //for all master's bots + for(PlayerBotMap::const_iterator it = masterSession.GetPlayerBotsBegin(); it != masterSession.GetPlayerBotsEnd(); ++it) + { + Player* const bot = it->second; + p.rpos(0); // reset reader + bot->GetSession()->HandleQuestgiverStatusMultipleQuery(p); + } + + + return; + } + + // if master accepts a quest, bots should also try to accept quest + case CMSG_QUESTGIVER_ACCEPT_QUEST: + { + WorldPacket p(packet); + p.rpos(0); // reset reader + uint64 guid; + uint32 quest; + p >> guid >> quest; + Quest const* qInfo = sObjectMgr->GetQuestTemplate(quest); + if (qInfo) + { + //for all master's bots + for(PlayerBotMap::const_iterator it = masterSession.GetPlayerBotsBegin(); it != masterSession.GetPlayerBotsEnd(); ++it) + { + Player* const bot = it->second; + + if (bot->GetQuestStatus(quest) == QUEST_STATUS_COMPLETE) + bot->GetPlayerbotAI()->TellMaster("I already completed that quest."); + else if (! bot->CanTakeQuest(qInfo, false)) + { + if (! bot->SatisfyQuestStatus(qInfo, false)) + bot->GetPlayerbotAI()->TellMaster("I already have that quest."); + else + bot->GetPlayerbotAI()->TellMaster("I can't take that quest."); + } + else if (! bot->SatisfyQuestLog(false)) + bot->GetPlayerbotAI()->TellMaster("My quest log is full."); + else if (! bot->CanAddQuest(qInfo, false)) + bot->GetPlayerbotAI()->TellMaster("I can't take that quest because it requires that I take items, but my bags are full!"); + + else + { + p.rpos(0); // reset reader + bot->GetSession()->HandleQuestgiverAcceptQuestOpcode(p); + bot->GetPlayerbotAI()->TellMaster("Got the quest."); + bot->GetPlayerbotAI()->SetQuestNeedItems(); + } + } + } + + return; + } + + case CMSG_LIST_INVENTORY: + { + uint64 npcGUID; + WorldPacket p1(packet); + p1.rpos(0); /* reset reader */ + p1 >> npcGUID; + + Object* const pNpc = ObjectAccessor::GetObjectByTypeMask(*masterSession.GetPlayer(), npcGUID, TYPEMASK_UNIT|TYPEMASK_GAMEOBJECT); + if (!pNpc) return; + + /* for all master's bots */ + for (PlayerBotMap::const_iterator it = masterSession.GetPlayerBotsBegin(); + it != masterSession.GetPlayerBotsEnd(); ++it) + { + Player* const bot = it->second; + if (!bot->IsInMap((WorldObject*) pNpc)) + { + bot->GetPlayerbotAI()->TellMaster("I'm too far away to sell items!"); + continue; + } + else + { + uint32 TotalCost = 0; + uint32 TotalSold = 0; + std::ostringstream report; + std::ostringstream canSell; + canSell << "Items that are not trash and can be sold: "; + // list out items in main backpack + for (uint8 slot = INVENTORY_SLOT_ITEM_START; slot < INVENTORY_SLOT_ITEM_END; slot++) + { + Item* const item = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot); + if (!item) + continue; + + if (item->CanBeTraded() && item->GetProto()->Quality == ITEM_QUALITY_POOR) + { + int32 cost = item->GetCount() * item->GetProto()->SellPrice; + bot->ModifyMoney(cost); + bot->MoveItemFromInventory(item->GetBagSlot(), item->GetSlot(), true); + bot->AddItemToBuyBackSlot(item); + + TotalSold = TotalSold + 1; + TotalCost = TotalCost + cost; + + report << "Sold " << item->GetCount() << "x"; + report << " |cffffffff|Hitem:" << item->GetProto()->ItemId << ":0:0:0:0:0:0:0" << "|h[" << item->GetProto()->Name1 << "]|h|r"; + report << " for "; + + uint32 gold = uint32(cost / 10000); + cost -= (gold * 10000); + uint32 silver = uint32(cost / 100); + cost -= (silver * 100); + + if (gold > 0) + { + report << gold << "|TInterface\\Icons\\INV_Misc_Coin_01:16|t"; + } + if (silver > 0) + { + report << silver << "|TInterface\\Icons\\INV_Misc_Coin_03:16|t"; + } + report << cost << "|TInterface\\Icons\\INV_Misc_Coin_05:16|t\n"; + } + else if (item->GetProto()->SellPrice > 0) + { + canSell << "|cffffffff|Hitem:" << item->GetProto()->ItemId << ":0:0:0:0:0:0:0" << "|h[" << item->GetProto()->Name1 << "]|h|r "; + } + } + + for (uint8 bag = INVENTORY_SLOT_BAG_START; bag < INVENTORY_SLOT_BAG_END;++bag) + { + const Bag* const pBag = (Bag*) bot->GetItemByPos(INVENTORY_SLOT_BAG_0, bag); + if (pBag) + { + for (uint8 slot = 0; slot < pBag->GetBagSize(); ++slot) + { + Item* const item = bot->GetItemByPos(bag, slot); + if (!item) + continue; + + if (item->CanBeTraded() && item->GetProto()->Quality == ITEM_QUALITY_POOR) + { + int32 cost = item->GetCount() * item->GetProto()->SellPrice; + bot->ModifyMoney(cost); + bot->MoveItemFromInventory(item->GetBagSlot(), item->GetSlot(), true); + bot->AddItemToBuyBackSlot(item); + + TotalSold = TotalSold + 1; + TotalCost = TotalCost + cost; + + report << "Sold " << item->GetCount() << "x"; + report << " |cffffffff|Hitem:" << item->GetProto()->ItemId << ":0:0:0:0:0:0:0" << "|h[" << item->GetProto()->Name1 << "]|h|r"; + report << " for "; + + uint32 gold = uint32(cost / 10000); + cost -= (gold * 10000); + uint32 silver = uint32(cost / 100); + cost -= (silver * 100); + if (gold > 0) + { + report << gold << "|TInterface\\Icons\\INV_Misc_Coin_01:16|t"; + } + if (silver > 0) + { + report << silver << "|TInterface\\Icons\\INV_Misc_Coin_03:16|t"; + } + report << cost << "|TInterface\\Icons\\INV_Misc_Coin_05:16|t\n"; + } + else if (item->GetProto()->SellPrice > 0) + { + canSell << "|cffffffff|Hitem:" << item->GetProto()->ItemId << ":0:0:0:0:0:0:0" << "|h[" << item->GetProto()->Name1 << "]|h|r "; + } + } + } + } + if (TotalSold > 0) { + report << "Sold total " << TotalSold << " item(s) for "; + uint32 gold = uint32(TotalCost / 10000); + TotalCost -= (gold * 10000); + uint32 silver = uint32(TotalCost / 100); + TotalCost -= (silver * 100); + if (gold > 0) + { + report << gold << "|TInterface\\Icons\\INV_Misc_Coin_01:16|t"; + } + if (silver > 0) + { + report << silver << "|TInterface\\Icons\\INV_Misc_Coin_03:16|t"; + } + report << TotalCost << "|TInterface\\Icons\\INV_Misc_Coin_05:16|t"; + bot->GetPlayerbotAI()->TellMaster(report.str()); + } + bot->GetPlayerbotAI()->TellMaster(canSell.str()); + } + } + return; + + } + + case CMSG_AREATRIGGER: + { + uint32 Trigger_ID; + WorldPacket p1(packet); + p1.rpos(0); /* reset reader */ + p1 >> Trigger_ID; + + /* for all master's bots */ + for (PlayerBotMap::const_iterator it = masterSession.GetPlayerBotsBegin(); + it != masterSession.GetPlayerBotsEnd(); ++it) + { + Player* const bot = it->second; + + uint32 quest_id = sObjectMgr->GetQuestForAreaTrigger( Trigger_ID ); + // The conditions that intentionally left unchecked are: + // Bot is alive or not + // Bot is in the trigger area or not + if( quest_id && bot->IsActiveQuest(quest_id) ) + { + Quest const* pQuest = sObjectMgr->GetQuestTemplate(quest_id); + if( pQuest ) + { + if(bot->GetQuestStatus(quest_id) == QUEST_STATUS_INCOMPLETE) + { + bot->AreaExploredOrEventHappens( quest_id ); + bot->GetPlayerbotAI()->TellMaster("Quest area explored"); + } + } + } + + if(sObjectMgr->IsTavernAreaTrigger(Trigger_ID)) + { + // set resting flag we are in the inn + bot->SetFlag(PLAYER_FLAGS, PLAYER_FLAGS_RESTING); + bot->InnEnter(time(NULL), bot->GetMapId() , bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ()); + bot->SetRestType(REST_TYPE_IN_TAVERN); + + if(sWorld->IsFFAPvPRealm()) + bot->RemoveByteFlag(UNIT_FIELD_BYTES_2, 1, UNIT_BYTE2_FLAG_FFA_PVP); + } + } + return; + } + + + default: + { + /* const char *oc = LookupOpcodeName(packet.GetOpcode()); + ChatHandler ch(masterSession.GetPlayer()); + // ch.SendSysMessage(oc); + + std::ostringstream out; + out << "HandleMasterIncomingPacket: " << oc; + sLog->outError(out.str().c_str()); + */ + return; + } + + } +} + +//handle outgoing packets the server would send to the client +void PlayerbotAI::HandleBotOutgoingPacket(const WorldPacket &packet) +{ + switch(packet.GetOpcode()) + { + case SMSG_TRADE_STATUS_EXTENDED: + { + //m_bot->GetSession()->SendTradeStatus(TRADE_STATUS_TRADE_ACCEPT); + return; + } + + + case SMSG_DUEL_WINNER: + m_bot->HandleEmoteCommand(EMOTE_ONESHOT_APPLAUD); + return; + + case SMSG_DUEL_COMPLETE: + SetIgnoreUpdateTime(4); + m_combatOrder = ORDERS_NONE; + m_ScenarioType = SCENARIO_PVEEASY; + m_bot->GetMotionMaster()->Clear(true); + return; + + case SMSG_DUEL_OUTOFBOUNDS: + m_bot->HandleEmoteCommand(EMOTE_ONESHOT_CHICKEN); + return; + + case SMSG_DUEL_REQUESTED: + { + SetIgnoreUpdateTime(0); + WorldPacket p(packet); + uint64 flagGuid; p >> flagGuid; + uint64 playerGuid; p >> playerGuid; + Player *const pPlayer = ObjectAccessor::FindPlayer(playerGuid); + if(canObeyCommandFrom(*pPlayer)) + { + m_bot->GetMotionMaster()->Clear(true); + WorldPacket *const packet = new WorldPacket(CMSG_DUEL_ACCEPTED, 8); + *packet << flagGuid; + m_bot->GetSession()->QueuePacket(packet); //queue the packet to get around race condition + + //follow target in casting range + float angle = rand_norm()*M_PI; //Generates random float between 0 and 3.14 + float dist = (float)(urand((m_followDistanceMin*10), (m_followDistanceMax*10))/10); //Using urand to get a random float is stupid. It takes uint32, not float. + m_bot->GetMotionMaster()->Clear(true); + m_bot->GetMotionMaster()->MoveFollow(pPlayer, dist, angle); + + m_bot->SetSelection(playerGuid); + SetIgnoreUpdateTime(4); + m_combatOrder = ORDERS_KILL; + m_ScenarioType = SCENARIO_DUEL; + } + return; + } + + case SMSG_INVENTORY_CHANGE_FAILURE: + TellMaster("I can't use that."); + return; + + case SMSG_SPELL_DELAYED: + { + //sLog->outDebug("Bot [%u] SMSG_SPELL_DELAYED",m_bot->GetGUIDLow()); + WorldPacket p(packet); + //uint64 casterGuid = extractGuid(p); //somehow the caster guid is corrupt + //if(casterGuid != m_bot->GetGUID()) return; + //uint32 delayTime; p >> delayTime; + //sLog->outDebug("Bot [%u] caster [%u] Spell Delayed [%u]",m_bot->GetGUIDLow(), casterGuid, delayTime); + //m_ignoreAIUpdatesUntilTime += ((((float)delayTime) / 1000.0f ) + 0.1f) * CLOCKS_PER_SEC; + if(m_CurrentlyCastingSpellId > 0) + { + m_ignoreAIUpdatesUntilTime += 0.5f * 1000; //Until this is handled correctly, assume, delay is the default 0.5 secs + } + return; + } + + case SMSG_SPELL_FAILURE: + { + WorldPacket p(packet); + uint64 casterGuid = extractGuid(p); + if(casterGuid != m_bot->GetGUID()) return; + uint32 spellId; p >> spellId; + if(m_CurrentlyCastingSpellId == spellId) + { + SetIgnoreUpdateTime(1); + m_CurrentlyCastingSpellId = 0; + } + return; + } + + //if a change in speed was detected for the master + //make sure we have the same mount status + case SMSG_FORCE_RUN_SPEED_CHANGE: + { + WorldPacket p(packet); + uint64 guid = extractGuid(p); + //uint64 guid; p >> guid; + Player *tPlayer = sObjectMgr->GetPlayer(guid); + if(!tPlayer) return; + if (!m_master || !m_bot) return; + if(guid == m_bot->GetGUID()) return; + if(guid == m_master->GetGUID()) { + m_bot->GetPlayerbotAI()->UseMount(); + SetIgnoreUpdateTime(2); + } + return; + } + + //handle flying acknowledgement + case SMSG_MOVE_SET_CAN_FLY: + { + WorldPacket p(packet); + uint64 guid = extractGuid(p); + if(guid != m_bot->GetGUID()) return; + m_bot->AddUnitMovementFlag(MOVEMENTFLAG_FLYING); + //SetSpeed(MOVE_RUN, m_master->GetSpeed(MOVE_FLIGHT) + 0.1f, true); + return; + } + + //handle dismount flying acknowledgement + case SMSG_MOVE_UNSET_CAN_FLY: + { + WorldPacket p(packet); + uint64 guid = extractGuid(p); + if(guid != m_bot->GetGUID()) return; + m_bot->RemoveUnitMovementFlag(MOVEMENTFLAG_FLYING); + //SetSpeed(MOVE_RUN, m_master->GetSpeedRate(MOVE_RUN), true); + return; + } + + //If the leader role was given to the bot automatically give it to the master + //if the master is in the group, otherwise leave group + case SMSG_GROUP_SET_LEADER: + { + WorldPacket p(packet); + std::string name; p >> name; + if(m_bot->GetGroup() && name == m_bot->GetName()) + { + if(m_bot->GetGroup()->IsMember(m_master->GetGUID())) + { + p.resize(8); + p << m_master->GetGUID(); + m_bot->GetSession()->HandleGroupSetLeaderOpcode(p); + } else { + p.clear(); //not really needed + m_bot->GetSession()->HandleGroupDisbandOpcode(p); //packet not used + } + } + return; + } + + //If the master leaves the group, then the bot leaves too + case SMSG_PARTY_COMMAND_RESULT: + { + WorldPacket p(packet); + uint32 operation; p >> operation; + std::string member; p >> member; + uint32 result; p >> result; + p.clear(); + if(operation == PARTY_OP_LEAVE && member == m_master->GetName()) m_bot->GetSession()->HandleGroupDisbandOpcode(p); //packet not used + return; + } + + //Automatically accept rez. Useful when bot dies, and a druid does a battle rez. + case SMSG_RESURRECT_REQUEST: + { + WorldPacket p, incP(packet); + uint8 status = 1; + uint64 rezzer; incP >> rezzer; + p << rezzer; + p << status; + m_bot->GetPlayerbotAI()->SetLooting(false); + m_bot->GetSession()->HandleResurrectResponseOpcode(p); + m_IsFollowingMaster = true; + m_TimeRessurect = 0; + return; + } + + //Handle Group invites (auto accept if master is in group, otherwise decline & send message) + case SMSG_GROUP_INVITE: + { + if(m_bot->GetGroupInvite()) + { + const Group *const grp = m_bot->GetGroupInvite(); + if(!grp) return; + Player *const inviter = sObjectMgr->GetPlayer(grp->GetLeaderGUID()); + if(!inviter) return; + WorldPacket p; + if(!canObeyCommandFrom(*inviter)) + { + std::string buf = "I can't accept your invite unless you first invite my master "; + buf += m_master->GetName(); + buf += "."; + SendWhisper(buf, *inviter); + m_bot->GetSession()->HandleGroupDeclineOpcode(p); //packet not used + } else + m_bot->GetSession()->HandleGroupAcceptOpcode(p); //packet not used + } + return; + } + + //Handle when another player opens the trade window with the bot + //also sends list of tradable items bot can trade if bot is allowed to obey commands from + case SMSG_TRADE_STATUS: + { + if(m_bot->GetTrader() == NULL) break; + WorldPacket p(packet); + uint32 status; p >> status; + p.clear(); + + if(status == 4) { // TRADE_STATUS_TRADE_ACCEPT + if (!m_bot->GetTradeData()->IsAccepted() || !m_bot->GetTrader()->GetTradeData()->IsAccepted()) { + m_bot->GetSession()->HandleAcceptTradeOpcode(p); //packet not used + } + + } else if(status == 1) // TRADE_STATUS_BEGIN_TRADE + { + m_bot->GetSession()->HandleBeginTradeOpcode(p); //packet not used + + //if(!canObeyCommandFrom(*(m_bot->GetTrader()))) + //{ + //SendWhisper("I'm not allowed to trade you any of my items, but you are free to give me money or items.", *(m_bot->GetTrader())); + //return; + //} + + //list out items available for trade + std::ostringstream out; + + //list out items in main backpack + for(uint8 slot = INVENTORY_SLOT_ITEM_START; slot < INVENTORY_SLOT_ITEM_END; slot++) + { + const Item *const pItem = m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot); + if(pItem && pItem->CanBeTraded()) + { + const ItemPrototype *const pItemProto = pItem->GetProto(); + std::string name = pItemProto->Name1; + + out << " |cffffffff|Hitem:" << pItemProto->ItemId << ":0:0:0:0:0:0:0" << "|h[" << name << "]|h|r"; + if(pItem->GetCount() > 1) + out << "x" << pItem->GetCount() << ' '; + } + } + //list out items in other removable backpacks + for(uint8 bag = INVENTORY_SLOT_BAG_START; bag < INVENTORY_SLOT_BAG_END; ++bag) + { + const Bag *const pBag = (Bag *)m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, bag); + if(pBag) + { + for(uint8 slot = 0; slot < pBag->GetBagSize(); ++slot) + { + const Item *const pItem = m_bot->GetItemByPos(bag, slot); + if(pItem && pItem->CanBeTraded()) + { + const ItemPrototype *const pItemProto = pItem->GetProto(); + const std::string name = pItemProto->Name1; + + //item link format: http://www.wowwiki.com/ItemString + //itemId, enchantId, jewelId1, jewelId2, jewelId3, jewelId4, suffixId, uniqueId + out << " |cffffffff|Hitem:" << pItemProto->ItemId << ":0:0:0:0:0:0:0" << "|h[" << name << "]|h|r"; + if(pItem->GetCount() > 1) + out << "x" << pItem->GetCount() << ' '; + } + } + } + } + + //calculate how much money bot has + uint32 copper = m_bot->GetMoney(); + uint32 gold = uint32(copper / 10000); + copper -= (gold * 10000); + uint32 silver = uint32(copper / 100); + copper -= (silver * 100); + + //send bot the message + std::ostringstream whisper; + whisper << "I have |cff00ff00" << gold + << "|r|cfffffc00g|r|cff00ff00" << silver + << "|r|cffcdcdcds|r|cff00ff00" << copper + << "|r|cffffd333c|r" << " and the following items:"; + m_bot->GetPlayerbotAI()->SendWhisper(whisper.str(), *(m_bot->GetTrader())); + ChatHandler ch(m_bot->GetTrader()); + ch.SendSysMessage(out.str().c_str()); + } + return; + } + + case SMSG_SPELL_GO: + { + WorldPacket p(packet); + uint64 castItemGuid = extractGuid(p); + uint64 casterGuid = extractGuid(p); + if(casterGuid != m_bot->GetGUID()) return; + + uint32 spellId; p >> spellId; + uint16 castFlags; p >> castFlags; + uint32 msTime; p >> msTime; + uint8 numHit; p >> numHit; + if(m_CurrentlyCastingSpellId == spellId) + { + Spell *const pSpell = m_bot->FindCurrentSpellBySpellId(spellId); + if(!pSpell) return; + if(pSpell->IsChannelActive() || pSpell->IsAutoRepeat()) + SetIgnoreUpdateTime( (((float)GetSpellDuration(pSpell->m_spellInfo) / 1000.0f) + 1.0f) ); + else if(pSpell->IsAutoRepeat()) + SetIgnoreUpdateTime(6); + else { + SetIgnoreUpdateTime(0.5f); + m_CurrentlyCastingSpellId = 0; + } + } + return; + } + + case SMSG_TEXT_EMOTE: + { + WorldPacket p(packet); + p.rpos(0); //reset reader + uint32 text_emote; + uint64 guid; + p >> guid; + p >> text_emote; + + switch(text_emote) + { + case TEXTEMOTE_BOW: + { + //Buff anyone who bows before me. Useful for players not in bot's group + Player *pPlayer = sObjectMgr->GetPlayer(guid); + + Player *const bot =sObjectMgr->GetPlayer(pPlayer->GetSelection()); + + if(bot && bot->GetGUID()==m_bot->GetGUID() && + bot->GetPlayerbotAI()->GetClassAI()) + { + bot->GetPlayerbotAI()->GetClassAI()->BuffPlayer(pPlayer); + } + return; + } + + default: + m_bot->HandleEmoteCommand(text_emote); + return; + } + return; + } + + case MSG_MOVE_TELEPORT_ACK: + HandleTeleportAck(); + return; + + case SMSG_QUESTGIVER_STATUS_MULTIPLE: + { + return; + } + + // used to communicate between bots + case SMSG_MESSAGECHAT: + { + WorldPacket p(packet); + p.rpos(0); //reset reader + uint8 msgtype; + uint32 language; + uint64 guid; + uint32 language2; + uint64 guid2; + uint32 textlen; + std::string msg; + + p>>msgtype; p>>language; p>>guid; p>>language2; p>>guid2; p>>textlen; + p>>msg; + + Player * fromPlayer = sObjectMgr->GetPlayer(guid); + if (fromPlayer == NULL) break; + const std::string text = msg; + HandleCommand(text, *fromPlayer); + } + + case SMSG_MONSTER_MOVE: + case SMSG_UPDATE_WORLD_STATE: + case SMSG_COMPRESSED_UPDATE_OBJECT: + case MSG_MOVE_SET_FACING: + case MSG_MOVE_STOP: + case MSG_MOVE_HEARTBEAT: + case MSG_MOVE_STOP_STRAFE: + case MSG_MOVE_START_STRAFE_LEFT: + case SMSG_UPDATE_OBJECT: + case MSG_MOVE_START_FORWARD: + case SMSG_WEATHER: + case SMSG_POWER_UPDATE: + case SMSG_TIME_SYNC_REQ: + case SMSG_STANDSTATE_UPDATE: + case SMSG_PERIODICAURALOG: + case SMSG_AURA_UPDATE: + return; + +/* + default: + const char *oc = LookupOpcodeName(packet.GetOpcode()); + TellMaster(oc); + sLog->outError("SMSG opcode: %s", oc); + */ + } +} +void PlayerbotAI::HandleTeleportAck() +{ + SetIgnoreUpdateTime(6); + m_bot->GetMotionMaster()->Clear(true); + if(m_bot->IsBeingTeleportedNear()) + { + WorldPacket p = WorldPacket(MSG_MOVE_TELEPORT_ACK, 8 + 4 + 4); + p.appendPackGUID(m_bot->GetGUID()); + p << (uint32) 0; //supposed to be flags? not used currently + p << (uint32) time(0); //time - not currently used + m_bot->GetSession()->HandleMoveTeleportAck(p); + } + else if(m_bot->IsBeingTeleportedFar()) + m_bot->GetSession()->HandleMoveWorldportAckOpcode(); +} + +uint8 PlayerbotAI::GetHealthPercent(const Unit &target) const +{ + return(static_cast<float>(target.GetHealth()) / target.GetMaxHealth()) * 100; +} + +uint8 PlayerbotAI::GetHealthPercent() const +{ + return GetHealthPercent(*m_bot); +} + +uint8 PlayerbotAI::GetManaPercent(const Unit &target) const +{ + return(static_cast<float>(target.GetPower(POWER_MANA)) / target.GetMaxPower(POWER_MANA)) * 100; +} + +uint8 PlayerbotAI::GetManaPercent() const +{ + return GetManaPercent(*m_bot); +} + +uint8 PlayerbotAI::GetBaseManaPercent(const Unit &target) const +{ + if(target.GetPower(POWER_MANA) >= target.GetCreateMana()) + return(100); + else + return(static_cast<float>(target.GetPower(POWER_MANA)) / target.GetCreateMana()) * 100; +} + +uint8 PlayerbotAI::GetBaseManaPercent() const +{ + return GetBaseManaPercent(*m_bot); +} + +uint8 PlayerbotAI::GetRageAmount(const Unit &target) const +{ + return(static_cast<float>(target.GetPower(POWER_RAGE))); +} + +uint8 PlayerbotAI::GetRageAmount() const +{ + return GetRageAmount(*m_bot); +} + +uint8 PlayerbotAI::GetEnergyAmount(const Unit &target) const +{ + return(static_cast<float>(target.GetPower(POWER_ENERGY))); +} + +uint8 PlayerbotAI::GetEnergyAmount() const +{ + return GetEnergyAmount(*m_bot); +} + +uint8 PlayerbotAI::GetRunicPower(const Unit &target) const +{ + return(static_cast<float>(target.GetPower(POWER_RUNIC_POWER))); +} + +uint8 PlayerbotAI::GetRunicPower() const +{ + return GetRunicPower(*m_bot); +} + + +typedef std::pair<uint32, uint8> spellEffectPair; +typedef std::multimap< spellEffectPair, Aura*> AuraMap; + +bool PlayerbotAI::HasAura(uint32 spellId, const Unit *player) const +{ + for(Unit::AuraMap::const_iterator iter = player->GetOwnedAuras().begin(); iter != player->GetOwnedAuras().end(); ++iter) + { + if(iter->second->GetId() == spellId) return true; + } + return false; +} +bool PlayerbotAI::HasAura(const char *spellName) const +{ + return HasAura(spellName, m_bot); +} +bool PlayerbotAI::HasAura(const char *spellName, const Unit *player) const +{ + uint32 spellId = getSpellId(spellName); + return(spellId) ? HasAura(spellId, player) : false; +} + +void PlayerbotAI::UseMount() const +{ + + if(m_master->IsMounted() && ! m_bot->IsMounted()) + { +// sLog->outError ("PlayerbotAI::UseMount: %s is mounted but %s is not", m_master->GetName(), m_bot->GetName()); + //Player Part + int32 master_speed1 = 0; + int32 master_speed2 = 0; + if(!m_master->GetAuraEffectsByType(SPELL_AURA_MOUNTED).empty()) + { + master_speed1 = m_master->GetAuraEffectsByType(SPELL_AURA_MOUNTED).front()->GetSpellProto()->EffectBasePoints[1]; + master_speed2 = m_master->GetAuraEffectsByType(SPELL_AURA_MOUNTED).front()->GetSpellProto()->EffectBasePoints[2]; + } +//sLog->outError ("master_speed1 = %d", master_speed1); +//sLog->outError ("master_speed2 = %d", master_speed2); + //Bot Part + uint32 spellMount = 0; + for(PlayerSpellMap::iterator itr = m_bot->GetSpellMap().begin(); itr != m_bot->GetSpellMap().end(); ++itr) + { + uint32 spellId = itr->first; + if(itr->second->state == PLAYERSPELL_REMOVED || itr->second->disabled || IsPassiveSpell(spellId)) + continue; + const SpellEntry *pSpellInfo = sSpellStore.LookupEntry(spellId); + if(!pSpellInfo) + continue; + if(pSpellInfo->EffectApplyAuraName[0] == SPELL_AURA_MOUNTED) + { + if((pSpellInfo->EffectApplyAuraName[1] == SPELL_AURA_MOD_INCREASE_MOUNTED_SPEED) + && (pSpellInfo->EffectApplyAuraName[2] == SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED)) + { + if((pSpellInfo->EffectBasePoints[1] == master_speed1) + && (pSpellInfo->EffectBasePoints[2] == master_speed2)) + { + spellMount = spellId; + break; + } + } + else if((pSpellInfo->EffectApplyAuraName[2] == SPELL_AURA_MOD_INCREASE_MOUNTED_SPEED) + && (pSpellInfo->EffectApplyAuraName[1] == SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED)) + { + if((pSpellInfo->EffectBasePoints[2] == master_speed2) + && (pSpellInfo->EffectBasePoints[1] == master_speed1)) + { + spellMount = spellId; + break; + } + } + else if(pSpellInfo->EffectApplyAuraName[1] == SPELL_AURA_MOD_INCREASE_MOUNTED_SPEED) + { + if(pSpellInfo->EffectBasePoints[1] == master_speed1 && master_speed2 <= 0) { spellMount = spellId; break; } //Has no secondary mount aura + else if (spellMount == 0) { spellMount = spellId; } // default to first mount in case it doesnt have correct version + } + } + } +//sLog->outError ("spellMount = %u", spellMount); + if(spellMount > 0) m_bot->GetPlayerbotAI()->CastSpell(spellMount, m_bot); + + } + else if(!m_master->IsMounted() && m_bot->IsMounted()) + { + WorldPacket emptyPacket; + m_bot->GetSession()->HandleCancelMountAuraOpcode(emptyPacket); + } +} //end UseMount + +Item *PlayerbotAI::FindFood() const +{ + // list out items in main backpack + for (uint8 slot=INVENTORY_SLOT_ITEM_START; slot < INVENTORY_SLOT_ITEM_END; slot++) + { + Item* const pItem = m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot); + if (pItem) + { + const ItemPrototype* const pItemProto = pItem->GetProto(); + if (! pItemProto || m_bot->CanUseItem(pItemProto)!=EQUIP_ERR_OK) continue; + if (pItemProto->Class==ITEM_CLASS_CONSUMABLE && + (pItemProto->SubClass==ITEM_SUBCLASS_FOOD || + pItemProto->SubClass==ITEM_SUBCLASS_POTION || + pItemProto->SubClass==ITEM_SUBCLASS_ELIXIR)) + { + // if is FOOD + if (pItemProto->Spells[0].SpellCategory == SPELL_CATEGORY_FOOD) + return pItem; + } + } + } + // list out items in other removable backpacks + for (uint8 bag = INVENTORY_SLOT_BAG_START; bag < INVENTORY_SLOT_BAG_END; ++bag) + { + const Bag* const pBag = (Bag*) m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, bag); + if (pBag) + { + for (uint8 slot = 0; slot < pBag->GetBagSize(); ++slot) + { + Item* const pItem = m_bot->GetItemByPos(bag, slot); + if (pItem) + { + const ItemPrototype* const pItemProto = pItem->GetProto(); + if (! pItemProto || m_bot->CanUseItem(pItemProto)!=EQUIP_ERR_OK) continue; + if (pItemProto->Class==ITEM_CLASS_CONSUMABLE && + (pItemProto->SubClass==ITEM_SUBCLASS_FOOD || + pItemProto->SubClass==ITEM_SUBCLASS_POTION || + pItemProto->SubClass==ITEM_SUBCLASS_ELIXIR)) + { + // if is FOOD + if (pItemProto->Spells[0].SpellCategory == SPELL_CATEGORY_FOOD) + return pItem; + } + } + } + } + } + return NULL; +} + +Item *PlayerbotAI::FindDrink() const +{ + // list out items in main backpack + for (uint8 slot=INVENTORY_SLOT_ITEM_START; slot < INVENTORY_SLOT_ITEM_END; slot++) + { + Item* const pItem = m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot); + if (pItem) + { + const ItemPrototype* const pItemProto = pItem->GetProto(); + if (! pItemProto || m_bot->CanUseItem(pItemProto)!=EQUIP_ERR_OK) continue; + if (pItemProto->Class==ITEM_CLASS_CONSUMABLE && + (pItemProto->SubClass==ITEM_SUBCLASS_FOOD || + pItemProto->SubClass==ITEM_SUBCLASS_POTION || + pItemProto->SubClass==ITEM_SUBCLASS_ELIXIR)) + { + if (pItemProto->Spells[0].SpellCategory == SPELL_CATEGORY_DRINK || + pItemProto->Spells[0].SpellCategory == 4) + return pItem; + } + } + } + // list out items in other removable backpacks + for (uint8 bag = INVENTORY_SLOT_BAG_START; bag < INVENTORY_SLOT_BAG_END; ++bag) + { + const Bag* const pBag = (Bag*) m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, bag); + if (pBag) + { + for (uint8 slot = 0; slot < pBag->GetBagSize(); ++slot) + { + Item* const pItem = m_bot->GetItemByPos(bag, slot); + if (pItem) + { + const ItemPrototype* const pItemProto = pItem->GetProto(); + + if (! pItemProto || m_bot->CanUseItem(pItemProto)!=EQUIP_ERR_OK) continue; + if (pItemProto->Class==ITEM_CLASS_CONSUMABLE && + (pItemProto->SubClass==ITEM_SUBCLASS_FOOD || + pItemProto->SubClass==ITEM_SUBCLASS_POTION || + pItemProto->SubClass==ITEM_SUBCLASS_ELIXIR)) + { + // if is WATER + if (pItemProto->Spells[0].SpellCategory == SPELL_CATEGORY_DRINK || + pItemProto->Spells[0].SpellCategory == 4) + return pItem; + } + } + } + } + } + return NULL; +} + + +Item *PlayerbotAI::FindPotion() const +{ + // list out items in main backpack + for (uint8 slot=INVENTORY_SLOT_ITEM_START; slot < INVENTORY_SLOT_ITEM_END; slot++) + { + Item* const pItem = m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot); + if (pItem) + { + const ItemPrototype* const pItemProto = pItem->GetProto(); + if (! pItemProto || m_bot->CanUseItem(pItemProto)!=EQUIP_ERR_OK) continue; + if (pItemProto->IsPotion()) + { + return pItem; + } + } + } + // list out items in other removable backpacks + for (uint8 bag = INVENTORY_SLOT_BAG_START; bag < INVENTORY_SLOT_BAG_END; ++bag) + { + const Bag* const pBag = (Bag*) m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, bag); + if (pBag) + { + for (uint8 slot = 0; slot < pBag->GetBagSize(); ++slot) + { + Item* const pItem = m_bot->GetItemByPos(bag, slot); + if (pItem) + { + const ItemPrototype* const pItemProto = pItem->GetProto(); + if (! pItemProto || m_bot->CanUseItem(pItemProto)!=EQUIP_ERR_OK) continue; + if (pItemProto->IsPotion()) + { + return pItem; + } + } + } + } + } + return NULL; +} + + + +Item *PlayerbotAI::FindBandage() const +{ + //list out items in main backpack + for(uint8 slot=INVENTORY_SLOT_ITEM_START; slot < INVENTORY_SLOT_ITEM_END; slot++) + { + Item *const pItem = m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot); + if(pItem) + { + const ItemPrototype *const pItemProto = pItem->GetProto(); + if(!pItemProto || m_bot->CanUseItem(pItemProto)!=EQUIP_ERR_OK) continue; + if(pItemProto->Class == ITEM_CLASS_CONSUMABLE && pItemProto->SubClass == ITEM_SUBCLASS_BANDAGE) return pItem; + } + } + //list out items in other removable backpacks + for(uint8 bag = INVENTORY_SLOT_BAG_START; bag < INVENTORY_SLOT_BAG_END; ++bag) + { + const Bag *const pBag = (Bag *)m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, bag); + if(pBag) + { + for(uint8 slot = 0; slot < pBag->GetBagSize(); ++slot) + { + Item *const pItem = m_bot->GetItemByPos(bag, slot); + if(pItem) + { + const ItemPrototype *const pItemProto = pItem->GetProto(); + if(!pItemProto || m_bot->CanUseItem(pItemProto)!=EQUIP_ERR_OK) continue; + if(pItemProto->Class == ITEM_CLASS_CONSUMABLE && pItemProto->SubClass == ITEM_SUBCLASS_BANDAGE) return pItem; + } + } + } + } + return NULL; +} + +// finds poison starting from the front +Item *PlayerbotAI::FindPoisonForward() const +{ + //list out items in main backpack + for(uint8 slot=INVENTORY_SLOT_ITEM_START; slot < INVENTORY_SLOT_ITEM_END; slot++) + { + Item *const pItem = m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot); + if(pItem) + { + const ItemPrototype *const pItemProto = pItem->GetProto(); + if(!pItemProto || m_bot->CanUseItem(pItemProto)!=EQUIP_ERR_OK) continue; + if(pItemProto->Class == ITEM_CLASS_CONSUMABLE && pItemProto->SubClass == ITEM_SUBCLASS_CONSUMABLE_OTHER) return pItem; + } + } + //list out items in other removable backpacks + for(uint8 bag = INVENTORY_SLOT_BAG_START; bag < INVENTORY_SLOT_BAG_END; ++bag) + { + const Bag *const pBag = (Bag *)m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, bag); + if(pBag && pBag->IsBag()) + { + for(uint8 slot = 0; slot < pBag->GetBagSize(); ++slot) + { + Item *const pItem = m_bot->GetItemByPos(bag, slot); + if(pItem) + { + const ItemPrototype *const pItemProto = pItem->GetProto(); + if(!pItemProto || m_bot->CanUseItem(pItemProto)!=EQUIP_ERR_OK) continue; + if(pItemProto->Class == ITEM_CLASS_CONSUMABLE && pItemProto->SubClass == ITEM_SUBCLASS_CONSUMABLE_OTHER) return pItem; + } + } + } + } + return NULL; +} + +// finds poison starting from the back +Item *PlayerbotAI::FindPoisonBackward() const +{ + //list out items in main backpack + for(uint8 slot=INVENTORY_SLOT_ITEM_END; slot > INVENTORY_SLOT_ITEM_START; slot--) + { + Item *const pItem = m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot); + if(pItem) + { + const ItemPrototype *const pItemProto = pItem->GetProto(); + if(!pItemProto || m_bot->CanUseItem(pItemProto)!=EQUIP_ERR_OK) continue; + if(pItemProto->Class == ITEM_CLASS_GLYPH) continue; + if(pItemProto->Class == ITEM_CLASS_CONSUMABLE && pItemProto->SubClass == ITEM_SUBCLASS_CONSUMABLE_OTHER) return pItem; + } + } + //list out items in other removable backpacks + for(uint8 bag = INVENTORY_SLOT_BAG_END; bag > INVENTORY_SLOT_BAG_START; --bag) + { + const Bag *const pBag = (Bag *)m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, bag); + if(pBag && pBag->IsBag()) + { + for(uint8 slot = pBag->GetBagSize(); slot > 0 ; --slot) + { + Item *const pItem = m_bot->GetItemByPos(bag, slot); + if(pItem) + { + const ItemPrototype *const pItemProto = pItem->GetProto(); + if(!pItemProto || m_bot->CanUseItem(pItemProto)!=EQUIP_ERR_OK) continue; + if(pItemProto->Class == ITEM_CLASS_GLYPH) continue; + if(pItemProto->Class == ITEM_CLASS_CONSUMABLE && pItemProto->SubClass == ITEM_SUBCLASS_CONSUMABLE_OTHER) return pItem; + } + } + } + } + return NULL; +} + +void PlayerbotAI::InterruptCurrentCastingSpell() +{ + //TellMaster("I'm interrupting my current spell!"); + WorldPacket *const packet = new WorldPacket(CMSG_CANCEL_CAST, 5); + uint8 counter = 1; + *packet << counter; + *packet << m_CurrentlyCastingSpellId; + m_CurrentlyCastingSpellId = 0; + m_bot->GetSession()->QueuePacket(packet); +} + +void PlayerbotAI::Feast() +{ + //stand up if we are done feasting + if(!(m_bot->GetHealth() < m_bot->GetMaxHealth() || (m_bot->getPowerType() == POWER_MANA && m_bot->GetPower(POWER_MANA) < m_bot->GetMaxPower(POWER_MANA)))) + { + m_TimeDoneDrinking = time(0) - 1; + m_TimeDoneEating = time(0) - 1; + m_bot->SetStandState(UNIT_STAND_STATE_STAND); + return; + } + + //wait 3 seconds before checking if we need to drink more or eat more + time_t currentTime = time(0); + SetIgnoreUpdateTime(3); + + //should we drink another + if(m_bot->getPowerType() == POWER_MANA && currentTime > m_TimeDoneDrinking && ((static_cast<float>(m_bot->GetPower(POWER_MANA)) / m_bot->GetMaxPower(POWER_MANA)) < 0.8)) + { + Item *pItem = FindDrink(); + if(pItem != NULL && !m_bot->HasSpellCooldown(pItem->GetSpell())) + { + UseItem(*pItem); + m_TimeDoneDrinking = currentTime + 30; + return; + } else { + + // find a mage + if (m_FeastSpamTimer > 0) --m_FeastSpamTimer; + else { + Player *mage = GetClassAI()->FindMage(m_bot); + if (mage != NULL) { + SendWhisper("I could use a drink.", *mage); + } + TellMaster("I need water."); + m_FeastSpamTimer=100; + } + } + } + + //should we eat another + if(currentTime > m_TimeDoneEating && currentTime > m_TimeDoneDrinking && ((static_cast<float>(m_bot->GetHealth()) / m_bot->GetMaxHealth()) < 0.8)) + { + Item *pItem = FindFood(); + if(pItem != NULL && !m_bot->HasSpellCooldown(pItem->GetSpell())) + { + UseItem(*pItem); + m_TimeDoneEating = currentTime + 30; + return; + } + //TellMaster("I need food."); //Disabled, tends to be horribly spammy. + } + + //if we are no longer eating or drinking + //because we are out of items or we are above 80% in both stats + if(currentTime > m_TimeDoneEating && currentTime > m_TimeDoneDrinking) + { + //TellMaster("I'm ready, let's go."); + m_bot->SetStandState(UNIT_STAND_STATE_STAND); + } +} + +Unit *PlayerbotAI::getNextTarget(Unit *victim) +{ + Unit *target = NULL; + AttackerSet m_attackers = victim->getAttackers(); + if(!m_attackers.empty()) + { + for(AttackerSet::iterator iter = m_attackers.begin(); iter != m_attackers.end(); ++iter) + { + if(*iter && m_bot->GetDistance((*iter)) < 30) + { + target = *iter; + break; + } //end if + } //end for + } + return target; +} //end getNextTarget + +//intelligently sets a reasonable combat order for this bot +//based on its class / level / etc +void PlayerbotAI::GetCombatOrders() +{ + if(m_bot->isDead() || isLooting) return; + Unit *thingToAttack=0; + + // check raid targets icons + if (!thingToAttack) + { + Group *group = m_bot->GetGroup(); + uint64 targetGUID = group->GetTargetWithIconByGroup (m_bot->GetGUID()); + if (targetGUID>0) + { + thingToAttack = ObjectAccessor::GetUnit(*m_master, targetGUID); + if (!thingToAttack || thingToAttack->isDead() || !m_bot->IsHostileTo(thingToAttack)) thingToAttack=0; +//else sLog->outError ("%s is attacking %s", m_bot->GetName(), thingToAttack->GetName()); + } + } + + //check if someone wants to attack master or me + if (!thingToAttack) thingToAttack = getNextTarget(m_master); + + if(!thingToAttack) + thingToAttack = getNextTarget(m_bot); + + //check master's target + if(!thingToAttack) + { + Unit *const pTarget = ObjectAccessor::GetUnit(*m_master, m_master->GetSelection()); + if(pTarget && pTarget->isInCombat() && pTarget->IsHostileTo(m_master)) + thingToAttack = pTarget; + } + + //last try to find something to attack + if(!thingToAttack) + { + Unit *pUnit = NULL; + Trinity::NearestHostileUnitInAttackDistanceCheck u_check((Creature*)m_bot, 30.0); + Trinity::UnitLastSearcher<Trinity::NearestHostileUnitInAttackDistanceCheck> searcher(m_bot, pUnit, u_check); + m_bot->VisitNearbyObject(30, searcher); + if(pUnit != NULL && pUnit->isAlive() && pUnit->IsHostileToPlayers()) thingToAttack = pUnit; + } + + + //if the thing to attack is a world invisible trigger, ex Glyph in UBRS, + //default to master's current victim + if(!thingToAttack || thingToAttack->GetUInt32Value(UNIT_FIELD_DISPLAYID) == 11686) thingToAttack = m_master->getVictim(); + + //if the thing to attack is an invisible trigger ex vazruden in Hellfire Ramparts, + //default to master's current victim + if(!thingToAttack || !thingToAttack->IsVisible()) thingToAttack = m_master->getVictim(); + + // if the thing to attack is not attackable + if (!thingToAttack || thingToAttack->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE)) thingToAttack = NULL; + + // override all others if ordered to pull + if (m_bot->GetPlayerbotAI()->GetClassAI()->isPulling()) { + thingToAttack = ObjectAccessor::GetUnit(*m_master,m_master->GetSelection()); + } + + if(!thingToAttack) + { + if(GetClassAI() && !m_bot->isInCombat()) (GetClassAI())->DoNonCombatActions(); + return; + } + + //wait till it gets closer + //if(m_bot->GetDistance(thingToAttack) > 30) return; + + //if thing to attack is in a duel, then ignore and don't call updateAI for 6 seconds + //this method never gets called when the bot is in a duel and this code + //prevents bot from helping + if(thingToAttack->GetTypeId() == TYPEID_PLAYER && ((Player*)(thingToAttack))->duel) + { + SetIgnoreUpdateTime(6); + return; + } + + m_bot->SetSelection(thingToAttack->GetGUID()); + SetIgnoreUpdateTime(1); + m_combatOrder = ORDERS_KILL; + + if(m_bot->getStandState() != UNIT_STAND_STATE_STAND) + m_bot->SetStandState(UNIT_STAND_STATE_STAND); + m_bot->Attack(thingToAttack, true); + + if(thingToAttack->GetTypeId() != TYPEID_PLAYER) + { + //add thingToAttack to loot list + CreatureInfo const *cInfo = ((Creature *)thingToAttack)->GetCreatureInfo(); + if(cInfo && cInfo->lootid) m_lootCreature.push_back(thingToAttack->GetGUID()); + } + + return; +} + + +void PlayerbotAI::DoNextCombatManeuver() +{ + if(isLooting) return; + + Unit *const pTarget = ObjectAccessor::GetUnit(*m_bot, m_bot->GetSelection()); + + //if current order doesn't make sense anymore + //clear our orders so we can get orders in next update + if((!pTarget || pTarget->isDead() || !pTarget->IsInWorld() || + !m_bot->IsHostileTo(pTarget) || pTarget->IsPolymorphed() || m_bot->isDead() + || ( !m_master->isInCombat() && !m_bot->isInCombat() && !pTarget->isInCombat()) // The mob probably is in evade mode, stop combat.. + //|| pTarget->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE) + ) && + !m_bot->GetPlayerbotAI()->GetClassAI()->isPulling() ) + { + m_combatOrder = ORDERS_NONE; + m_bot->SetSelection(0); + m_bot->GetMotionMaster()->Clear(true); + m_bot->InterruptNonMeleeSpells(true); +//sLog->outError ("current target doesn't make sense so following"); + Follow(*m_master); + return; + } + + if(GetClassAI()) + { + if(m_bot->HasUnitState(UNIT_STAT_CASTING)) + { + return; + } + + GetClassAI()->DoNextCombatManeuver(pTarget); + } +} + +//this is where the AI should go +//GetRandomContactPoint +//GetPower, GetMaxPower +//HasSpellCooldown +//IsAffectedBySpellmod +//isMoving +//HasUnitState(FLAG) FLAG like: UNIT_STAT_ROOT, UNIT_STAT_CONFUSED, UNIT_STAT_STUNNED +//hasAuraType + +void PlayerbotAI::UpdateAI(const uint32 p_time) +{ + time_t currentTime = time(0); + uint32 currentClock = getMSTime(); + m_bot->UpdateZone(m_bot->GetZoneId(), m_bot->GetAreaId()); + + if (m_playerBotsFly==0 && m_master->isInFlight()) + { + if (m_IsFollowingMaster) + { + const WorldLocation fakeloc = WorldLocation(35, -0.873190f, 52.920242f, -27.550674f, 1.655620f); + PlayerbotChatHandler ch(m_master); + if (! ch.teleport(*m_bot, fakeloc)) + { + ch.sysmessage(".. could not be teleported .."); + return; + } + m_bot->SendUpdateToPlayer(m_master); + } + Stay(); + return; + } + + if(m_TimeRessurect == 0 && m_bot->isDead()) + { + m_IsFollowingMaster = false; + m_TimeRessurect = currentTime + 30; + return; + } + else if(m_TimeRessurect > currentTime && m_bot->isDead()) + { + return; + } + else if((!m_TimeRessurect == 0) && m_TimeRessurect <= currentTime && m_bot->isDead()) + { + m_IsFollowingMaster = true; + m_TimeRessurect = 0; + } + if(((int64)m_ignoreAIUpdatesUntilTime - (int64)currentClock) > (int64) 30000) { SetIgnoreUpdateTime(2); return; } // Fix Timer overflow and AI freeze (max limit 30 secs) + if(currentClock < m_ignoreAIUpdatesUntilTime || m_bot->IsBeingTeleported() || m_bot->GetTrader()) return; + + + //default updates occur every 1.5 seconds + SetIgnoreUpdateTime(1.5); + + // prevent cheating + if (!m_bot->GetGroup()) + { + m_master->GetSession()->LogoutPlayerBot(m_bot->GetGUID(), false); + return; + } + + if(m_bot->isDead()) isLooting = false; + + /* + * combat checks + */ + if(m_master <= 0 || ((m_master->isInCombat() || m_bot->isInCombat()) && m_bot->isDead())) return; //You're DEAD, stop thinking. + + //if we are casting a spell then interrupt it + //make sure any actions that cast a spell set a proper m_ignoreAIUpdatesUntilTime! + Spell *const pSpell = GetCurrentSpell(); + if(pSpell && !(pSpell->IsChannelActive() || pSpell->IsAutoRepeat())) InterruptCurrentCastingSpell(); + + //direct cast command from master + else if(m_spellIdCommand != 0) + { + Unit *pTarget = ObjectAccessor::GetUnit(*m_bot, m_targetGuidCommand); + if(pTarget != NULL) CastSpell(m_spellIdCommand, pTarget); + m_spellIdCommand = 0; + m_targetGuidCommand = 0; + } + + else if(m_combatOrder != ORDERS_NONE) DoNextCombatManeuver(); //handle combat + + else if (m_bot->GetPlayerbotAI()->GetClassAI()->isPulling()) + { + GetCombatOrders(); + return; + } + + //if master is in combat and bot is not, automatically assist master + //NOTE: combat orders are also set via incoming packets to bot or outgoing packets from master + else if(m_master->isInCombat() && (!m_bot->isInCombat() || m_combatOrder == ORDERS_NONE) || m_master->isDead()) GetCombatOrders(); + + //if bot is in combat but master is not, attack + else if(m_bot->isInCombat()) GetCombatOrders(); + + // if bot is not in combat, but main tank is + else if (!m_bot->isInCombat()) { + Unit *tank=m_classAI->FindMainTankInRaid(m_bot); + if (tank!=NULL && tank->isInCombat()) GetCombatOrders(); + + } + + /* + * Non combat checks + */ + + //are we sitting, if so feast if possible +// if(m_bot->getStandState() == UNIT_STAND_STATE_SIT) { +//sLog->outError ("%s - sitting so feast", m_bot->GetName()); +// +// } + + //if commanded to follow master and not already following master then follow master + if(!m_bot->isInCombat() && m_IsFollowingMaster && m_bot->GetMotionMaster()->GetCurrentMovementGeneratorType() == IDLE_MOTION_TYPE) + { + Follow(*m_master); + + //do class specific non combat actions + } else if(!m_bot->isInCombat() && GetClassAI()) { + (GetClassAI())->DoNonCombatActions(); + } if(!m_master->isInCombat()) + DoLoot(); + + + if (m_master->getStandState() == UNIT_STAND_STATE_SIT) + { + m_bot->SetStandState(UNIT_STAND_STATE_SIT); + m_bot->SendUpdateToPlayer(m_master); + Feast(); + } + else if (m_TimeDoneDrinking < time(0) && m_TimeDoneEating < time(0)) //Do no interrupt if bot is eating/drinking + { + m_bot->SetStandState(UNIT_STAND_STATE_STAND); + m_bot->SendUpdateToPlayer(m_master); + } + + + //try to catch if he is falling through the world. This happens + //when zoning in/out of an instance + if(m_IsFollowingMaster && m_bot->GetMapId() != m_master->GetMapId() || + //m_bot->GetZoneId() != m_master->GetZoneId() || + (abs(abs(m_bot->GetPositionX()) - abs(m_master->GetPositionX())) > 90) || + (abs(abs(m_bot->GetPositionY()) - abs(m_master->GetPositionY())) > 90) || + (abs(abs(m_bot->GetPositionZ()) - abs(m_master->GetPositionZ())) > 50)) + { +//sLog->outError ("%s: %s is too far away so following", m_bot->GetName(), m_master->GetName()); + Follow(*m_master); + } +} + + +void PlayerbotAI::KilledMonster(uint32 entry, uint64 guid) +{ + // isLooting = true; + + if(m_master->isAlive() && m_IsFollowingMaster && !m_master->isInCombat()) + { + if(!DoLoot()) + { + float angle = rand_norm()*M_PI; //Generates random float between 0 and 3.14 + float dist = (float)(urand((m_followDistanceMin*10), (m_followDistanceMax*10))/10); + + m_bot->GetMotionMaster()->Clear(true); + m_bot->GetMotionMaster()->MoveFollow(m_master, dist, angle); + } + } + + // reset main tank every time we finish combat, just in case the + // original main tank died and got set to next tank. + //m_classAI->SetMainTank(NULL); +} + +Spell *PlayerbotAI::GetCurrentSpell() const +{ + if(m_CurrentlyCastingSpellId == 0) return NULL; + Spell *const pSpell = m_bot->FindCurrentSpellBySpellId(m_CurrentlyCastingSpellId); + return pSpell; +} + +void PlayerbotAI::TellMaster(const std::string &text) +{ + SendWhisper(text, *m_master); +} + +bool PlayerbotAI::CanBotsFly() +{ + if (m_playerBotsFly==0) return false; + else return true; +} + +void PlayerbotAI::SendWhisper(const std::string &text, Player &player) +{ + WorldPacket data(SMSG_MESSAGECHAT, 200); + + data << uint8(CHAT_MSG_WHISPER_INFORM); + data << uint32(LANG_UNIVERSAL); + data << uint64(player.GetGUID()); + data << uint32(LANG_UNIVERSAL); //language 2.1.0 ? + data << uint64(player.GetGUID()); + data << uint32(text.length() + 1); + data << text; + data << uint8(player.chatTag()); + + player.GetSession()->SendPacket(&data); +} + +bool PlayerbotAI::canObeyCommandFrom(const Player &player) const +{ + return player.GetSession()->GetAccountId() == m_master->GetSession()->GetAccountId(); +} + +void PlayerbotAI::SetInFront(const Unit *obj) +{ + if(!m_bot->HasInArc(M_PI, obj)) + { + m_bot->SetInFront(obj); + m_bot->SendUpdateToPlayer(m_master); + } +} + +bool PlayerbotAI::CastSpell(const char *args) +{ + uint32 spellId = getSpellId(args); + return(spellId) ? CastSpell(spellId) : false; +} + +bool PlayerbotAI::CastSpell(uint32 spellId, Unit *target, bool checkFirst, bool castExistingAura, bool skipFriendlyCheck, bool skipEquipStanceCheck, bool triggered) +{ + if (!spellId) return false; + + if (!m_bot->HasSpell(spellId)) { + return false; + } + + const SpellEntry * pSpellInfo = sSpellStore.LookupEntry(spellId); + if (!pSpellInfo) + { + sLog->outDebug("CRITICAL: PBot Class %u - Non-existing Spell in Cast - [%u]", m_bot->getClass(), spellId); + std::stringstream ss; + ss << "Missing spell entry in CastSpell - "; + ss << spellId; + TellMaster(ss.str()); + SetIgnoreUpdateTime(1); + return false; + } + return CastSpell(pSpellInfo, target, checkFirst, castExistingAura, skipFriendlyCheck, skipEquipStanceCheck, triggered); +} + +bool PlayerbotAI::CastSpell(const SpellEntry * pSpellInfo, Unit *target, bool checkFirst, bool castExistingAura, bool skipFriendlyCheck, bool skipEquipStanceCheck, bool triggered) +{ + if(!m_bot->isAlive()) return false; + if(!pSpellInfo) + { + sLog->outError ("%s: Missing spell entry in CastSpell Direct", m_bot->GetName()); + TellMaster("Missing spell entry in CastSpell"); + SetIgnoreUpdateTime(1); + return false; + } + uint32 spellId = pSpellInfo->Id; + uint64 oldSel = m_bot->GetSelection(); + + // Auto Targeting + if (!target) + { + //NEGATIVE SPELL + if (sSpellMgr->GetSpellCustomAttr(spellId) & SPELL_ATTR0_CU_NEGATIVE) + { + if (m_bot->GetSelection() <= 0) return false; + else + { + target = ObjectAccessor::GetUnit(*m_bot, m_bot->GetSelection()); + if (!target) return false; + } + } + else { target = m_bot; } + } + + //Make the Checks + + if (!triggered && checkFirst && !CanCast(pSpellInfo, target, castExistingAura, skipFriendlyCheck, skipEquipStanceCheck) ) { return false; } + if ( m_bot->GetSelection() != target->GetGUID() ) { m_bot->SetSelection(target->GetGUID()); } //if target is different than selection apply it + + m_bot->CastSpell(target, pSpellInfo, triggered); //CAST THE SPELL + if ( m_bot->GetSelection() != oldSel ) { m_bot->SetSelection(oldSel); } // Restore if target changed to cast + + // Check if the casting started.. + Spell *const pSpell = m_bot->FindCurrentSpellBySpellId(spellId); + if(!pSpell) return false; + + // Trigger Pseudo Global Cooldown and consider casttime + float GCD = 1.5f; + if (m_bot->getPowerType() == POWER_ENERGY) GCD = 1; + float psCastTime = ((float)pSpell->GetCastTime()) / 1000.0f; + if (psCastTime - GCD > -0.3f) GCD = 0.3f; //Global cooldown won't be an issiue for casts (0.3 secs is for safe next cast) + else { GCD -= psCastTime; } //Remaining GCD after cast.. + //float psRecoveryTime = GetSpellRecoveryTime(pSpellInfo) / 1000; + //sLog->outDebug("Bot [%u] Start Spell [%u] Cast Time [%f]", m_bot->GetGUIDLow(), pSpellInfo->Id, psCastTime); + m_CurrentlyCastingSpellId = spellId; + //SetIgnoreUpdateTime(psCastTime + GCD); + SetIgnoreUpdateTime(psCastTime > GCD ? psCastTime : GCD); + return true; +} + +bool PlayerbotAI::CanCast(uint32 spellId, Unit *target, bool castExistingAura, bool skipFriendlyCheck, bool skipEquipStanceCheck) +{ + //if spellId == 0, it means that the bot is not high enough level to + //have learned the spell + if (!spellId) return false; + const SpellEntry * pSpellInfo = sSpellStore.LookupEntry(spellId); + if (!pSpellInfo) + { + sLog->outDebug("CRITICAL: PBot Class %u - Non-existing Spell in Cast - [%u]", m_bot->getClass(), spellId); + std::stringstream ss; + ss << "Missing spell entry in CastSpell - "; + ss << spellId; + TellMaster(ss.str()); + SetIgnoreUpdateTime(1); + return false; + } + return CanCast(pSpellInfo, target, castExistingAura, skipFriendlyCheck, skipEquipStanceCheck); +} + +bool PlayerbotAI::CanCast(const SpellEntry * pSpellInfo, Unit *target, bool castExistingAura, bool skipFriendlyCheck, bool skipEquipStanceCheck) +{ + if (!pSpellInfo) + { + sLog->outDebug("CRITICAL: PBot Class %u - Non-existing Spell in CastCheck - Direct SpellEntry", m_bot->getClass()); + TellMaster("Missing spell entry in CastSpell"); + SetIgnoreUpdateTime(1); + return false; + } + uint32 spellId = pSpellInfo->Id; + + // Auto Targeting + if (!target) + { + //NEGATIVE SPELL + if (sSpellMgr->GetSpellCustomAttr(spellId) & SPELL_ATTR0_CU_NEGATIVE) + { + if (m_bot->GetSelection() <= 0) return false; + else + { + target = ObjectAccessor::GetUnit(*m_bot, m_bot->GetSelection()); + if (!target) return false; + } + } + else { target = m_bot; } + } + + if (!m_bot->isAlive()) return false; + if (m_bot->HasSpellCooldown(spellId)) return false; + if (m_bot->HasUnitState(UNIT_STAT_CASTING)) return false; + if (m_bot->IsMounted()) return false; + + //cast existing aura over again? + if (!castExistingAura && target->HasAura(spellId, m_bot->GetGUID())) return false; + + //Stances-forms and equipment REQs + if (!skipEquipStanceCheck) + { + uint32 formMask = (GetForm() ? 1 << (GetPlayerBot()->GetShapeshiftForm() - 1) : 0); + //sLog->outDebug("DEBUG: Spell [%u] - Form [%X] - Need Form [%X] - Not Form [%X]", pSpellInfo->Id, formMask, pSpellInfo->Stances, pSpellInfo->StancesNot ); + if (pSpellInfo->Stances & formMask) { return true; } + if (pSpellInfo->StancesNot && pSpellInfo->StancesNot & formMask) { return false; } + if (!m_bot->HasItemFitToSpellRequirements(pSpellInfo)) return false; + } + + //Power Costs + const SpellSchoolMask pSpellSchool = GetSpellSchoolMask(pSpellInfo); + uint32 pPowerCost = CalculatePowerCost(pSpellInfo, m_bot, pSpellSchool); + + if (skipEquipStanceCheck) { if (m_bot->GetPower((Powers)pSpellInfo->powerType) < pPowerCost) return false; } //Power check for Required PowerType (After changind stance, powertype may change, for druids) + else { if (m_bot->GetPower(m_bot->getPowerType()) < pPowerCost) return false; } //Power check for Current m_bot Power Type + + //Distance / movement checks + const SpellRangeEntry * pSpellRange = sSpellRangeStore.LookupEntry(pSpellInfo->rangeIndex); + float curDistance = m_bot->GetDistance(target); + if (GetSpellCastTime(pSpellInfo) > 0 && m_bot->isMoving()) return false; //Cannot cast while moving + + //The target is immune or not? + if (target->GetTypeId() != TYPEID_PLAYER) { if ( ((Creature*)target)->IsImmunedToSpell(pSpellInfo)) { return false; } } + else { if (target->IsImmunedToSpell(pSpellInfo)) { return false; } } + + //target reaction checks (Has problems with dual effect spells like death coil/holy shock) + if (skipFriendlyCheck) + { + if (pSpellRange->maxRangeHostile != 0) { if ( pSpellRange->maxRangeHostile < curDistance || pSpellRange->minRangeHostile > curDistance ) { return false; } } //Assume hostile spell + } + else if (IsPositiveSpell(spellId)) + { + if(! m_bot->IsFriendlyTo(target)) { return false; } + else if (pSpellRange->maxRangeFriend != 0) { if (pSpellRange->maxRangeFriend < curDistance || pSpellRange->minRangeFriend > curDistance) { return false; } } + else if (curDistance > MELEE_RANGE) return false; + } + else + { + if (m_bot->IsFriendlyTo(target)) return false; + if (!m_bot->HasInArc(M_PI,target)) return false; //target is not in front + if (pSpellRange->maxRangeHostile != 0) { if ( pSpellRange->maxRangeHostile < curDistance || pSpellRange->minRangeHostile > curDistance ) { return false; } } + else if (curDistance > MELEE_RANGE) return false; //Out of range - Melee Range + } + + return true; +} + +uint8 PlayerbotAI::GetForm(Unit *pPlayer) +{ + if (!pPlayer) pPlayer = m_bot; + return (pPlayer->GetUInt32Value(UNIT_FIELD_BYTES_2) & 0xFF000000) >> (4 * 6); +} + +//extracts all item ids in format below +//I decided to roll my own extractor rather then use the one in ChatHandler +//because this one works on a const string, and it handles multiple links +//|color|linkType:key:something1:...:somethingN|h[name]|h|r +void PlayerbotAI::extractItemIds(const std::string &text, std::list<uint32> &itemIds) const +{ + uint8 pos = 0; + while(true) + { + int i = text.find("Hitem:", pos); + if(i == -1) break; + pos = i + 6; + int endPos = text.find(':', pos); + if(endPos == -1) break; + std::string idC = text.substr(pos, endPos - pos); + uint32 id = atol(idC.c_str()); + pos = endPos; + if(id) itemIds.push_back(id); + } +} + +bool PlayerbotAI::extractGOinfo(const std::string& text, uint32 &guid, uint32 &entry, int &mapid, float &x, float &y, float &z) const +{ + + // Link format + // |cFFFFFF00|Hfound:" << guid << ':' << entry << ':' << x << ':' << y << ':' << z << ':' << mapid << ':' << "|h[" << gInfo->name << "]|h|r"; + + // |cFFFFFF00|Hfound:5093:1731:-9295:-270:81.874:0:|h[Copper Vein]|h|r + uint8 pos = 0; + // extract GO guid + int i = text.find("Hfound:", pos); // base H = 11 + if (i == -1) // break if error + return false; + + pos = i + 7; //start of window in text 11 + 7 = 18 + int endPos = text.find(':', pos); // end of window in text 22 + if (endPos == -1) //break if error + return false; + std::string guidC = text.substr(pos, endPos - pos); // get string within window i.e guid 22 - 18 = 4 + guid = atol(guidC.c_str()); // convert ascii to long int + // extract GO entry + pos = endPos + 1; + endPos = text.find(':', pos); // end of window in text + if (endPos == -1) //break if error + return false; + std::string entryC = text.substr(pos, endPos - pos); // get string within window i.e entry + entry = atol(entryC.c_str()); // convert ascii to float + // extract GO x + pos = endPos + 1; + endPos = text.find(':', pos); // end of window in text + if (endPos == -1) //break if error + return false; + std::string xC = text.substr(pos, endPos - pos); // get string within window i.e x + + x = atof(xC.c_str()); // convert ascii to float + // extract GO y + pos = endPos + 1; + endPos = text.find(':', pos); // end of window in text + if (endPos == -1) //break if error + return false; + std::string yC = text.substr(pos, endPos - pos); // get string within window i.e y + y = atof(yC.c_str()); // convert ascii to float + // extract GO z + + pos = endPos + 1; + endPos = text.find(':', pos); // end of window in text + if (endPos == -1) //break if error + return false; + + std::string zC = text.substr(pos, endPos - pos); // get string within window i.e z + + z = atof(zC.c_str()); // convert ascii to float + + //extract GO mapid + pos = endPos + 1; + endPos = text.find(':', pos); // end of window in text + if (endPos == -1) //break if error + return false; + std::string mapidC = text.substr(pos, endPos - pos); // get string within window i.e mapid + mapid = atoi(mapidC.c_str()); // convert ascii to int + pos = endPos; // end + return true; +} + +//extracts currency in #g#s#c format +uint32 PlayerbotAI::extractMoney(const std::string &text) const +{ + //if user specified money in ##g##s##c format + std::string acum = ""; + uint32 copper = 0; + for(uint8 i = 0; i < text.length(); i++) + { + if(text[i] == 'g') + { + copper += (atol(acum.c_str()) * 100 * 100); + acum = ""; + } + else if(text[i] == 'c') + { + copper += atol(acum.c_str()); + acum = ""; + } + else if(text[i] == 's') + { + copper += (atol(acum.c_str()) * 100); + acum = ""; + } + else if(text[i] == ' ') + { + break; + } + else if(text[i] >= 48 && text[i] <= 57) + { + acum += text[i]; + } else { + copper = 0; + break; + } + } + return copper; +} + +// finds items in equipment and adds Item* to foundItemList +// also removes found item IDs from itemIdSearchList when found +void PlayerbotAI::findItemsInEquip(std::list<uint32>& itemIdSearchList, std::list<Item*>& foundItemList) const +{ + for( uint8 slot=EQUIPMENT_SLOT_START; itemIdSearchList.size()>0 && slot<EQUIPMENT_SLOT_END; slot++ ) { + Item* const pItem = m_bot->GetItemByPos( INVENTORY_SLOT_BAG_0, slot ); + if( !pItem ) + continue; + + for (std::list<uint32>::iterator it = itemIdSearchList.begin(); it != itemIdSearchList.end(); ++it) + { + if (pItem->GetProto()->ItemId != *it) + continue; + + foundItemList.push_back(pItem); + itemIdSearchList.erase(it); + break; + } + } +} + + +//finds items in inventory and adds Item *to foundItemList +//also removes found item IDs from itemIdSearchList when found +void PlayerbotAI::findItemsInInv(std::list<uint32>& itemIdSearchList, std::list<Item*>& foundItemList) const +{ + + //look for items in main bag + for(uint8 slot = INVENTORY_SLOT_ITEM_START; itemIdSearchList.size() > 0 && slot < INVENTORY_SLOT_ITEM_END; ++slot) + { + Item *const pItem = m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot); + if(!pItem) continue; + for(std::list<uint32>::iterator it = itemIdSearchList.begin(); it != itemIdSearchList.end(); ++it) + { + if(pItem->GetProto()->ItemId != *it) continue; + foundItemList.push_back(pItem); + itemIdSearchList.erase(it); + break; + } + } + + //for all for items in other bags + for(uint8 bag = INVENTORY_SLOT_BAG_START; itemIdSearchList.size() > 0 && bag < INVENTORY_SLOT_BAG_END; ++bag) + { + Bag *const pBag = (Bag *)m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, bag); + if(!pBag) continue; + for(uint8 slot = 0; itemIdSearchList.size() > 0 && slot < pBag->GetBagSize(); ++slot) + { + Item *const pItem = m_bot->GetItemByPos(bag, slot); + if(!pItem) continue; + for(std::list<uint32>::iterator it = itemIdSearchList.begin(); it != itemIdSearchList.end(); ++it) + { + if(pItem->GetProto()->ItemId != *it) continue; + foundItemList.push_back(pItem); + itemIdSearchList.erase(it); + break; + } + } + } +} + +bool PlayerbotAI::HasPick() +{ + QueryResult result; + + // list out equiped items + for( uint8 slot = EQUIPMENT_SLOT_START; slot < EQUIPMENT_SLOT_END; slot++) + { + Item* const pItem = m_bot->GetItemByPos( INVENTORY_SLOT_BAG_0, slot ); + if (pItem ) + { + const ItemPrototype* const pItemProto = pItem->GetProto(); + + if (!pItemProto ) + continue; + + result = WorldDatabase.PQuery("SELECT TotemCategory FROM item_template WHERE entry = '%i'", pItemProto->ItemId); + if (result) + { + Field *fields = result->Fetch(); + uint32 tc = fields[0].GetUInt32(); + // sLog->outDebug("HasPick %u",tc); + if(tc == 165 || tc == 167) // pick = 165, hammer = 162 or hammer pick = 167 + return true; + } + } + } + + // list out items in backpack + for (uint8 slot = INVENTORY_SLOT_ITEM_START; slot < INVENTORY_SLOT_ITEM_END; slot++) + { + // sLog->outDebug("[%s's]backpack slot = %u",m_bot->GetName(),slot); // 23 to 38 = 16 + Item* const pItem = m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot); // 255, 23 to 38 + if (pItem) + { + const ItemPrototype* const pItemProto = pItem->GetProto(); + if (!pItemProto ) + continue; + result = WorldDatabase.PQuery("SELECT TotemCategory FROM item_template WHERE entry = '%i'", pItemProto->ItemId); + if (result) + { + Field *fields = result->Fetch(); + uint32 tc = fields[0].GetUInt32(); + // sLog->outDebug("HasPick %u",tc); + if(tc == 165 || tc == 167) // pick = 165, hammer = 162 or hammer pick = 167 + return true; + } + } + } + + // list out items in other removable backpacks + for (uint8 bag = INVENTORY_SLOT_BAG_START; bag < INVENTORY_SLOT_BAG_END; ++bag) // 20 to 23 = 4 + { + const Bag* const pBag = (Bag*) m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, bag); // 255, 20 to 23 + if (pBag) + { + for (uint8 slot = 0; slot < pBag->GetBagSize(); ++slot) + { + // sLog->outDebug("[%s's]bag[%u] slot = %u",m_bot->GetName(),bag,slot); // 1 to bagsize = ? + Item* const pItem = m_bot->GetItemByPos(bag, slot); // 20 to 23, 1 to bagsize + if (pItem) + { + const ItemPrototype* const pItemProto = pItem->GetProto(); + + if (!pItemProto ) + continue; + + result = WorldDatabase.PQuery("SELECT TotemCategory FROM item_template WHERE entry = '%i'", pItemProto->ItemId); + if (result) + { + + Field *fields = result->Fetch(); + uint32 tc = fields[0].GetUInt32(); + // sLog->outDebug("HasPick %u",tc); + if(tc == 165 || tc == 167) + return true; + } + } + } + } + } + std::ostringstream out; + out << "|cffffffffI do not have a pick!"; + TellMaster( out.str().c_str() ); + return false; +} + + +//submits packet to use an item +void PlayerbotAI::PoisonWeapon(Item &item, uint32 _spellId, uint32 _target, EquipmentSlots weaponSlot) +{ + uint8 bagIndex = item.GetBagSlot(); + uint8 slot = item.GetSlot(); + uint8 cast_count = 1; + uint32 spellid = _spellId; + uint64 item_guid = item.GetGUID(); + uint32 glyphIndex = 0; //?? + uint8 unk_flags = 0; //not 0x02 + + uint32 target = _target; + uint32 targetItemGUID = 0; + uint8 x = 0; + Item *weapon=NULL; + if (_spellId>0){ + + targetItemGUID = 16; + x = 135; + cast_count = 7; + weapon = GetPlayerBot()->GetItemByPos( INVENTORY_SLOT_BAG_0, weaponSlot ); + + } + + WorldPacket *const packet = new WorldPacket(CMSG_USE_ITEM, 1 + 1 + 1 + 4 + 8 + 4 + 1 + 4); + *packet << bagIndex << slot << cast_count << spellid << item_guid << glyphIndex << unk_flags << target; + if (weapon) packet->appendPackGUID(weapon->GetGUID()); + + m_bot->GetSession()->QueuePacket(packet); //queue the packet to get around race condition + +} // end PoisonWeapon + + + +//submits packet to use an item +void PlayerbotAI::UseItem(Item &item) +{ + uint8 bagIndex = item.GetBagSlot(); + uint8 slot = item.GetSlot(); + uint8 cast_count = 1; + uint32 spellid = 0; //only used in combat + uint64 item_guid = item.GetGUID(); + uint32 glyphIndex = 0; //?? + uint8 unk_flags = 0; //not 0x02 + + //create target data + //note other targets are possible but not supported at the moment + //see SpellCastTargets::read in Spell.cpp to see other options + //for setting target + + uint32 target = TARGET_FLAG_SELF; + + WorldPacket *const packet = new WorldPacket(CMSG_USE_ITEM, 1 + 1 + 1 + 4 + 8 + 4 + 1); + *packet << bagIndex << slot << cast_count << spellid << item_guid << glyphIndex << unk_flags << target; + m_bot->GetSession()->QueuePacket(packet); //queue the packet to get aroundrace condition + +} // end UseItem + + +//submits packet to use an item +void PlayerbotAI::EquipItem(Item &item) +{ + uint8 bagIndex = item.GetBagSlot(); + uint8 slot = item.GetSlot(); + + WorldPacket *const packet = new WorldPacket(CMSG_AUTOEQUIP_ITEM, 2); + *packet << bagIndex << slot; + m_bot->GetSession()->QueuePacket(packet); +} + +// submits packet to trade an item (trade window must already be open) +// default slot is -1 which means trade slots 0 to 5. if slot is set +// to TRADE_SLOT_NONTRADED (which is slot 6) item will be shown in the +// 'Will not be traded' slot. +bool PlayerbotAI::TradeItem(const Item& item, int8 slot) +{ + sLog->outDebug( "[PlayerbotAI::TradeItem]: slot=%d, hasTrader=%d, itemInTrade=%d, itemTradeable=%d", + slot, + (m_bot->GetTrader()?1:0), + (item.IsInTrade()?1:0), + (item.CanBeTraded()?1:0) + ); + + if (!m_bot->GetTrader() || item.IsInTrade()) + return false; + + int8 tradeSlot = -1; + + if( (slot>=0 && slot<TRADE_SLOT_COUNT) /*&& m_bot->GetItemPosByTradeSlot(slot)==NULL_SLOT */) { + tradeSlot = slot; + } else if (!item.CanBeTraded()) + { + tradeSlot = (uint8) TRADE_SLOT_NONTRADED; + } + else + { + for( uint8 i=0; i<TRADE_SLOT_TRADED_COUNT; ++i ) + { + if (m_bot->GetTradeData()->GetItem(TradeSlots(i)) == NULL){ + tradeSlot = (uint8)i; + break; + } + } + } + + if( tradeSlot == -1 ) return false; + + WorldPacket* const packet = new WorldPacket(CMSG_SET_TRADE_ITEM, 3); + *packet << (uint8) tradeSlot << (uint8) item.GetBagSlot() + << (uint8) item.GetSlot(); + m_bot->GetSession()->QueuePacket(packet); + + return true; +} + + +//submits packet to trade copper (trade window must be open) +bool PlayerbotAI::TradeCopper(uint32 copper) +{ + if(copper > 0) + { + WorldPacket *const packet = new WorldPacket(CMSG_SET_TRADE_GOLD, 4); + *packet << copper; + m_bot->GetSession()->QueuePacket(packet); + return true; + } + return false; +} + +void PlayerbotAI::Stay() +{ + if (!m_IsFollowingMaster) + return; + + m_IsFollowingMaster = false; + m_bot->GetMotionMaster()->Clear(true); + m_bot->HandleEmoteCommand(EMOTE_ONESHOT_SALUTE); +} + + +bool PlayerbotAI::Follow(Player &player) +{ + if(m_master <= 0 || ((m_master->isInCombat() || m_bot->isInCombat()) && m_bot->isDead())) return false; //You're DEAD, stop thinking. + if(m_master->isDead()) return false; + if(m_master->IsBeingTeleported() || m_master->isInFlight()) return false; + + if(m_bot->getStandState() == UNIT_STAND_STATE_SIT && (m_TimeDoneDrinking < time(0) && m_TimeDoneEating < time(0))) return false; //Do no interrupt if bot is eating/drinking + + m_IsFollowingMaster = true; + + if(!m_bot->IsStandState()) { + m_bot->SetStandState(UNIT_STAND_STATE_STAND); + m_bot->SendUpdateToPlayer(m_master); + } + + if(!m_bot->isInCombat()) + { + //if bot is dead and master is alive, revive bot + if(m_master->isAlive() && !m_bot->isAlive()) + { + SetIgnoreUpdateTime(6); + isLooting = false; + PlayerbotChatHandler ch(m_master); + if(!ch.revive(*m_bot)) + { + ch.sysmessage(".. could not be revived .."); + return false; + } + } + + if(!m_bot->isDead() && !m_bot->IsBeingTeleported() && !player.isDead() && + (m_bot->GetMapId() != player.GetMapId() + || m_bot->GetZoneId() != player.GetZoneId() + || m_bot->GetAreaId() != player.GetAreaId() + || m_bot->GetPhaseMask() != player.GetPhaseMask()) + || m_bot->GetDistance(player) > 255) + { + SetIgnoreUpdateTime(6); + isLooting = false; + PlayerbotChatHandler ch(m_master); + if(!ch.teleport(*m_bot)) + { + ch.sysmessage(".. could not be teleported .."); + return false; + } + } + } + + if(m_bot->isAlive() && !isLooting) + { + float angle = M_PI/2 + rand_norm()*M_PI ; //Generates random float between 90 and 270 degrees + float dist = (float)(urand((m_followDistanceMin*10), (m_followDistanceMax*10))/10); // Using urand to get a random float is stupid. + m_bot->GetMotionMaster()->Clear(true); + m_bot->GetMotionMaster()->MoveFollow(&player, dist, angle); + + return true; + } + return false; +} + +//handle commands sent through chat channels +void PlayerbotAI::HandleCommand(const std::string &text, Player &fromPlayer) +{ + //ignore any messages from Addons + if(text.empty() || + text.find("X-Perl") != std::wstring::npos || + text.find("HealBot") != std::wstring::npos || + text.find("LOOT_OPENED") != std::wstring::npos || + text.find("CTRA") != std::wstring::npos) + return; + + //if message is not from a player in the masters account auto reply and ignore + //if(!canObeyCommandFrom(fromPlayer)) + //{ + //std::string msg = "I can't talk to you. Please speak to my master "; + //msg += m_master->GetName(); + //msg += "."; + //m_bot->SendWhisper(msg, *fromPlayer); + //m_bot->HandleEmoteCommand(EMOTE_ONESHOT_NO); + //} + + + else if (text == "I could use a drink." && + m_bot->getClass() == CLASS_MAGE) + { + Item const*pItem = FindDrink(); + if (pItem != NULL) { + WorldPacket *p = new WorldPacket(CMSG_INITIATE_TRADE, 8); + *p << fromPlayer.GetGUID(); + m_bot->GetSession()-> HandleInitiateTradeOpcode(*p); + SendWhisper ("Here is a tasty treat for you", fromPlayer); + + } + } + // accept food/drink from mage + else if (text == "Here is a tasty treat for you") + { + m_bot->Say ("Thank you for this treat", LANG_UNIVERSAL); + SendWhisper ("Thank you for this treat.", fromPlayer); + } + // trade opened so send items over + else if (text == "Thank you for this treat") + { + if (m_bot->getClass()!=CLASS_MAGE) return; + + Item const*pItem = FindDrink(); + if (pItem==NULL) return; + + bool trade = TradeItem (*pItem,2); + if (trade) { + m_bot->Say("Enjoy the refreshing drink.", LANG_UNIVERSAL); + + WorldPacket* const packet = new WorldPacket(CMSG_ACCEPT_TRADE, 3); + m_bot->GetSession()->QueuePacket(packet); // packet is not used + } else { + m_bot->Say ("I cannot trade with you.", LANG_UNIVERSAL); + } + } + + // if in the middle of a trade, and player asks for an item/money + else if (m_bot->GetTrader() && m_bot->GetTrader()->GetGUID() == fromPlayer.GetGUID() && + fromPlayer.GetPlayerbotAI() == NULL) + { + uint32 copper = extractMoney(text); + if (copper > 0) + TradeCopper(copper); + + std::list<uint32> itemIds; + extractItemIds(text, itemIds); + if (itemIds.size() == 0) + SendWhisper("Show me what item you want by shift clicking the item in the chat window.", fromPlayer); + else + { + std::list<Item*> itemList; + findItemsInInv(itemIds, itemList); + findItemsInEquip(itemIds, itemList); + for (std::list<Item*>::iterator it = itemList.begin(); it != itemList.end(); ++it) + TradeItem(**it); + } + } + + + + else if(text == "follow" || text == "come") + Follow(*m_master); + + else if(text == "stay" || text == "stop") + Stay(); + + //handle cast command + else if(text.size() > 2 && text.substr(0, 2) == "c " || + text.size() > 5 && text.substr(0, 5) == "cast ") + { + uint32 spellId = 0; + std::string spellStr = text.substr(text.find(" ") + 1); + + if(spellStr.find("Hspell:")) + { + spellStr = spellStr.substr(spellStr.find("|h[") + 3); + spellStr = spellStr.substr(0, spellStr.find("]")); + } else + spellId = (uint32)atol(spellStr.c_str()); + + //try and get spell ID by name + if(spellId == 0) spellId = getSpellId(spellStr.c_str(), true); + + uint64 castOnGuid = fromPlayer.GetSelection(); + if(castOnGuid == 0) castOnGuid = m_bot->GetGUID(); + if(spellId != 0) + { + m_spellIdCommand = spellId; + m_targetGuidCommand = castOnGuid; + } + } + + //use items + else if(text.size() > 2 && text.substr(0, 2) == "u " || + text.size() > 4 && text.substr(0, 4) == "use ") + { + std::list<uint32> itemIds; + std::list<Item *> itemList; + extractItemIds(text, itemIds); + findItemsInInv(itemIds, itemList); + for(std::list<Item *>::iterator it = itemList.begin(); it != itemList.end(); ++it) UseItem(**it); + } + + // poison mainhand weapon + else if(text.size() > 2 && text.substr(0, 2) == "p " || + text.size() >= 8 && text.substr(0, 8) == "poison m") + { + + Item *poison = FindPoisonForward(); + if(poison == NULL) { + std::string msg = "No poison found for mainhand."; + SendWhisper(msg, fromPlayer); + return; + } + + PoisonWeapon(*poison, poison->GetProto()->Spells[0].SpellId, TARGET_FLAG_ITEM, EQUIPMENT_SLOT_MAINHAND); + } + + // poison offhand weapon + else if(text.size() >= 8 && text.substr(0, 8) == "poison o") + { + + Item *poison = FindPoisonBackward(); + if(poison == NULL) { + std::string msg = "No poison found for offhand."; + SendWhisper(msg, fromPlayer); + return; + } + + PoisonWeapon(*poison, poison->GetProto()->Spells[0].SpellId, TARGET_FLAG_ITEM, EQUIPMENT_SLOT_OFFHAND); + } + + // npcbot commands + else if(text.size() >= 8 && text.substr(0, 8) == "npcbot a") + { + if(m_bot->HaveBot()) { + SendWhisper("I already have a bot.", fromPlayer); + return; + } + + std::string text1 = text.substr(text.find(" ") + 1); + std::string botClass = text1.substr(text1.find(" ") + 1); + + + if (botClass == "priest") m_bot->CreateNPCBot(CLASS_PRIEST); + else if (botClass == "warrior") m_bot->CreateNPCBot(CLASS_WARRIOR); + else if (botClass == "druid") m_bot->CreateNPCBot(CLASS_DRUID); + else if (botClass == "paladin") m_bot->CreateNPCBot(CLASS_PALADIN); + else if (botClass == "hunter") m_bot->CreateNPCBot(CLASS_HUNTER); + else if (botClass == "mage") m_bot->CreateNPCBot(CLASS_MAGE); + else if (botClass == "warlock") m_bot->CreateNPCBot(CLASS_WARLOCK); + else if (botClass == "shaman") m_bot->CreateNPCBot(CLASS_SHAMAN); + else if (botClass == "rogue") m_bot->CreateNPCBot(CLASS_ROGUE); + else { + SendWhisper("Unknown class", fromPlayer); + return; + } + + // m_master->CreateNPCBot(CLASS_PALADIN); + } + else if(text.size() >= 8 && text.substr(0, 8) == "npcbot d") + { + if(m_bot->HaveBot()) + m_bot->SetBotMustDie(); + } + + //equip items + else if(text.size() > 2 && text.substr(0, 2) == "e " || + text.size() > 6 && text.substr(0, 6) == "equip ") + { + std::list<uint32> itemIds; + std::list<Item *> itemList; + extractItemIds(text, itemIds); + findItemsInInv(itemIds, itemList); + for(std::list<Item *>::iterator it = itemList.begin(); it != itemList.end(); ++it) EquipItem(**it); + } + + else if(text == "spells") + { + int loc = m_master->GetSession()->GetSessionDbcLocale(); + + std::ostringstream posOut; + std::ostringstream negOut; + + const std::string ignoreList = ",Opening,Closing,Stuck,Remove Insignia,Opening - No Text,Grovel,Duel,Honorless Target,"; + std::string alreadySeenList = ","; + + for(PlayerSpellMap::iterator itr = m_bot->GetSpellMap().begin(); itr != m_bot->GetSpellMap().end(); ++itr) + { + const uint32 spellId = itr->first; + + if(itr->second->state == PLAYERSPELL_REMOVED || itr->second->disabled || IsPassiveSpell(spellId)) + continue; + + const SpellEntry *const pSpellInfo = sSpellStore.LookupEntry(spellId); + if(!pSpellInfo) + continue; + + //|| name.find("Teleport") != -1 + + std::string comp = ","; + comp.append(pSpellInfo->SpellName[loc]); + comp.append(","); + + if(!(ignoreList.find(comp) == std::string::npos && + alreadySeenList.find(comp) == std::string::npos)) + continue; + + alreadySeenList += pSpellInfo->SpellName[loc]; + alreadySeenList += ","; + + if(IsPositiveSpell(spellId)) + posOut << " |cffffffff|Hspell:" << spellId << "|h[" << pSpellInfo->SpellName[loc] << "]|h|r"; + else + negOut << " |cffffffff|Hspell:" << spellId << "|h[" << pSpellInfo->SpellName[loc] << "]|h|r"; + } + + ChatHandler ch(&fromPlayer); + SendWhisper("Here's my non-attack spells:", fromPlayer); + ch.SendSysMessage(posOut.str().c_str()); + SendWhisper("Here's my attack spells:", fromPlayer); + ch.SendSysMessage(negOut.str().c_str()); + } + + else if (text.size() > 13 && text.substr(0,13) == "accept quest ") + { + uint32 questId = 0; + std::string questStr = text.substr(text.find(" ") + 1); + questStr = questStr.substr(questStr.find(" ") + 1); + std::string questStrLink = questStr; + if (questStr.find("Hquest:")) + { + questStr = questStr.substr(questStr.find("|h[") + 3); + questStr = questStr.substr(0, questStr.find("]")); + } + else + questId = (uint32)atol(questStr.c_str()); + + // try and get quest ID by name + if (questId == 0) + questId = getQuestId(questStr.c_str(), 0); + std::ostringstream out; + out << "Quest " << questId << " " << questStr.c_str(); + + PlayerbotChatHandler ch(m_master); + uint64 oldSel = 0; + if (m_master->GetSelection()) + oldSel = m_master->GetSelection(); + m_master->SetSelection(m_bot->GetGUID()); + if (questId != 0 && ch.acceptQuest(questStrLink.c_str())) + { + out << " accepted."; + TellMaster(out.str()); + } + else + { + out << " not accepted."; + TellMaster(out.str()); + } + if (oldSel > 0) + m_master->SetSelection(oldSel); + } + + else if (text.size() > 14 && text.substr(0,14) == "abandon quest ") + { + uint32 questId = 0; + std::string questStr = text.substr(text.find(" ") + 1); + questStr = questStr.substr(questStr.find(" ") + 1); + std::string questStrLink = questStr; + if (questStr.find("Hquest:")) + { + questStr = questStr.substr(questStr.find("|h[") + 3); + questStr = questStr.substr(0, questStr.find("]")); + } + else + questId = (uint32)atol(questStr.c_str()); + + // try and get quest ID by name + if (questId == 0) + questId = getQuestId(questStr.c_str(), 1); + std::ostringstream out; + out << "Quest " << questId << " " << questStr.c_str(); + + PlayerbotChatHandler ch(m_master); + uint64 oldSel = 0; + if (m_master->GetSelection()) + oldSel = m_master->GetSelection(); + m_master->SetSelection(m_bot->GetGUID()); + if (questId != 0 && ch.abandonQuest(questStrLink.c_str())) + { + out << " abandoned."; + TellMaster(out.str()); + } + else + { + out << " not abandoned."; + TellMaster(out.str()); + } + if (oldSel > 0) + m_master->SetSelection(oldSel); + } + + else if (text.size() == 1 && text.substr(0,1) == "q" || + text == "quests") + { + std::ostringstream out; + for (QuestStatusMap::iterator iter=m_bot->getQuestStatusMap().begin(); iter!=m_bot->getQuestStatusMap().end(); ++iter) + { + const Quest *qInfo = sObjectMgr->GetQuestTemplate(iter->first); + if (!qInfo) + continue; + QuestStatusData *qData = &iter->second; + + uint32 questId = qInfo->GetQuestId(); + const std::string name = qInfo->GetTitle(); + if (name.empty()) + continue; + //out << qData->m_status << " "; + if (qData->m_status == QUEST_STATUS_NONE) + { + //out << "Quest " << " |cffffffff|Hquest:" << questId << "|h[" << name << "]|h|r" << " "; + //out << " no status" << "\n"; + continue; + } + else if (qData->m_status == QUEST_STATUS_COMPLETE) + { + out << "Quest " << " |cffffffff|Hquest:" << questId << "|h[" << name << "]|h|r" << " "; + out << " complete" << "\n"; + } + else if (qData->m_status == QUEST_STATUS_UNAVAILABLE) + { + //out << "Quest " << " |cffffffff|Hquest:" << questId << "|h[" << name << "]|h|r" << " "; + //out << " unavailable" << "\n"; + continue; + } + else if (qData->m_status == QUEST_STATUS_INCOMPLETE) + { + out << "Quest " << " |cffffffff|Hquest:" << questId << "|h[" << name << "]|h|r" << " "; + out << " incomplete" << "\n"; + } + else if (qData->m_status == QUEST_STATUS_AVAILABLE) + { + //out << "Quest " << " |cffffffff|Hquest:" << questId << "|h[" << name << "]|h|r" << " "; + //out << " available" << "\n"; + continue; + } + else if (qData->m_status == QUEST_STATUS_FAILED) + { + out << "Quest " << " |cffffffff|Hquest:" << questId << "|h[" << name << "]|h|r" << " "; + out << " failed" << "\n"; + } + else + { + //out << "Quest " << " |cffffffff|Hquest:" << questId << "|h[" << name << "]|h|r" << " "; + //out << " unknown" << "\n"; + continue; + } + } + if (!out.str().empty()) + TellMaster(out.str()); + } + + else if (text == "train") + { + Unit *unit = m_master->GetSelectedUnit(); + + if (!unit) + { + TellMaster("Please select the trainer which I should learn from!"); + return; + } + if (!unit->isTrainer()) + { + TellMaster("This is not a trainer!"); + return; + } + + Creature *creature = m_bot->GetMap()->GetCreature(m_master->GetSelection()); + if(!creature->isCanTrainingOf(m_bot, false)) + { + TellMaster("This trainer can not train me anything at all!"); + return; + } + + CreatureInfo const *ci = creature->GetCreatureInfo(); + + if (!ci) + { + TellMaster("This trainer can not train me anything at all!"); + return; + } + + TrainerSpellData const* trainer_spells = creature->GetTrainerSpells(); + if(!trainer_spells) + { + TellMaster("No training spells can be found from this trainer"); + return; + } + // reputation discount + float fDiscountMod = m_bot->GetReputationPriceDiscount(creature); + bool can_learn_primary_prof = m_bot->GetFreePrimaryProfessionPoints() > 0; + + std::ostringstream msg; + msg << "I had learnt the following spells:\n"; + uint32 totalCost = 0; + uint32 totalSpellLearnt = 0; + int loc = m_master->GetSession()->GetSessionDbcLocale(); + for (TrainerSpellMap::const_iterator itr = trainer_spells->spellList.begin(); itr != trainer_spells->spellList.end(); ++itr) + { + TrainerSpell const* tSpell = &itr->second; + + bool valid = true; + bool primary_prof_first_rank = false; + for (uint8 i = 0; i < MAX_SPELL_EFFECTS ; ++i) + { + if (!tSpell->learnedSpell[i]) + continue; + if(!m_bot->IsSpellFitByClassAndRace(tSpell->learnedSpell[i])) + { + valid = false; + break; + } + if (sSpellMgr->IsPrimaryProfessionFirstRankSpell(tSpell->learnedSpell[i])) + primary_prof_first_rank = true; + } + if (!valid) + continue; + + TrainerSpellState state = m_bot->GetTrainerSpellState(tSpell); + if (state != TRAINER_SPELL_GREEN) + { + continue; + } + //data << uint32(primary_prof_first_rank && can_learn_primary_prof ? 1 : 0); + uint32 spellId = tSpell->spell; + const SpellEntry *const pSpellInfo = sSpellStore.LookupEntry(spellId); + if (!pSpellInfo) + { + continue; + } + uint32 cost = (uint32) (floor(tSpell->spellCost * fDiscountMod)); + + // check money requirement + if(m_bot->GetMoney() < cost ) + continue; + + m_bot->ModifyMoney( -int32(cost) ); + + // learn explicitly or cast explicitly + if(tSpell->IsCastable()) + //FIXME: prof. spell entry in trainer list not marked gray until list re-open. + m_bot->CastSpell(m_bot,tSpell->spell,true); + else + m_bot->learnSpell(spellId,false); + totalSpellLearnt++; + totalCost = totalCost + cost; + + msg << " |cffffffff|Hspell:" << spellId << "|h[" << pSpellInfo->SpellName[loc] << "]|h|r" << ", "; + uint32 gold = uint32(cost / 10000); + cost -= (gold * 10000); + uint32 silver = uint32(cost / 100); + cost -= (silver * 100); + if (gold > 0) + { + msg << gold << "|TInterface\\Icons\\INV_Misc_Coin_01:16|t"; + } + if (silver > 0) + { + msg << silver << "|TInterface\\Icons\\INV_Misc_Coin_03:16|t"; + } + msg << cost << "|TInterface\\Icons\\INV_Misc_Coin_05:16|t\n"; + } + + uint32 gold = uint32(totalCost / 10000); + totalCost -= (gold * 10000); + uint32 silver = uint32(totalCost / 100); + totalCost -= (silver * 100); + msg << "Total of " << totalSpellLearnt << " spell(s) learnt, "; + if (gold > 0) + { + msg << gold << "|TInterface\\Icons\\INV_Misc_Coin_01:16|t"; + } + if (silver > 0) + { + msg << silver << "|TInterface\\Icons\\INV_Misc_Coin_03:16|t"; + } + msg << totalCost << "|TInterface\\Icons\\INV_Misc_Coin_05:16|t spent."; + + TellMaster(msg.str()); + + m_bot->GetPlayerbotAI()->GetClassAI()->LoadSpells(); + + } + else if (text == "train list") + { + Unit *unit = m_master->GetSelectedUnit(); + + if (!unit) + { + TellMaster("Please select the trainer which I should learn from!"); + return; + } + if (!unit->isTrainer()) + { + TellMaster("This is not a trainer!"); + return; + } + + Creature *creature = m_bot->GetMap()->GetCreature(m_master->GetSelection()); + if(!creature->isCanTrainingOf(m_bot, false)) + { + TellMaster("This trainer can not train me anything at all!"); + return; + } + + CreatureInfo const *ci = creature->GetCreatureInfo(); + + if (!ci) + { + TellMaster("This trainer can not train me anything at all!"); + return; + } + + TrainerSpellData const* trainer_spells = creature->GetTrainerSpells(); + if(!trainer_spells) + { + TellMaster("No training spells can be found from this trainer"); + return; + } + // reputation discount + float fDiscountMod = m_bot->GetReputationPriceDiscount(creature); + bool can_learn_primary_prof = m_bot->GetFreePrimaryProfessionPoints() > 0; + + std::ostringstream msg; + msg << "The spells I can learn and their costs are:\n"; + uint32 totalCost = 0; + int loc = m_master->GetSession()->GetSessionDbcLocale(); + for (TrainerSpellMap::const_iterator itr = trainer_spells->spellList.begin(); itr != trainer_spells->spellList.end(); ++itr) + { + TrainerSpell const* tSpell = &itr->second; + + bool valid = true; + bool primary_prof_first_rank = false; + for (uint8 i = 0; i < MAX_SPELL_EFFECTS ; ++i) + { + if (!tSpell->learnedSpell[i]) + continue; + if(!m_bot->IsSpellFitByClassAndRace(tSpell->learnedSpell[i])) + { + valid = false; + break; + } + if (sSpellMgr->IsPrimaryProfessionFirstRankSpell(tSpell->learnedSpell[i])) + primary_prof_first_rank = true; + } + if (!valid) + continue; + + TrainerSpellState state = m_bot->GetTrainerSpellState(tSpell); + if (state != TRAINER_SPELL_GREEN) + { + continue; + } + //data << uint32(primary_prof_first_rank && can_learn_primary_prof ? 1 : 0); + uint32 spellId = tSpell->spell; + const SpellEntry *const pSpellInfo = sSpellStore.LookupEntry(spellId); + if (!pSpellInfo) + { + continue; + } + uint32 cost = (uint32) (floor(tSpell->spellCost * fDiscountMod)); + totalCost = totalCost + cost; + + uint32 gold = uint32(cost / 10000); + cost -= (gold * 10000); + uint32 silver = uint32(cost / 100); + cost -= (silver * 100); + msg << " |cffffffff|Hspell:" << spellId << "|h[" << pSpellInfo->SpellName[loc] << "]|h|r" << ", "; + if (gold > 0) + { + msg << gold << "|TInterface\\Icons\\INV_Misc_Coin_01:16|t"; + } + if (silver > 0) + { + msg << silver << "|TInterface\\Icons\\INV_Misc_Coin_03:16|t"; + } + msg << cost << "|TInterface\\Icons\\INV_Misc_Coin_05:16|t\n"; + } + uint32 moneyDiff = m_bot->GetMoney() - totalCost; + if (moneyDiff >= 0) + { + uint32 gold = uint32(moneyDiff / 10000); + moneyDiff -= (gold * 10000); + uint32 silver = uint32(moneyDiff / 100); + moneyDiff -= (silver * 100); + if (gold > 0) + { + msg << gold << "|TInterface\\Icons\\INV_Misc_Coin_01:16|t"; + } + if (silver > 0) + { + msg << silver << "|TInterface\\Icons\\INV_Misc_Coin_03:16|t"; + } + msg << moneyDiff << "|TInterface\\Icons\\INV_Misc_Coin_05:16|t left."; + } + else + { + moneyDiff = moneyDiff * (-1); + uint32 gold = uint32(moneyDiff / 10000); + moneyDiff -= (gold * 10000); + uint32 silver = uint32(moneyDiff / 100); + moneyDiff -= (silver * 100); + msg << "I need "; + if (gold > 0) + { + msg << gold << "|TInterface\\Icons\\INV_Misc_Coin_01:16|t"; + } + if (silver > 0) + { + msg << silver << "|TInterface\\Icons\\INV_Misc_Coin_03:16|t"; + } + msg << moneyDiff << "|TInterface\\Icons\\INV_Misc_Coin_05:16|t more to learn all the spells!"; + } + TellMaster(msg.str()); + + } + else if(text.size() >= 4 && text.substr(0, 4) == "sell") + { + Unit *unit = m_master->GetSelectedUnit(); + + if (!unit) + { + TellMaster("Please show me who I should trade with!"); + return; + } + if (!unit->isVendor()) + { + TellMaster("This person does not want to trade with me!"); + return; + } + if (!m_bot->IsInMap((WorldObject*) unit)) + { + TellMaster("I'm too far away to sell items!"); + return; + } + uint32 TotalCost = 0; + uint32 TotalSold = 0; + std::ostringstream report; + + std::list<uint32> itemIds; + std::list<Item *> itemList; + extractItemIds(text, itemIds); + findItemsInInv(itemIds, itemList); + for(std::list<Item *>::iterator it = itemList.begin(); it != itemList.end(); ++it) + { + if ((**it).GetProto()->SellPrice > 0) + { + int32 cost = (**it).GetCount() * (**it).GetProto()->SellPrice; + m_bot->ModifyMoney(cost); + m_bot->MoveItemFromInventory((**it).GetBagSlot(), (**it).GetSlot(), true); + + TotalSold = TotalSold + 1; + TotalCost = TotalCost + cost; + + if ((**it).GetCount() > 0) { + report << "Sold " << (**it).GetCount() << "x"; + report << " |cffffffff|Hitem:" << (**it).GetProto()->ItemId << ":0:0:0:0:0:0:0" << "|h[" << (**it).GetProto()->Name1 << "]|h|r"; + report << " for "; + + uint32 gold = uint32(cost / 10000); + cost -= (gold * 10000); + uint32 silver = uint32(cost / 100); + cost -= (silver * 100); + + if (gold > 0) + { + report << gold << "|TInterface\\Icons\\INV_Misc_Coin_01:16|t"; + } + if (silver > 0) + { + report << silver << "|TInterface\\Icons\\INV_Misc_Coin_03:16|t"; + } + report << cost << "|TInterface\\Icons\\INV_Misc_Coin_05:16|t\n"; + } + } + } + if (TotalSold > 0) { + report << "Sold total " << TotalSold << " item(s) for "; + + uint32 gold = uint32(TotalCost / 10000); + TotalCost -= (gold * 10000); + uint32 silver = uint32(TotalCost / 100); + TotalCost -= (silver * 100); + + if (gold > 0) + { + report << gold << "|TInterface\\Icons\\INV_Misc_Coin_01:16|t"; + } + if (silver > 0) + { + report << silver << "|TInterface\\Icons\\INV_Misc_Coin_03:16|t"; + } + report << TotalCost << "|TInterface\\Icons\\INV_Misc_Coin_05:16|t."; + TellMaster(report.str()); + } + } + + else if (text.size() > 2 && text.substr(0, 2) == "g " || text.size() > 4 && text.substr(0, 4) == "get ") + { + uint32 guid; + float x,y,z; + uint32 entry; + int mapid; + if (extractGOinfo(text, guid, entry, mapid, x, y, z)) + { + sLog->outDebug("find: guid : %u entry : %u x : (%f) y : (%f) z : (%f) mapid : %d",guid, entry, x, y, z, mapid); + m_lootCurrent = MAKE_NEW_GUID(guid, entry, HIGHGUID_GAMEOBJECT); + GameObject *go = m_bot->GetMap()->GetGameObject(m_lootCurrent); + if (!go) + { + m_bot->Say("I can't find it.", LANG_UNIVERSAL); + m_lootCurrent = 0; + return; + } + + if ( !go->isSpawned() ) { + m_bot->Say("It is not there anymore.", LANG_UNIVERSAL); + return; + } + + m_bot->UpdateGroundPositionZ(x,y,z); + m_bot->GetMotionMaster()->MovePoint( mapid, x, y, z ); + m_bot->SetPosition(x, y, z, m_bot->GetOrientation()); + m_bot->SendLoot( m_lootCurrent, LOOT_CORPSE ); + Loot *loot = &go->loot; + uint32 lootNum = loot->GetMaxSlotInLootFor( m_bot ); + + sLog->outDebug( "[PlayerbotAI]: GetGOType %u - %s looting: '%s' got %d items", go->GetGoType(), m_bot->GetName(), go->GetGOInfo()->name, loot->GetMaxSlotInLootFor( m_bot )); + for ( uint32 l=0; l<lootNum; l++ ) + { + QuestItem *qitem=0, *ffaitem=0, *conditem=0; + LootItem *item = loot->LootItemInSlot( l, m_bot, &qitem, &ffaitem, &conditem ); + if ( !item ) + continue; + + if ( !qitem && item->is_blocked ) + { + m_bot->SendLootRelease( m_lootCurrent ); + continue; + } + + if ( m_needItemList[item->itemid]>0 ) + { + ItemPosCountVec dest; + if ( m_bot->CanStoreNewItem( NULL_BAG, NULL_SLOT, dest, item->itemid, item->count ) == EQUIP_ERR_OK ) + { + Item * newitem = m_bot->StoreNewItem( dest, item->itemid, true, item->randomPropertyId); + if ( qitem ) + { + qitem->is_looted = true; + if ( item->freeforall || loot->GetPlayerQuestItems().size() == 1 ) + m_bot->SendNotifyLootItemRemoved( l ); + else + loot->NotifyQuestItemRemoved( qitem->index ); + } + else + { + if ( ffaitem ) + { + ffaitem->is_looted=true; + m_bot->SendNotifyLootItemRemoved( l ); + } + else + { + if ( conditem ) + conditem->is_looted=true; + loot->NotifyItemRemoved( l ); + } + } + if (!item->freeforall) + item->is_looted = true; + --loot->unlootedCount; + m_bot->SendNewItem( newitem, uint32(item->count), false, false, true ); + + m_bot->GetAchievementMgr().UpdateAchievementCriteria( ACHIEVEMENT_CRITERIA_TYPE_LOOT_ITEM, item->itemid, item->count ); + } + } + uint32 lockId = go->GetGOInfo()->GetLockId(); + LockEntry const *lockInfo = sLockStore.LookupEntry(lockId); + if(lockInfo) + { + uint32 skillId = SkillByLockType(LockType(lockInfo->Index[0])); + switch(skillId) + { + case SKILL_MINING: + if (m_bot->HasSkill(SKILL_MINING) && HasPick()) // Has skill & suitable pick + { + ItemPosCountVec dest; + if ( m_bot->CanStoreNewItem( NULL_BAG, NULL_SLOT, dest, item->itemid, item->count) == EQUIP_ERR_OK ) + { + Item* pItem = m_bot->StoreNewItem (dest,item->itemid,true,item->randomPropertyId); + uint32 reqSkillValue = lockInfo->Skill[0]; + uint32 SkillValue = m_bot->GetPureSkillValue(SKILL_MINING); + if (SkillValue >= reqSkillValue) + { + m_bot->SendNewItem(pItem, uint32(item->count), false, false, true); + m_bot->UpdateGatherSkill(SKILL_MINING, SkillValue, reqSkillValue); + --loot->unlootedCount; + } + } + } + break; + case SKILL_HERBALISM: + if (m_bot->HasSkill(SKILL_HERBALISM)) // Has skill + { + ItemPosCountVec dest; + if ( m_bot->CanStoreNewItem( NULL_BAG, NULL_SLOT, dest, item->itemid, item->count) == EQUIP_ERR_OK ) + + { + Item* pItem = m_bot->StoreNewItem (dest,item->itemid,true,item->randomPropertyId); + uint32 reqSkillValue = lockInfo->Skill[0]; + uint32 SkillValue = m_bot->GetPureSkillValue(SKILL_HERBALISM); + if (SkillValue >= reqSkillValue) + { + m_bot->SendNewItem(pItem, uint32(item->count), false, false, true); + m_bot->UpdateGatherSkill(SKILL_HERBALISM, SkillValue, reqSkillValue); + --loot->unlootedCount; + } + } + } + break; + } + } + } + // release loot + m_bot->GetSession()->DoLootRelease( m_lootCurrent ); + + // clear movement target, take next target on next update + m_bot->GetMotionMaster()->Clear(); + m_bot->GetMotionMaster()->MoveIdle(); + sLog->outDebug( "[PlayerbotAI]: %s looted target 0x%08X", m_bot->GetName(), m_lootCurrent ); + SetQuestNeedItems(); + } + else + SendWhisper("I have no info on that object", fromPlayer); + } + + else if (text == "survey") + { + float distance = 100.0f; + uint32 count = 0; + std::ostringstream detectout; + + QueryResult result = WorldDatabase.PQuery("SELECT guid, id, position_x, position_y, position_z, map, " + "(POW(position_x - '%f', 2) + POW(position_y - '%f', 2) + POW(position_z - '%f', 2)) AS order_ " + "FROM gameobject WHERE map='%u' AND (POW(position_x - '%f', 2) + POW(position_y - '%f', 2) + POW(position_z - '%f', 2)) <= '%f' ORDER BY order_", + m_bot->GetPositionX(), m_bot->GetPositionY(), m_bot->GetPositionZ(), + m_bot->GetMapId(), m_bot->GetPositionX(), m_bot->GetPositionY(), m_bot->GetPositionZ(), distance*distance); + + if (result) + { + do + { + Field *fields = result->Fetch(); + uint32 guid = fields[0].GetUInt32(); + uint32 entry = fields[1].GetUInt32(); + float x = fields[2].GetFloat(); + float y = fields[3].GetFloat(); + float z = fields[4].GetFloat(); + int mapid = fields[5].GetUInt16(); + + GameObjectInfo const * gInfo = ObjectMgr::GetGameObjectInfo(entry); + + if(!gInfo) + continue; + + uint64 objGuid = MAKE_NEW_GUID(guid, entry, HIGHGUID_GAMEOBJECT); + GameObject *go = m_bot->GetMap()->GetGameObject(objGuid); + if ( !go || (go && !go->isSpawned()) ) continue; + + if(count < 12) // count, limits number of links + detectout << "|cFFFFFF00|Hfound:" << guid << ":" << entry << ":" << x << ":" << y << ":" << z << ":" << mapid << ":" << "|h[" << gInfo->name << "]|h|r"; + ++count; + } while (result->NextRow()); + + } + SendWhisper(detectout.str().c_str(), fromPlayer); + } + + else if (text == "pull") + { + m_bot->GetPlayerbotAI()->GetClassAI()->Pull(); + } + else if(text == "help") + { + std::string msg; + if (m_bot->getClass() == CLASS_ROGUE) + msg = "The commands I respond to are \n follow, stay, (c)ast <spellname>, spells, (e)quip, (u)se, \nnpcbot (a)dd, npcbot (d)elete <class>,\n(q)uests, accept quest <hlink>, abandon quest <hlink>\npoison [main | off].\ntrain list"; + else + msg = "The commands I respond to are \n follow, stay, (c)ast <spellname>, spells, (e)quip, (u)se, \nnpcbot (a)dd, npcbot (d)elete <class>,\n(q)uests, accept quest <hlink>, abandon quest <hlink>\ntrain list."; + + SendWhisper(msg, fromPlayer); + m_bot->HandleEmoteCommand(EMOTE_ONESHOT_TALK); + } + else + { + // if this looks like an item link, reward item it completed quest and talking to NPC + std::list<uint32> itemIds; + extractItemIds(text, itemIds); + if (!itemIds.empty()) { + uint32 itemId = itemIds.front(); + bool wasRewarded = false; + uint64 questRewarderGUID = m_bot->GetSelection(); + Object* const pNpc = ObjectAccessor::GetObjectByTypeMask(*m_bot, questRewarderGUID, TYPEMASK_UNIT|TYPEMASK_GAMEOBJECT); + if (!pNpc) + return; + + QuestMenu& questMenu = m_bot->PlayerTalkClass->GetQuestMenu(); + for (uint32 iI = 0; !wasRewarded && iI < questMenu.MenuItemCount(); ++iI) + { + QuestMenuItem const& qItem = questMenu.GetItem(iI); + + uint32 questID = qItem.m_qId; + Quest const* pQuest = sObjectMgr->GetQuestTemplate(questID); + QuestStatus status = m_bot->GetQuestStatus(questID); + + // if quest is complete, turn it in + if (status == QUEST_STATUS_COMPLETE && + ! m_bot->GetQuestRewardStatus(questID) && + pQuest->GetRewChoiceItemsCount() > 1 && + m_bot->CanRewardQuest(pQuest, false)) + { + for (uint8 rewardIdx=0; !wasRewarded && rewardIdx < pQuest->GetRewChoiceItemsCount(); ++rewardIdx) + { + ItemPrototype const * const pRewardItem = sObjectMgr->GetItemPrototype(pQuest->RewChoiceItemId[rewardIdx]); + if (itemId == pRewardItem->ItemId) + { + m_bot->RewardQuest(pQuest, rewardIdx, pNpc, false); + + std::string questTitle = pQuest->GetTitle(); + m_bot->GetPlayerbotAI()->QuestLocalization(questTitle, questID); + std::string itemName = pRewardItem->Name1; + m_bot->GetPlayerbotAI()->ItemLocalization(itemName, pRewardItem->ItemId); + + std::ostringstream out; + out << "|cffffffff|Hitem:" << pRewardItem->ItemId << ":0:0:0:0:0:0:0" << "|h[" << itemName << "]|h|r rewarded"; + SendWhisper(out.str(), fromPlayer); + wasRewarded = true; + } + } + } + } + } + } + +} + +void PlayerbotAI::SetLooting(bool looting) +{ + isLooting = looting; +} + +void PlayerbotAI::AddLootGUID(uint64 guid) { + m_lootCreature.push_back(guid); +} + +bool PlayerbotAI::DoLoot() +{ + if(!m_lootCurrent && m_lootCreature.empty()) + { + sLog->outDebug("[PlayerbotAI]: %s reset loot list / go back to idle", m_bot->GetName()); +// SetQuestNeedItems(); + isLooting = false; + return false; + } + + if(m_bot->isDead()) + { + isLooting = false; + return false; + } + + if(!m_lootCurrent) + { + m_lootCurrent = m_lootCreature.front(); + + if(!m_lootCurrent) + { + //sLog->outError("PlayerbotAI::DoLoot() error location #1, please report this error immediately!"); + return false; + } + + Creature *c = m_bot->GetMap()->GetCreature(m_lootCurrent); + GameObject *o = m_bot->GetMap()->GetGameObject( m_lootCurrent ); + + if(!c && !o) + { + m_lootCurrent = 0; + isLooting = false; + return false; + } + if(c && c->isAlive()) + { + m_lootCurrent = 0; + isLooting = false; + return false; //not dead yet + } + + sLog->outDebug("[PlayerbotAI]: %s got loot target 0x%08X", m_bot->GetName(), m_lootCurrent); + Position pos; + + WorldObject *object; + if (c) object = c; + else object = o; + + if(m_bot->IsWithinDistInMap(object, INTERACTION_DISTANCE * 3)){ //Verify if the bot it close to a loot. + + m_lootCreature.pop_front(); + object->GetPosition(&pos); + m_bot->GetMotionMaster()->MovePoint(object->GetMapId(), (const Position &)(pos)); + + } else { //Rotate the loot to very if one is not near the bot. + + m_lootobjtemp = m_lootCreature.front(); + m_lootCreature.pop_front(); + m_lootCreature.push_back(m_lootobjtemp); + m_lootCurrent = 0; + } + return true; + + } else { + + Creature *c = m_bot->GetMap()->GetCreature(m_lootCurrent); + GameObject *o = m_bot->GetMap()->GetGameObject( m_lootCurrent ); + if(!c && !o) + { + m_lootCurrent = 0; + isLooting = false; + return false; + } + if(c && c->isAlive()) + { + m_lootCurrent = 0; + isLooting = false; + return false; //not dead yet + } + + WorldObject *object; + if (c) object = c; + else object = o; + + if(m_bot->IsWithinDistInMap(object, INTERACTION_DISTANCE)) + { + //check for needed items + m_bot->SendLoot(m_lootCurrent, LOOT_CORPSE); + + Loot *loot; + if (c) + loot = &c->loot; + else + loot = &o->loot; + + assert(loot); + + uint32 lootNum = loot->GetMaxSlotInLootFor(m_bot); +//sLog->outError("[PlayerbotAI]: %s loot target 0x%08X got %d items", m_bot->GetName(), m_lootCurrent, loot->GetMaxSlotInLootFor(m_bot)); + + for(uint32 l = 0; l < lootNum; ++l) + { + QuestItem *qitem = 0, *ffaitem = 0, *conditem = 0; + LootItem *item = loot->LootItemInSlot(l, m_bot, &qitem, &ffaitem, &conditem); + if(!item) continue; + if(!qitem && item->is_blocked) + { + m_bot->SendLootRelease(m_bot->GetLootGUID()); + continue; + } + + if(m_needItemList[item->itemid] > 0) + { +//sLog->outError("[PlayerbotAI]: %s LOOT needed item 0x%04X", m_bot->GetName(), item->itemid); + ItemPosCountVec dest; + if(m_bot->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, item->itemid, item->count) == EQUIP_ERR_OK) + { + Item *newitem = m_bot->StoreNewItem(dest, item->itemid,true, item->randomPropertyId); + if(qitem) + { + qitem->is_looted = true; + if(item->freeforall ||loot->GetPlayerQuestItems().size() == 1) + m_bot->SendNotifyLootItemRemoved(l); + else + loot->NotifyQuestItemRemoved(qitem->index); + } + else if(ffaitem) + { + ffaitem->is_looted = true; + m_bot->SendNotifyLootItemRemoved(l); + } else { + if(conditem) conditem->is_looted = true; + loot->NotifyItemRemoved(l); + } + if(!item->freeforall) item->is_looted = true; + --(loot->unlootedCount); + m_bot->SendNewItem(newitem, uint32(item->count), false,false, true); + m_bot->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LOOT_ITEM, item->itemid, item->count); + + std::ostringstream out; + PlayerbotChatHandler ch(m_master); + out << m_bot->GetName() << " needs " << m_needItemList[item->itemid]-1 << " more."; + ch.sysmessage(out.str().c_str()); + m_ignoreAIUpdatesUntilTime = time(0); + } + } + } + //release loot + if(uint64 lguid = m_bot->GetLootGUID() && m_bot->GetSession()) + m_bot->GetSession()->DoLootRelease(lguid); + else if(!m_bot->GetSession()) + sLog->outDebug("[PlayerbotAI]: %s has no session. Cannot releaseloot!", m_bot->GetName()); + + //clear movement target, take next target on next update + m_bot->GetMotionMaster()->Clear(); + m_bot->GetMotionMaster()->MoveIdle(); + sLog->outDebug("[PlayerbotAI]: %s looted target 0x%08X", m_bot->GetName(), m_lootCurrent); + m_lootCurrent = 0; + m_ignoreAIUpdatesUntilTime = time(0); + isLooting = false; + } else { //keep moving till we get there + Position pos; + + if (c) { + c->GetPosition(&pos); + m_bot->GetMotionMaster()->MovePoint(c->GetMapId(), (const Position &)(pos)); + } else { + o->GetPosition(&pos); + m_bot->GetMotionMaster()->MovePoint(o->GetMapId(), (const Position &)(pos)); + } + + } + } + return false; +} //end DoLoot + +void PlayerbotAI::SetQuestNeedItems() +{ + //reset values first + m_needItemList.clear(); + m_lootCreature.clear(); + m_lootCurrent = 0; + + //run through accepted quests, get quest infoand data + for(QuestStatusMap::iterator iter=m_bot->getQuestStatusMap().begin(); iter!=m_bot->getQuestStatusMap().end(); ++iter) + { + const Quest *qInfo = sObjectMgr->GetQuestTemplate(iter->first); + if(!qInfo) continue; + QuestStatusData *qData = &iter->second; + + //only check quest if it is incomplete + if(qData->m_status != QUEST_STATUS_INCOMPLETE) continue; + + //check for items we not have enough of + for(uint8 i = 0; i < QUEST_OBJECTIVES_COUNT; i++) + { + if(!qInfo->ReqItemCount[i] || (qInfo->ReqItemCount[i]-qData->m_itemcount[i]) <= 0) continue; + m_needItemList[qInfo->ReqItemId[i]] = (qInfo->ReqItemCount[i]-qData->m_itemcount[i]); + } + } +}//end SetQuestNeedItems + +//Localization support +void PlayerbotAI::ItemLocalization(std::string &itemName, const uint32 itemID) const +{ + int loc = m_master->GetSession()->GetSessionDbLocaleIndex(); + std::wstring wnamepart; + ItemLocale const *pItemInfo = sObjectMgr->GetItemLocale(itemID); + if(pItemInfo) + { + if(pItemInfo->Name.size() > loc && !pItemInfo->Name[loc].empty()) + { + const std::string name = pItemInfo->Name[loc]; + if(Utf8FitTo(name, wnamepart)) + itemName = name.c_str(); + } + } +} + +void PlayerbotAI::QuestLocalization(std::string &questTitle, const uint32 questID) const +{ + int loc = m_master->GetSession()->GetSessionDbLocaleIndex(); + std::wstring wnamepart; + QuestLocale const *pQuestInfo = sObjectMgr->GetQuestLocale(questID); + if(pQuestInfo) + { + if(pQuestInfo->Title.size() > loc && !pQuestInfo->Title[loc].empty()) + { + const std::string title = pQuestInfo->Title[loc]; + if(Utf8FitTo(title, wnamepart)) + questTitle = title.c_str(); + } + } +} + +void PlayerbotAI::TurnInQuests( WorldObject *pNpc ) +{ + + uint64 npcGUID = pNpc->GetGUID(); + if (!m_bot->IsInMap((WorldObject*) pNpc)) + m_bot->GetPlayerbotAI()->TellMaster("hey you are turning in quests without me!"); + else + { + m_bot->SetSelection(npcGUID); + + // auto complete every completed quest this NPC has + m_bot->PrepareQuestMenu(npcGUID); + QuestMenu& questMenu = m_bot->PlayerTalkClass->GetQuestMenu(); + for (uint32 iI = 0; iI < questMenu.MenuItemCount(); ++iI) + { + QuestMenuItem const& qItem = questMenu.GetItem(iI); + uint32 questID = qItem.m_qId; + Quest const* pQuest = sObjectMgr->GetQuestTemplate(questID); + + std::ostringstream out; + std::string questTitle = pQuest->GetTitle(); + m_bot->GetPlayerbotAI()->QuestLocalization(questTitle, questID); + + QuestStatus status = m_bot->GetQuestStatus(questID); + // if quest is complete, turn it in + if (status == QUEST_STATUS_COMPLETE) + { + // if bot hasn't already turned quest in + if (! m_bot->GetQuestRewardStatus(questID)) + { + // auto reward quest if no choice in reward + if (pQuest->GetRewChoiceItemsCount() == 0) + { + if (m_bot->CanRewardQuest(pQuest, false)) + { + m_bot->RewardQuest(pQuest, 0, pNpc, false); + out << "Quest complete: |cff808080|Hquest:" << questID << ':' << pQuest->GetQuestLevel() << "|h[" << questTitle << "]|h|r"; + } + else + { + out << "|cffff0000Unable to turn quest in:|r |cff808080|Hquest:" << questID << ':' << pQuest->GetQuestLevel() << "|h[" << questTitle << "]|h|r"; + } + } + + // auto reward quest if one item as reward + else if (pQuest->GetRewChoiceItemsCount() == 1) + { + int rewardIdx = 0; + ItemPrototype const *pRewardItem = sObjectMgr->GetItemPrototype(pQuest->RewChoiceItemId[rewardIdx]); + std::string itemName = pRewardItem->Name1; + m_bot->GetPlayerbotAI()->ItemLocalization(itemName, pRewardItem->ItemId); + if (m_bot->CanRewardQuest(pQuest, rewardIdx, false)) + { + m_bot->RewardQuest(pQuest, rewardIdx, pNpc, true); + + std::string itemName = pRewardItem->Name1; + m_bot->GetPlayerbotAI()->ItemLocalization(itemName, pRewardItem->ItemId); + + out << "Quest complete: " + << " |cff808080|Hquest:" << questID << ':' << pQuest->GetQuestLevel() + << "|h[" << questTitle << "]|h|r reward: |cffffffff|Hitem:" + << pRewardItem->ItemId << ":0:0:0:0:0:0:0" << "|h[" << itemName << "]|h|r"; + } + else + { + out << "|cffff0000Unable to turn quest in:|r " + << "|cff808080|Hquest:" << questID << ':' + << pQuest->GetQuestLevel() << "|h[" << questTitle << "]|h|r" + << " reward: |cffffffff|Hitem:" + << pRewardItem->ItemId << ":0:0:0:0:0:0:0" << "|h[" << itemName << "]|h|r"; + } + } + + // else multiple rewards - let master pick + else { + out << "What reward should I take for |cff808080|Hquest:" << questID << ':' << pQuest->GetQuestLevel() + << "|h[" << questTitle << "]|h|r? "; + for (uint8 i=0; i < pQuest->GetRewChoiceItemsCount(); ++i) + { + ItemPrototype const * const pRewardItem = sObjectMgr->GetItemPrototype(pQuest->RewChoiceItemId[i]); + std::string itemName = pRewardItem->Name1; + m_bot->GetPlayerbotAI()->ItemLocalization(itemName, pRewardItem->ItemId); + out << "|cffffffff|Hitem:" << pRewardItem->ItemId << ":0:0:0:0:0:0:0" << "|h[" << itemName << "]|h|r"; + } + } + } + } + + else if (status == QUEST_STATUS_INCOMPLETE) { + out << "|cffff0000Quest incomplete:|r " + << " |cff808080|Hquest:" << questID << ':' << pQuest->GetQuestLevel() << "|h[" << questTitle << "]|h|r"; + } + +// else if (status == QUEST_STATUS_AVAILABLE){ + // out << "|cff00ff00Quest available:|r " +// << " |cff808080|Hquest:" << questID << ':' << pQuest->GetQuestLevel() << "|h[" << questTitle << "]|h|r"; +// } + + if (! out.str().empty()) + m_bot->GetPlayerbotAI()->TellMaster(out.str()); + } + } +} // TurnInQuests + + + void PlayerbotAI::SetCombatOrder (CombatOrderType orders) + { + m_combatOrder = orders; + } diff --git a/src/server/game/AI/Bots/PlayerbotAI.h b/src/server/game/AI/Bots/PlayerbotAI.h new file mode 100644 index 0000000..e40d6ad --- /dev/null +++ b/src/server/game/AI/Bots/PlayerbotAI.h @@ -0,0 +1,284 @@ +#ifndef _PLAYERBOTAI_H +#define _PLAYERBOTAI_H + +#include "Common.h" + + +class WorldPacket; +class Player; +class Unit; +class Object; +class Item; +class PlayerbotClassAI; + +enum ScenarioType +{ + SCENARIO_PVEEASY, + SCENARIO_PVEHARD, + SCENARIO_DUEL, + SCENARIO_PVPEASY, + SCENARIO_PVPHARD +}; + +//masters orders that should be obeyed by the AI during the updteAI routine +//the master will auto set the target of the bot +enum CombatOrderType +{ + ORDERS_NONE, + ORDERS_KILL, + ORDERS_CC, + ORDERS_HEAL, + ORDERS_TANK, + ORDERS_PROTECT, + ORDERS_REGEN +}; + +typedef std::set<Unit *> AttackerInfoList; + +class PlayerbotAI +{ +public: + //******* Stuff the outside world calls **************************** + PlayerbotAI(Player *const master, Player *const bot); + virtual ~PlayerbotAI(); + + //This is called from Unit.cpp and is called every second (I think) + void UpdateAI(const uint32 p_time); + + //This is called from ChatHandler.cpp when there is an incoming message to the bot + //from a whisper or from the party channel + void HandleCommand(const std::string &text, Player &fromPlayer); + + //This is called by WorldSession.pm + //It provides a view of packets normally sent to the client. + //Since there is no client at the other end, the packets are dropped of course. + //For a list of opcodes that can be caught see Opcodes.cpp (SMSG_* opcodes only) + void HandleBotOutgoingPacket(const WorldPacket &packet); + + //This is called whenever the master sends a packet to the server. + //These packets can be viewed, but not edited. + //It allows bot creators to craft AI in response to a master's actions. + //For a list of opcodes that can be caught see Opcodes.cpp (CMSG_* opcodes only) + //Notice: that this is static which means it is called once for all bots of the master. + static void HandleMasterIncomingPacket(const WorldPacket &packet, WorldSession &masterSession); + static void HandleMasterOutgoingPacket(const WorldPacket &packet, WorldSession &masterSession); + + //Returns what kind of situation we are in so the AI can react accordingly + ScenarioType GetScenarioType(){ return m_ScenarioType; } + + PlayerbotClassAI *GetClassAI(){ return m_classAI; } + + //protected: + + //******* Utilities *************************************************** + + //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; + //Main PlayerBot spell finding function Returns ONLY exact matches including Upper/Lower case differentiation. + uint32 getSpellIdExact(const char *args, bool includePassive=false, bool master=false); + + // finds quest ID for matching substring args + uint32 getQuestId(const char* args, bool remove) const; + + //extracts item ids from links + void extractItemIds(const std::string &text, std::list<uint32> &itemIds) const; + + //extracts currency from a string as #g#s#c and returns the total in copper + uint32 extractMoney(const std::string &text) const; + + // extracts gameobject info from link + bool extractGOinfo(const std::string& text, uint32 &guid, uint32 &entry, int &mapid, float &x, float &y, float &z) const; + + // finds items in equipment and adds Item* to foundItemList + // also removes found item IDs from itemIdSearchList when found + 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; + + //currently bots only obey commands from the master + bool canObeyCommandFrom(const Player &player) const; + + //get current casting spell (will return NULL if no spell!) + Spell *GetCurrentSpell() const; + + bool HasAura(uint32 spellId, const Unit *player) const; + bool HasAura(const char *spellName, const Unit *player) const; + bool HasAura(const char *spellName) const; + void HandleTeleportAck(); + + bool HasPick(); + + uint8 GetHealthPercent(const Unit &target) const; + uint8 GetHealthPercent() const; + uint8 GetBaseManaPercent(const Unit &target) const; + uint8 GetBaseManaPercent() const; + uint8 GetManaPercent(const Unit &target) const; + uint8 GetManaPercent() const; + uint8 GetRageAmount(const Unit &target) const; + uint8 GetRageAmount() const; + uint8 GetEnergyAmount(const Unit &target) const; + uint8 GetEnergyAmount() const; + uint8 GetRunicPower(const Unit &target) const; + uint8 GetRunicPower() const; + + Item *FindFood() const; + Item *FindDrink() const; + Item *FindPotion() const; + Item *FindBandage() const; + Item *FindPoisonForward() const; // finds poison starting from the front + Item *FindPoisonBackward() const; // finds poison starting from the back + + void UseMount() const; + + //******* Actions **************************************** + //Your handlers can call these actions to make the bot do things. + void TellMaster(const std::string &text); + void SendWhisper(const std::string &text, Player &player); + bool CastSpell(const char *args); + //Player bots main spell cast function, if checkFirst > performs canCast() first, if castExistingAura > performs the cast even if the aura exists on target, + //if skipFriendlyCheck > do not perform spell positive/negative and target friendly/hostile checks (Useful for dual purpose spells like holy shock) + virtual bool CastSpell(uint32 spellId, Unit *target=NULL, bool checkFirst=true, bool castExistingAura=false, bool skipFriendlyCheck=false, bool skipEquipStanceCheck=false, bool triggered=false); + virtual bool CastSpell(const SpellEntry * pSpellInfo, Unit *target=NULL, bool checkFirst=true, bool castExistingAura=false, bool skipFriendlyCheck=false, bool skipEquipStanceCheck=false, bool triggered=false); + //Simple Checks to determine if the bot can cast the spell or not... + //Mana/Stance/EquipmentRequirement/Distance/TargetInFront/OverwriteOrStackExistingAura/FriendlyFire checks + virtual bool CanCast(uint32 spellId, Unit *target=NULL,bool castExistingAura=false, bool skipFriendlyCheck=false, bool skipEquipStanceCheck=false); + virtual bool CanCast(const SpellEntry * pSpellInfo, Unit *target=NULL,bool castExistingAura=false, bool skipFriendlyCheck=false, bool skipEquipStanceCheck=false); + //Gets the current form/stance of player + uint8 GetForm(Unit *pPlayer=NULL); + void UseItem(Item &item); + void PoisonWeapon(Item &item, uint32 spellid=0, uint32 target=0, EquipmentSlots weapon=EQUIPMENT_SLOT_MAINHAND); + void EquipItem(Item &item); + void Stay(); + bool Follow(Player &player); + void SendNotEquipList(Player &player); + void Feast(); + void SetLooting(bool looting); + void InterruptCurrentCastingSpell(); + void GetCombatOrders(); + void DoNextCombatManeuver(); + void KilledMonster(uint32 entry, uint64 guid); + void ItemLocalization(std::string &itemName, const uint32 itemID) const; + void QuestLocalization(std::string &questTitle, const uint32 questID) const; + + uint32 GetAttackerCount(){ return m_attackerInfo.size(); } + void SetIgnoreUpdateTime(float t){m_ignoreAIUpdatesUntilTime=getMSTime() + (t * 1000); }; + + Player *GetPlayerBot(){ return m_bot; } + void SetInFront(const Unit *obj); + + bool CanBotsFly(); // take the flight path? + uint32 GetStartMapID() { return m_startMapID; }; + uint32 GetStartZoneID() { return m_startZoneID; }; + uint32 GetStartAreaID() { return m_startAreaID; }; + uint32 GetStartPhase() { return m_startPhase; }; + uint32 GetStartDifficulty() { return m_startDifficulty; }; + uint32 GetStartInstanceID() { return m_startInstanceID; }; + float GetStartX() { return m_startX; }; + float GetStartY() { return m_startY; }; + float GetStartZ() { return m_startZ; }; + float GetStartO() { return m_startO; }; + + void SetStartMapID(uint32 mapID) { m_startMapID = mapID; }; + void SetStartZoneID(uint32 zoneID) { m_startZoneID = zoneID; }; + void SetStartAreaID(uint32 areaID) { m_startAreaID = areaID; }; + void SetStartPhase(uint32 phase) { m_startPhase = phase; }; + void SetStartDifficulty(uint32 difficulty) { m_startDifficulty = difficulty; }; + void SetStartInstanceID(uint32 instanceID) { m_startInstanceID = instanceID; }; + void SetStartX(float x) { m_startX = x; }; + void SetStartY(float y) { m_startY = y; }; + void SetStartZ(float z) { m_startZ = z; }; + void SetStartO(float o) { m_startO = o; }; + + void AddLootGUID(uint64 guid); + void SetCombatOrder (CombatOrderType orders); + +private: + + //****** Closed Actions ******************************** + //These actions may only be called at special times. + //Trade methods are only applicable when the trade window is open + //and are only called from within HandleCommand. + // submits packet to trade an item (trade window must already be open) + + // default slot is -1 which means trade slots 0 to 5. if slot is set + // to TRADE_SLOT_NONTRADED (which is slot 6) item will be shown in the + // 'Will not be traded' slot. + bool TradeItem(const Item& item, int8 slot=-1); + + bool TradeCopper(uint32 copper); + + //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. + Player *const m_master; + Player *const m_bot; + PlayerbotClassAI *m_classAI; + + //ignores AI updates until time specified + //no need to waste CPU cycles during casting etc + uint32 m_ignoreAIUpdatesUntilTime; + + CombatOrderType m_combatOrder; + + ScenarioType m_ScenarioType; + typedef std::set<Unit *> AttackerSet; + + time_t m_TimeDoneEating; + time_t m_TimeDoneDrinking; + time_t m_TimeRessurect; + uint32 m_CurrentlyCastingSpellId; + bool m_IsFollowingMaster; + + //if master commands bot to do something, store here until updateAI + //can do it + uint32 m_spellIdCommand; + uint64 m_targetGuidCommand; + + //finds who to attack next + Unit *getNextTarget(Unit *victim); + + /* -- Loot routines by runsttren */ + bool DoLoot(); + void SetQuestNeedItems(); + + void TurnInQuests( WorldObject *questgiver ); + + typedef std::map<uint32, uint32> BotNeedItem; + typedef std::list<uint64> BotLootCreature; + typedef std::map<uint32, std::string> BotQuestsSeen; + + //list of items needed to fullfill quests + BotNeedItem m_needItemList; + + //list of items needed to fullfill quests + BotNeedItem m_needEmblemList; + + //list of quests recently seen that we can accept + BotQuestsSeen m_questsSeen; + + //list of creatures we recently attacked and want to loot + BotLootCreature m_lootCreature; //list of creatures + uint64 m_lootCurrent; //current remains of interest + uint64 m_lootobjtemp; + bool isLooting; + AttackerInfoList m_attackerInfo; + + float m_followDistanceMin, m_followDistanceMax; + int m_playerBotsFly; + + uint32 m_startMapID; + uint32 m_startZoneID; + uint32 m_startAreaID; + uint32 m_startPhase; + uint32 m_startDifficulty; + uint32 m_startInstanceID; + float m_startX; + float m_startY; + float m_startZ; + float m_startO; + + uint32 m_FeastSpamTimer; +}; + +#endif diff --git a/src/server/game/AI/Bots/PlayerbotClassAI.cpp b/src/server/game/AI/Bots/PlayerbotClassAI.cpp new file mode 100644 index 0000000..6ac9f55 --- /dev/null +++ b/src/server/game/AI/Bots/PlayerbotClassAI.cpp @@ -0,0 +1,592 @@ +/* +Name : PlayerbotClassAI.cpp +Notes: Does not really work with peldor's own classbot AIs + Contains many improvements and hacks to overcome some difficulites +Known +Problems: - Contains hardcoded values, for an example check group heal, individual heal decision + - ai->getSpellIdExact func, *although works more accurately* is probably slower and hackish + - FindMainTankRaid func, includes a db query making it a resource hog + - canCast func, does not check for every possible problem, can cause AI stuck.. Should be inside PlayerbotAI class + - castSpell func is redundant and should be placed in PlayerbotAI class, sets private variable m_ai->m_CurrentlyCastingSpellId which is made public as a hack.. + +Authors : SwaLLoweD +Version : 0.40 +*/ + +#include "PlayerbotClassAI.h" +#include "Common.h" +#include "Spell.h" +#include "Group.h" + +PlayerbotClassAI::PlayerbotClassAI(Player *const master, Player *const bot, PlayerbotAI *const ai): m_master(master), m_bot(bot), m_ai(ai), rezSpamTimer(0) +{ + threatThreshold = 75; // Threat % threshold for dps to lower tps + offensiveSpellThreshold = 70; // Mana % threshold for healers to use offensive spells + + // first aid + RECENTLY_BANDAGED = 11196; // first aid check + + // RACIALS + R_ARCANE_TORRENT = ai->getSpellIdExact("Arcane Torrent"); + R_BERSERKING = ai->getSpellIdExact("Berserking"); + R_BLOOD_FURY = ai->getSpellIdExact("Blood Fury"); + R_CANNIBALIZE = ai->getSpellIdExact("Cannibalize"); + R_ESCAPE_ARTIST = ai->getSpellIdExact("Escape Artist"); + R_EVERY_MAN_FOR_HIMSELF = ai->getSpellIdExact("Every Man for Himself"); + R_GIFT_OF_NAARU = ai->getSpellIdExact("Gift of the Naaru"); + R_SHADOWMELD = ai->getSpellIdExact("Shadowmeld"); + R_STONEFORM = ai->getSpellIdExact("Stoneform"); + R_WAR_STOMP = ai->getSpellIdExact("War Stomp"); + R_WILL_OF_FORSAKEN = ai->getSpellIdExact("Will of the Forsaken"); + + mainTank = NULL; + m_pulling = false; +} +PlayerbotClassAI::~PlayerbotClassAI(){} + +void PlayerbotClassAI::DoNextCombatManeuver(Unit *){} + +void PlayerbotClassAI::DoNonCombatActions(){} + +void PlayerbotClassAI::LoadSpells(){} + +void PlayerbotClassAI::Pull(){} + +bool PlayerbotClassAI::BuffPlayer(Unit *target){ return false; } + +bool PlayerbotClassAI::FindMount(){ return true; } + +bool PlayerbotClassAI::Unmount(){ return true; } + +bool PlayerbotClassAI::HealTarget (Unit *target, uint8 hp){ return false; } + +bool PlayerbotClassAI::HealGroup (Unit *target, uint8 hp, uint8 &countNeedHeal){ return false; } + +bool PlayerbotClassAI::CureTarget (Unit *target){ return false; } + +bool PlayerbotClassAI::RezTarget (Unit *target){ return false; } + +bool PlayerbotClassAI::IsMounted(){ return m_bot->IsMounted(); } + +bool PlayerbotClassAI::CastSpell(uint32 spellId, Unit *target, bool checkFirst, bool castExistingAura, bool skipFriendlyCheck, bool skipEquipStanceCheck, bool triggered) +{return m_ai->CastSpell(spellId, target, checkFirst, castExistingAura, skipFriendlyCheck, skipEquipStanceCheck, triggered); } +bool PlayerbotClassAI::CastSpell(const SpellEntry * pSpellInfo, Unit *target, bool checkFirst, bool castExistingAura, bool skipFriendlyCheck, bool skipEquipStanceCheck, bool triggered) +{return m_ai->CastSpell(pSpellInfo, target, checkFirst, castExistingAura, skipFriendlyCheck, skipEquipStanceCheck, triggered);} + +bool PlayerbotClassAI::CanCast(uint32 spellId, Unit *target, bool castExistingAura, bool skipFriendlyCheck, bool skipEquipStanceCheck) +{return m_ai->CanCast(spellId, target, castExistingAura, skipFriendlyCheck, skipEquipStanceCheck);} + +bool PlayerbotClassAI::CanCast(const SpellEntry * pSpellInfo, Unit *target, bool castExistingAura, bool skipFriendlyCheck, bool skipEquipStanceCheck) +{return m_ai->CanCast(pSpellInfo, target, castExistingAura, skipFriendlyCheck, skipEquipStanceCheck);} + +bool PlayerbotClassAI::listAuras(Unit *u) +{ + int loc = 0; + Unit *target = u; + typedef std::pair<uint32, uint8> spellEffectPair; + typedef std::multimap< spellEffectPair, Aura*> AuraMap; + Unit::AuraMap &vAuras = target->GetOwnedAuras(); + for(Unit::AuraMap::const_iterator itr = vAuras.begin(); itr!=vAuras.end(); ++itr) + { + //SpellEntry const *spellInfo = (*itr).second->GetSpellProto(); + const SpellEntry *spellInfo = itr->second->GetSpellProto(); + const std::string name = spellInfo->SpellName[loc]; + sLog->outDebug("aura = %u %s", spellInfo->Id, name.c_str()); + } + return true; +};//end listAuras + +bool PlayerbotClassAI::HasAuraName (Unit *unit, uint32 spellId, uint64 casterGuid) +{ + const SpellEntry *const pSpellInfo = GetSpellStore()->LookupEntry (spellId); + if(!pSpellInfo) return false; + int loc = m_bot->GetSession()->GetSessionDbcLocale(); + const std::string name = pSpellInfo->SpellName[loc]; + if(name.length() == 0) return false; + return HasAuraName(unit, name, casterGuid); +} + +bool PlayerbotClassAI::HasAuraName (Unit *target, std::string spell, uint64 casterGuid) +{ + int loc = m_bot->GetSession()->GetSessionDbcLocale(); + typedef std::pair<uint32, uint8>spellEffectPair; + typedef std::multimap<spellEffectPair, Aura*>AuraMap; + + Unit::AuraMap &vAuras = target->GetOwnedAuras(); + for(Unit::AuraMap::const_iterator itr = vAuras.begin(); itr!=vAuras.end(); ++itr) + { + //SpellEntry const *spellInfo = (*itr).second->GetSpellProto(); + const SpellEntry *spellInfo = itr->second->GetSpellProto(); + const std::string name = spellInfo->SpellName[loc]; + if(!spell.compare(name)) + //if(!strcmp(name.c_str(),spell.c_str())) + { + if(casterGuid == 0) //don't care who casted it + return true; + else if(casterGuid == itr->second->GetCasterGUID()) //only if correct caster casted it + return true; + } + } + return false; +}; + +bool PlayerbotClassAI::castDispel (uint32 dispelSpell, Unit *dTarget, bool checkFirst, bool castExistingAura, bool skipFriendlyCheck, bool skipEquipStanceCheck) +{ + if (dispelSpell == 0 || !dTarget ) return false; + //if (!canCast(dispelSpell, dTarget, true)) return false; //Needless cpu cycles wasted, usually a playerbot can cast a dispell + const SpellEntry *dSpell = GetSpellStore()->LookupEntry(dispelSpell); + if (!dSpell) return false; + + for (uint8 i = 0 ; i < MAX_SPELL_EFFECTS ; ++i) + { + if (dSpell->Effect[i] != (uint32)SPELL_EFFECT_DISPEL) continue; + uint32 dispel_type = dSpell->EffectMiscValue[i]; + uint32 dispelMask = GetDispellMask(DispelType(dispel_type)); + Unit::AuraMap const& auras = dTarget->GetOwnedAuras(); + for (Unit::AuraMap::const_iterator itr = auras.begin(); itr != auras.end(); itr++) + { + Aura * aura = itr->second; + AuraApplication * aurApp = aura->GetApplicationOfTarget(dTarget->GetGUID()); + if (!aurApp) + continue; + + if ((1<<aura->GetSpellProto()->Dispel) & dispelMask) + { + if(aura->GetSpellProto()->Dispel == DISPEL_MAGIC) + { + bool positive = aurApp->IsPositive() ? (!(aura->GetSpellProto()->AttributesEx & SPELL_ATTR1_NEGATIVE)) : false; + + // do not remove positive auras if friendly target + // negative auras if non-friendly target + if(positive == dTarget->IsFriendlyTo(GetPlayerBot())) + continue; + } + // If there is a successfull match return, else continue searching. + if (CastSpell(dSpell, dTarget, checkFirst, castExistingAura, skipFriendlyCheck, skipEquipStanceCheck)) { return true; } + } + } + } + return false; +} + +bool PlayerbotClassAI::castSelfCCBreakers (uint32 castList[]) +{ + uint32 dispelSpell = 0; + Player *dTarget = GetPlayerBot(); + + + /*dispelSpell = (uint32) R_ESCAPE_ARTIST; // this is script effect, + Unit::AuraMap const& auras = dTarget->GetOwnedAuras(); + for (Unit::AuraMap::const_iterator itr = auras.begin(); itr != auras.end(); itr++) + { + Aura * aura = itr->second; + AuraApplication * aurApp = aura->GetApplicationOfTarget(dTarget->GetGUID()); + if (!aurApp) + continue; + + if ( ( aura->GetSpellProto()->Mechanic == MECHANIC_SNARE ) || ( aura->GetSpellProto()->Mechanic == MECHANIC_ROOT ) ) + { + if(aura->GetSpellProto()->Dispel == DISPEL_MAGIC) + { + bool positive = aurApp->IsPositive() ? (!(aura->GetSpellProto()->AttributesEx & SPELL_ATTR_EX_NEGATIVE)) : false; + + // do not remove positive auras if friendly target + // negative auras if non-friendly target + if(positive == dTarget->IsFriendlyTo(caster)) + continue; + } + return castSpell(dispelSpell, dTarget); + } + } + return false; */ + + // racial abilities + /* if( GetPlayerBot()->getRace() == RACE_BLOODELF && !pTarget->HasAura( ARCANE_TORRENT,0 ) && castSpell( ARCANE_TORRENT,pTarget ) ) { + //GetPlayerBot()->Say("Arcane Torrent!", LANG_UNIVERSAL); + } else if( GetPlayerBot()->getRace() == RACE_HUMAN && (GetPlayerBot()->HasUnitState( UNIT_STAT_STUNNED ) || GetPlayerBot()->HasAuraType( SPELL_AURA_MOD_FEAR ) || GetPlayerBot()->HasAuraType( SPELL_AURA_MOD_DECREASE_SPEED ) || GetPlayerBot()->HasAuraType( SPELL_AURA_MOD_CHARM )) && castSpell( EVERY_MAN_FOR_HIMSELF, GetPlayerBot() ) ) { + //GetPlayerBot()->Say("EVERY MAN FOR HIMSELF!", LANG_UNIVERSAL); + } else if( GetPlayerBot()->getRace() == RACE_UNDEAD_PLAYER && (GetPlayerBot()->HasAuraType( SPELL_AURA_MOD_FEAR ) || GetPlayerBot()->HasAuraType( SPELL_AURA_MOD_CHARM )) && castSpell( WILL_OF_THE_FORSAKEN, GetPlayerBot() ) ) { + // GetPlayerBot()->Say("WILL OF THE FORSAKEN!", LANG_UNIVERSAL); + } else if( GetPlayerBot()->getRace() == RACE_DWARF && GetPlayerBot()->HasAuraState( AURA_STATE_DEADLY_POISON ) && castSpell( STONEFORM, GetPlayerBot() ) ) { + //GetPlayerBot()->Say("STONEFORM!", LANG_UNIVERSAL); + } else if( GetPlayerBot()->getRace() == RACE_GNOME && (GetPlayerBot()->HasUnitState( UNIT_STAT_STUNNED ) || GetPlayerBot()->HasAuraType( SPELL_AURA_MOD_DECREASE_SPEED )) && castSpell( ESCAPE_ARTIST, GetPlayerBot() ) ) { + // GetPlayerBot()->Say("ESCAPE ARTIST!", LANG_UNIVERSAL); + } */ + + for (uint8 j = 0; j < sizeof (castList); j++) + { + dispelSpell = castList[j]; + if (dispelSpell == 0 || !dTarget->HasSpell(dispelSpell) || !CanCast(dispelSpell, dTarget, true)) continue; + SpellEntry const *dSpell = GetSpellStore()->LookupEntry(dispelSpell); + if (!dSpell) continue; + + for (uint8 i = 0 ; i < MAX_SPELL_EFFECTS ; ++i) + { + if (dSpell->Effect[i] != (uint32)SPELL_EFFECT_DISPEL && dSpell->Effect[i] != (uint32)SPELL_EFFECT_APPLY_AURA) continue; + if (dSpell->Effect[i] == (uint32)SPELL_EFFECT_APPLY_AURA && ( + (dSpell->EffectApplyAuraName[i] != (uint32) SPELL_AURA_MECHANIC_IMMUNITY) || + (dSpell->EffectApplyAuraName[i] != (uint32) SPELL_AURA_DISPEL_IMMUNITY) + )) continue; + + Unit::AuraMap const& auras = dTarget->GetOwnedAuras(); + for (Unit::AuraMap::const_iterator itr = auras.begin(); itr != auras.end(); itr++) + { + Aura * aura = itr->second; + AuraApplication * aurApp = aura->GetApplicationOfTarget(dTarget->GetGUID()); + if (!aurApp) continue; + + if (aura->GetSpellProto() && ( + (dSpell->Effect[i] == (uint32)SPELL_EFFECT_DISPEL && ((1<<aura->GetSpellProto()->Dispel) & GetDispellMask(DispelType(dSpell->EffectMiscValue[i]))) ) + || (dSpell->EffectApplyAuraName[i] == (uint32) SPELL_AURA_MECHANIC_IMMUNITY && ( GetAllSpellMechanicMask(aura->GetSpellProto()) & ( 1 << dSpell->EffectMiscValue[i]) ) ) + || (dSpell->EffectApplyAuraName[i] == (uint32) SPELL_AURA_DISPEL_IMMUNITY && ( (1<<aura->GetSpellProto()->Dispel) & GetDispellMask(DispelType(dSpell->EffectMiscValue[i])) ) ) + ) ) + { + if(aura->GetSpellProto()->Dispel == DISPEL_MAGIC) + { + bool positive = aurApp->IsPositive() ? (!(aura->GetSpellProto()->AttributesEx & SPELL_ATTR1_NEGATIVE)) : false; + if(positive)continue; + } + return CastSpell(dispelSpell, dTarget, false); + } + } + } + } + return false; +} + +bool PlayerbotClassAI::DoSupportRaid(Player *gPlayer, float radius, bool dResurrect, bool dGroupHeal, bool dHeal, bool dCure, bool dBuff) +{ + bool needHeal = false; + if (dGroupHeal || dHeal) + { + uint8 cntNeedHeal = 0; + uint8 raidHPPercent = GetHealthPercentRaid(gPlayer, cntNeedHeal); + if (dGroupHeal && raidHPPercent <=90 && cntNeedHeal > 1) + { + if (HealGroup(gPlayer, raidHPPercent, cntNeedHeal)) return true; + } + if (raidHPPercent < 60 ) needHeal = true; + } + //std::list<Unit*> unitList; + //gPlayer->GetRaidMember(unitList,30); + Group *pGroup = gPlayer->GetGroup(); + if (!pGroup) return false; + for (GroupReference *itr = pGroup->GetFirstMember(); itr != NULL; itr = itr->next()) + { + Unit* tPlayer = itr->getSource(); + if(!tPlayer || gPlayer->IsHostileTo(tPlayer)) continue; + if(GetPlayerBot()->GetAreaId() != tPlayer->GetAreaId()) continue; + if(!m_bot->IsWithinDistInMap(tPlayer, radius)) { continue; } + if(tPlayer->isDead()) // May be we can rez + { + if(!dResurrect) continue; + if(needHeal) continue; //First heal others needing heal + if(tPlayer->GetGUID() == GetPlayerBot()->GetGUID()) continue; + if(tPlayer->IsNonMeleeSpellCasted(true)) continue; //Already rez + if(RezTarget(tPlayer)) { return true; } + else continue; + } + if (dHeal) + { + uint8 tarHPPercent = tPlayer->GetHealth()*100 / tPlayer->GetMaxHealth(); + if (tarHPPercent < 100 && HealTarget(tPlayer, tarHPPercent)) return true; + } + if (needHeal && dHeal) continue; //First heal others needing heal + if (dCure && CureTarget(tPlayer)) return true; + if (dBuff && BuffPlayer(tPlayer)) return true; + } + return false; +} + +bool PlayerbotClassAI::TakePosition(Unit *followTarget, BotRole bRole, float bDist, float bMinDist, float bMaxDist, float bAngle, Unit *faceTarget) +{ + bool doFollow = true; + bool omitAngle = false; + bool angleIsAutoSet = false; + if (!bAngle) angleIsAutoSet = true; + if (bAngle < 0) bAngle += 2 * M_PI; + //if (bAngle > 2 * M_PI) bAngle -= 2 * M_PI; //Do not send values higher than 2 PI, lower than -2 PI + bool rval = false; + if (followTarget == NULL) { followTarget = GetMaster(); if (followTarget == NULL) { return false; } } + if (faceTarget == NULL) { faceTarget = followTarget; } + if (bRole == BOT_ROLE_NONE) { bRole = ( (m_role == BOT_ROLE_NONE) ? BOT_ROLE_DPS_MELEE : m_role); } + //Default values + Unit *pVictim = followTarget->getVictim(); + if (pVictim && pVictim->GetGUID() == m_bot->GetGUID()) //if target is attacking me + { + if (bRole == BOT_ROLE_TANK || bRole == BOT_ROLE_OFFTANK || bRole == BOT_ROLE_DPS_MELEE) + { + //Move to target + if (!bDist || bDist > 0.7f) bDist = 0.7f; + if (bMinDist < 0 || bMinDist > 1) bMinDist = 0; + if (bMaxDist <= 0 || bMaxDist > MELEE_RANGE) bMaxDist = MELEE_RANGE; + bAngle = 0; + } + else {doFollow = false;} //Do not move, creature will come + } + else + { + // calculating distance to follow + switch (bRole) + { + case BOT_ROLE_TANK: + case BOT_ROLE_OFFTANK: + if (!bDist) { bDist = 0.7f; bMinDist = 0; bMaxDist = MELEE_RANGE; bAngle = 0;} + break; + case BOT_ROLE_HEALER: + case BOT_ROLE_SUPPORT: + if (!bDist) { bDist = urand(12, 14); bMinDist = 10; bMaxDist = 18; bAngle = ((urand(0,1) * 90 ) + urand(110,160)) * M_PI / 180; } + break; + case BOT_ROLE_DPS_RANGED: + if (!bDist) { bDist = urand(18, 24); bMinDist = 10; bMaxDist = 26; bAngle = ((urand(0,1) * 90 ) + urand(110,160)) * M_PI / 180; } + break; + default: + if (!bDist) { bDist = 0.7f; bMinDist = 0.1f; bMaxDist = MELEE_RANGE; bAngle = ((urand(0,1) * 90 ) + urand(110,160)) * M_PI / 180; } + break; + } + } + //Do not try to go behind if ranged and creature is not boss like + if (bDist > MELEE_RANGE && followTarget->GetTypeId() != TYPEID_PLAYER) + { + const CreatureInfo *cInfo = ((Creature*) followTarget)->GetCreatureInfo(); + if (!cInfo || cInfo->rank != 3) { omitAngle = true; } + } + + //Move + if (doFollow) + { + float curDist = m_bot->GetDistance(followTarget); + if (m_pulling || + (!m_bot->isMoving() && + ((curDist > bMaxDist || curDist < bMinDist) //Outside range boundries + || (!omitAngle && ((!followTarget->HasInArc(M_PI,m_bot)) ^ (bAngle > 0.5f * M_PI && bAngle < 1.5f * M_PI)))) )//is at right position front/behind? + ) + { + //m_bot->GetMotionMaster()->Clear(); + //sLog->outError("Bot[%u] is moving, curDist[%f], bDist[%f], bminDist[%f], bMaxDist[%f], bAngle[%f], InFront[%u]", m_bot->GetGUIDLow(), curDist, bDist,bMinDist, bMaxDist, bAngle, followTarget->HasInArc(M_PI,m_bot)); + if (angleIsAutoSet && omitAngle) { m_bot->GetMotionMaster()->MoveChase(followTarget, bDist); } + else { m_bot->GetMotionMaster()->MoveChase(followTarget, bDist, bAngle); } + rval |= true; + } + } + //Face your faceTarget + if (!m_bot->HasInArc(M_PI/16, faceTarget) && !m_bot->isMoving() ) { m_bot->SetFacingToObject(faceTarget); rval |= true; } + return rval; +} + +uint8 PlayerbotClassAI::GetThreatPercent(Unit *pTarget, Unit *pFrom) +{ + uint8 tPercent = 0; + Unit *pVictim = pTarget->getVictim(); + if (!pVictim) return 100; //Not Attacking anyone yet, somehow.. + if (!pFrom) { pFrom = m_bot; } + if (pVictim->GetGUID() == pFrom->GetGUID()) return 100; //I'm already being attacked, too late for alert, kill it.. + //if (m_tank->GetGUID() == m_bot->GetGUID()) {} //If I am not tank and there is a target + + ThreatManager &pthreatManager = pTarget->getThreatManager(); + float maxThreat = pthreatManager.getThreat(pTarget->getVictim()) ; + if (maxThreat <= 0) { return 100; } //0 threat + float curThreat = pthreatManager.getThreat(pFrom); + return (curThreat * 100 / maxThreat); +} +//Gets if the unit is under attack by # of attackers +bool PlayerbotClassAI::isUnderAttack(Unit *pAttacked,const uint8 &minNumberOfAttackers) +{ + if (!pAttacked) { pAttacked = m_bot; if (!pAttacked) { return false; } } + Unit::AttackerSet fAttackerSet = pAttacked->getAttackers(); + if (fAttackerSet.size() >= minNumberOfAttackers) { return true; } + return false; +} + +//Gets the first found attacker of Unit +Unit *PlayerbotClassAI::GetAttackerOf(Unit *pAttacked) +{ + if (!pAttacked) { pAttacked = m_bot; if (!pAttacked) { return NULL; } } + Unit::AttackerSet fAttackerSet = pAttacked->getAttackers(); + if (fAttackerSet.size() <= 0) { return NULL; } + return (*fAttackerSet.begin()); +} +//Gets the first found attacker of Unit if not nearestToAttacked > finds the one nearest to bot +Unit *PlayerbotClassAI::GetNearestAttackerOf(Unit *pAttacked, bool nearestToAttacked) +{ + if (!pAttacked) { pAttacked = m_bot; if (!pAttacked) return NULL;} + + Unit::AttackerSet fAttackerSet = pAttacked->getAttackers(); + if (fAttackerSet.size() <= 0) { return NULL; } + + Unit *nearestTo = m_bot; + if (nearestToAttacked) { nearestTo = pAttacked; } + + Unit *curAtt = NULL; + float minDist = 30; + + + for (Unit::AttackerSet::const_iterator itr = fAttackerSet.begin(); itr != fAttackerSet.end(); ++itr) + { + Unit *tAtt = (*itr); + if (!tAtt) break; // Something is wrong.. How can a non existing mob attack? + if (tAtt->isDead()) break; + if (m_bot->GetDistance(tAtt) >= minDist) continue; //Get the nearest one + curAtt = tAtt; + minDist = tAtt->GetDistance(nearestTo); + } + return curAtt; + +} + +uint8 PlayerbotClassAI::GetHealthPercentRaid(Player *gPlayer, uint8 &countNeedHealing) +{ + uint8 validMemberCount=0; + uint16 totalHPPercent=0; + std::list<Unit*> unitList; + gPlayer->GetRaidMember(unitList,30); + if(!unitList.empty()){ + for (std::list<Unit*>::iterator itr = unitList.begin() ; itr!=unitList.end();++itr) { + //Player *tPlayer = GetPlayerBot()->GetObjPlayer((*itr)->GetGUID()); + Unit *tPlayer = sObjectMgr->GetPlayer((*itr)->GetGUID()); + if(tPlayer == NULL) continue; + if(tPlayer->isDead()) continue; + if(GetPlayerBot()->GetAreaId() != tPlayer->GetAreaId()) continue; + //if(tPlayer->GetGUID() == GetPlayerBot()->GetGUID()) continue; + if(GetPlayerBot()->GetDistance(tPlayer) > 30) continue; + uint8 fndHPPercent = tPlayer->GetHealth()*100 / tPlayer->GetMaxHealth(); + totalHPPercent+=fndHPPercent; + validMemberCount++; + if (fndHPPercent < 100) countNeedHealing++; + + //const std::string myname = GetPlayerBot()->GetName(); + //const std::string hisname = tPlayer->GetName(); + //sLog->outDebug("me = %s, checked= %s %u [%u / %u]", myname.c_str(), hisname.c_str(), fndHPPercent, tPlayer->GetHealth(), tPlayer->GetMaxHealth()); + + } + } + if (validMemberCount == 0) return 100; + return totalHPPercent / validMemberCount; +} + +Unit *PlayerbotClassAI::DoSelectLowestHpFriendly(float range, uint32 MinHPDiff) +{ + Unit *pUnit = NULL; + Trinity::MostHPMissingInRange u_check(GetPlayerBot(), range, MinHPDiff); + Trinity::UnitLastSearcher<Trinity::MostHPMissingInRange> searcher(GetPlayerBot(), pUnit, u_check); + + GetPlayerBot()->VisitNearbyObject(range, searcher); + + return pUnit; +} + +void PlayerbotClassAI::SetMainTank(Unit *tank) +{ + mainTank = tank; +} + +// is Resource heavy, do not spam or use heavily in loop +Unit *PlayerbotClassAI::FindMainTankInRaid(Player *gPlayer) +{ + // check if original main tank is still alive. No point regetting main + // tank b/c chances are slim that it will not get reset in the middle of a fight. + // But if main tank dies, try to find next best canidate + if (mainTank!=NULL && mainTank->isAlive()) { + return mainTank; + } + + if (!gPlayer) return NULL; + Group *pGroup = gPlayer->GetGroup(); + if (!pGroup) return NULL; + uint64 pLeaderGuid = pGroup->GetLeaderGUID(); + + Unit *pPlayer = NULL; + + // Check if set in raid + if (pGroup->isRaidGroup()) + { + QueryResult result = CharacterDatabase.PQuery("SELECT memberGuid FROM group_member WHERE memberFlags='%u' AND guid = '%u'",MEMBER_FLAG_MAINTANK, pGroup->GetGUID()); + if(result) + { + uint64 pGuid = MAKE_NEW_GUID(result->Fetch()->GetInt32(),0,HIGHGUID_PLAYER); + pPlayer = sObjectMgr->GetPlayer(pGuid); + if (pPlayer && pGroup->IsMember(pGuid) && pPlayer->isAlive()){ + mainTank = pPlayer; + return pPlayer; + } + } + } + + + // if could not find tank try assuming + // Assume the one with highest health is the main tank + uint32 maxhpfound=0; + std::list<Unit*> unitList; + gPlayer->GetRaidMember(unitList,30); + if (!unitList.empty()){ + for (std::list<Unit*>::iterator itr = unitList.begin() ; itr!=unitList.end();++itr) { + //Player *tPlayer = GetPlayerBot()->GetObjPlayer((*itr)->GetGUID()); + Unit *tPlayer = sObjectMgr->GetPlayer((*itr)->GetGUID()); + if (tPlayer == NULL) continue; + if (tPlayer->isDead()) continue; + if (GetPlayerBot()->GetAreaId() != tPlayer->GetAreaId()) continue; + //if(tPlayer->GetGUID() == GetPlayerBot()->GetGUID()) continue; + if (GetPlayerBot()->GetDistance(tPlayer) > 50) continue; + if (tPlayer->GetMaxHealth() > maxhpfound) { maxhpfound = tPlayer->GetMaxHealth(); pPlayer=tPlayer; } + // Also check pets + if ( (tPlayer->getClass() == (uint8) CLASS_HUNTER || tPlayer->getClass() == (uint8) CLASS_WARLOCK) && IS_PET_GUID(tPlayer->GetPetGUID()) ) + { + Pet* tpet = ObjectAccessor::GetPet(*tPlayer, tPlayer->GetPetGUID()); + if (!tpet || !tpet->IsInWorld() || !tpet->isDead()) continue; + if (tpet->GetArmor() > tPlayer->GetArmor()) //Probably a tanking capable pet.. + { + if (tpet->GetMaxHealth() > maxhpfound) { maxhpfound = tpet->GetMaxHealth(); pPlayer=tpet; } + else if (tPlayer->GetGUID() == pPlayer->GetGUID()) {pPlayer = tpet;} //set pet as tank instead of owner + } + } + } + } + + mainTank = pPlayer; + return pPlayer; +} + +Unit *PlayerbotClassAI::FindMainAssistInRaid(Player *gPlayer) +{ + if (!gPlayer) return NULL; + Group *pGroup = gPlayer->GetGroup(); + if (!pGroup) return NULL; + uint64 pLeaderGuid = pGroup->GetLeaderGUID(); + + + Unit *pPlayer = NULL; + + // Check if set in raid + if (pGroup->isRaidGroup()) + { + QueryResult result = CharacterDatabase.PQuery("SELECT memberGuid FROM group_member WHERE memberFlags='%u' AND guid = '%u'",MEMBER_FLAG_MAINASSIST, pGroup->GetGUID()); + if(result) + { + uint64 pGuid = MAKE_NEW_GUID(result->Fetch()->GetInt32(),0,HIGHGUID_PLAYER); + pPlayer = sObjectMgr->GetPlayer(pGuid); + if (pPlayer && pGroup->IsMember(pGuid) && pPlayer->isAlive()){ + return pPlayer; + } + } + } + + // default to main tank + return FindMainTankInRaid(gPlayer); +} + +Player * PlayerbotClassAI::FindMage(Player *gPlayer) +{ + Group::MemberSlotList const &groupSlot = gPlayer->GetGroup()->GetMemberSlots(); + for(Group::member_citerator itr = groupSlot.begin(); itr != groupSlot.end(); itr++) + { + Player *tPlayer = sObjectMgr->GetPlayer(itr->guid); + + if(tPlayer == NULL) continue; + if(tPlayer->GetGUID() == GetPlayerBot()->GetGUID()) continue; + if(GetPlayerBot()->GetAreaId() != gPlayer->GetAreaId()) continue; + if(GetPlayerBot()->GetDistance(tPlayer) > 30) continue; + + if (tPlayer->getClass() == CLASS_MAGE) return tPlayer; + } + return NULL; +} diff --git a/src/server/game/AI/Bots/PlayerbotClassAI.h b/src/server/game/AI/Bots/PlayerbotClassAI.h new file mode 100644 index 0000000..5537b1d --- /dev/null +++ b/src/server/game/AI/Bots/PlayerbotClassAI.h @@ -0,0 +1,162 @@ +#ifndef _PLAYERBOTCLASSAI_H +#define _PLAYERBOTCLASSAI_H + +#include "Common.h" +#include "World.h" +#include "SpellMgr.h" +#include "Player.h" +#include "ObjectMgr.h" +#include "WorldPacket.h" +#include "Unit.h" +#include "SharedDefines.h" +#include "PlayerbotAI.h" +#include "SpellAuras.h" +#include "Cell.h" +#include "CellImpl.h" +#include "GridNotifiers.h" +#include "GridNotifiersImpl.h" + + +class Player; +class PlayerbotAI; +class Aura; + + enum BotRole + { + BOT_ROLE_NONE, + BOT_ROLE_TANK, + BOT_ROLE_OFFTANK, + BOT_ROLE_DPS_RANGED, + BOT_ROLE_DPS_MELEE, + BOT_ROLE_SUPPORT, + BOT_ROLE_HEALER + }; + +class PlayerbotClassAI +{ + public: + PlayerbotClassAI(Player *const master, Player *const bot, PlayerbotAI *const ai); + virtual ~PlayerbotClassAI(); + + //all combat actions go here + virtual void DoNextCombatManeuver(Unit *); + + //all non combat actions go here, ex buffs, heals, rezzes + virtual void DoNonCombatActions(); + + //buff a specific player, usually a real PC who is not in group + virtual bool BuffPlayer(Unit *target); + + //Heals the target based off its HP + virtual bool HealTarget (Unit *target, uint8 hp); + + //Heals the group based off its HP + virtual bool HealGroup (Unit *target, uint8 hp, uint8 &countNeedHeal); + + //Cure the target + virtual bool CureTarget (Unit *target); + + //Resurrect the target (OBSOLETE - Check individual ClassAIs instead) + virtual bool RezTarget(Unit *target); + + //find any specific mount spells, ie druids = cat, shaman = ghost wolf etc (OBSOLETE) + virtual bool FindMount(); + + virtual bool Unmount(); + + virtual bool IsMounted(); + + virtual void LoadSpells(); + + virtual void Pull(); + + //Utilities + Player *GetMaster (){ return m_master; } + Player *GetPlayerBot(){ return m_bot; } + PlayerbotAI *GetAI (){ return m_ai; } + + bool isPulling() { return m_pulling; } + bool TakePosition(Unit *followTarget, BotRole bRole=BOT_ROLE_NONE, float bDist=0, float bMinDist=0, float bMaxDist=0, float bAngle=0, Unit *faceTarget=NULL); + //Gets the threat done by bot / threat max (percent) to the target. + uint8 GetThreatPercent(Unit *pTarget, Unit *pFrom = NULL); + //Gets if the unit is under attack by # of attackers + bool isUnderAttack(Unit *pAttacked=NULL,const uint8 &minNumberOfAttackers=1); + //Gets the first found attacker of Unit + Unit *GetAttackerOf(Unit *pAttacked=NULL); + //Gets the nearest attacker of Unit if not nearestToAttacked > finds the one nearest to bot + Unit *GetNearestAttackerOf(Unit *pAttacked=NULL, bool nearestToAttacked=false); + //Calculates Average Raid Health condition as Percentage, ref value is the Count of units need healing.. + uint8 GetHealthPercentRaid(Player *gPlayer, uint8 &countNeedHealing); + + // Called when the main tank is set from raid ui + void SetMainTank (Unit *tank); + + //Finds the possible MainTank in Raid including Hunter/Warlock pets.. Makes the assumption based on - max maxHealth.. + Unit *FindMainTankInRaid(Player *gPlayer); + + //Finds the possible MainAssist in Raid. Defaults to Main Tank if it cannot find one. + Unit *FindMainAssistInRaid(Player *gPlayer); + + Player *FindMage(Player *gPlayer); + //Finds the lowest hp creature around that is friendly with the caster. + Unit *DoSelectLowestHpFriendly(float range, uint32 MinHPDiff); + + + protected: + bool CastSpell(uint32 spellId, Unit *target=NULL, bool checkFirst=true, bool castExistingAura=false, bool skipFriendlyCheck=false, bool skipEquipStanceCheck=false, bool triggered=false); + bool CastSpell(const SpellEntry * pSpellInfo, Unit *target=NULL, bool checkFirst=true, bool castExistingAura=false, bool skipFriendlyCheck=false, bool skipEquipStanceCheck=false, bool triggered=false); + bool CanCast(uint32 spellId, Unit *target=NULL,bool castExistingAura=false, bool skipFriendlyCheck=false, bool skipEquipStanceCheck=false); + bool CanCast(const SpellEntry * pSpellInfo, Unit *target=NULL,bool castExistingAura=false, bool skipFriendlyCheck=false, bool skipEquipStanceCheck=false); + + //Debug method to list the auras currently active. + //Use to find what spells were casted + bool listAuras(Unit *unit); + + //More generalized method than HasAura(). It looks for + //any rank of the spell and it doesn't care which + //spell effect you want. If it has the spell aura than + //it returns true + bool HasAuraName(Unit *unit, std::string spell, uint64 casterGuid=0); + bool HasAuraName(Unit *unit, uint32 spellId, uint64 casterGuid=0); + + //The following functions return true only a match is found and the bot successfully casted a spell to resolve the problem + //If the result is false, either a match is not found, or the ClassAI could not cast or refused to cast a spell for some reason.. + + //Combination of all Healer roles, scans the party and decides if group healing > individual healing > Rez > curing > buffing is needed + //and directs any matches found to individual ClassAIs + //Main Raid scan function for Healer/Support types.. + bool DoSupportRaid(Player *gPlayer, float radius=30, bool dResurrect=true, bool dGroupHeal=true, bool dHeal=true, bool dCure=true, bool dBuff=true); + //Find matching debuffs on target to provided Spell, and call castSpell() with provided parameters + bool castDispel (uint32 dispelSpell, Unit *dTarget, bool checkFirst=true, bool castExistingAura=false, bool skipFriendlyCheck=false, bool skipEquipStanceCheck=false); + //Cast matching debuffs on self with probided SpellId list.. Mainly Used for Racial spells.. List is used to prevent extra loops for each spell.. + bool castSelfCCBreakers (uint32 castList[]); + + + + typedef std::set<Unit *> AttackerSet; + + uint8 rezSpamTimer; + uint32 foodDrinkSpamTimer; + static const uint32 foodDrinkSpamCount = 100; + + BotRole m_role; + bool m_pulling; + uint32 threatThreshold, offensiveSpellThreshold; + + // RACIAL SPELLS + uint32 R_ARCANE_TORRENT, R_BERSERKING, R_BLOOD_FURY, R_CANNIBALIZE, R_ESCAPE_ARTIST, R_EVERY_MAN_FOR_HIMSELF, R_GIFT_OF_NAARU, R_SHADOWMELD, R_STONEFORM, R_WAR_STOMP, R_WILL_OF_FORSAKEN; + // first aid + uint32 RECENTLY_BANDAGED; + uint32 SHOOT; + + private: + Player *m_master; + Player *m_bot; + PlayerbotAI *m_ai; + Unit *mainTank; + + + +}; + +#endif diff --git a/src/server/game/AI/Bots/PlayerbotDeathKnightAI.cpp b/src/server/game/AI/Bots/PlayerbotDeathKnightAI.cpp new file mode 100644 index 0000000..a3836f4 --- /dev/null +++ b/src/server/game/AI/Bots/PlayerbotDeathKnightAI.cpp @@ -0,0 +1,325 @@ +/* +Name : PlayerbotDeathknightAI.cpp +Complete: maybe around 65% + +Limitations: - Talent build decision is made by key talent spells, which makes them viable only after level 50-ish.. Until then default behaviour is Blood dps/offtank type + - Tanking bots should taunt if any group member is under attack, currently only saves master + - Situations needing Death grip casting : limited / non-existant.. + +Authors : SwaLLoweD, rrtn +Version : 0.40 +*/ + +#include "PlayerbotDeathKnightAI.h" + +class PlayerbotAI; +PlayerbotDeathKnightAI::PlayerbotDeathKnightAI(Player* const master, Player* const bot, PlayerbotAI* const ai): PlayerbotClassAI(master, bot, ai) +{ + foodDrinkSpamTimer = 0; + LoadSpells(); +} + +PlayerbotDeathKnightAI::~PlayerbotDeathKnightAI() {} + +void PlayerbotDeathKnightAI::LoadSpells(){ + PlayerbotAI *ai = GetAI(); + if (!ai) return; + #pragma region SpellId Fill + // Unholy + PLAGUE_STRIKE = ai->getSpellIdExact("Plague Strike"); + DEATH_STRIKE = ai->getSpellIdExact("Death Strike"); + SCOURGE_STRIKE = ai->getSpellIdExact("Scourge Strike"); + + // Frost + ICY_TOUCH = ai->getSpellIdExact("Icy Touch"); + OBLITERATE = ai->getSpellIdExact("Obliterate"); + + // Blood + BLOOD_STRIKE = ai->getSpellIdExact("Blood Strike"); + HEART_STRIKE = ai->getSpellIdExact("Heart Strike"); + RUNE_TAP = ai->getSpellIdExact("Rune Tap"); + DARK_COMMAND = ai->getSpellIdExact("Dark Command"); + + // AOE + HOWLING_BLAST = ai->getSpellIdExact("Howling Blast"); + BLOOD_BOIL = ai->getSpellIdExact("Blood Boil"); + PESTILENCE = ai->getSpellIdExact("Pestilence"); + CORPSE_EXPLOSION = ai->getSpellIdExact("Corpse Explosion"); + DEATH_AND_DECAY = ai->getSpellIdExact("Death and Decay"); + + // Rune attacks + FROST_STRIKE = ai->getSpellIdExact("Frost Strike"); + DEATH_COIL = ai->getSpellIdExact("Death Coil"); + RUNE_STRIKE = ai->getSpellIdExact("Rune Strike"); + + // CC Interrupt + DEATH_GRIP = ai->getSpellIdExact("Death Grip"); + CHAINS_OF_ICE = ai->getSpellIdExact("Chains of Ice"); + MIND_FREEZE = ai->getSpellIdExact("Mind Freeze"); + HUNGERING_COLD = ai->getSpellIdExact("Hungering Cold"); + STRANGULATE = ai->getSpellIdExact("Strangulate"); + + // Debuffs + FROST_FEVER = 55095; //ai->getSpellIdExact("Frost Fever",true); + BLOOD_PLAGUE = 55078; //ai->getSpellIdExact("Blood Plague",true); + CRYPT_FEVER = ai->getSpellIdExact("Crypt Fever",true); + EBON_PLAGUE = ai->getSpellIdExact("Ebon Plague",true); + MARK_OF_BLOOD = ai->getSpellIdExact("Mark of Blood"); + + // Buffs + HORN_OF_WINTER = ai->getSpellIdExact("Horn of Winter"); + BONE_SHIELD = ai->getSpellIdExact("Bone Shield"); + VAMPIRIC_BLOOD = ai->getSpellIdExact("Vampiric Blood"); + HYSTERIA = ai->getSpellIdExact("Hysteria"); + UNBREAKABLE_ARMOR = ai->getSpellIdExact("Unbreakable Armor"); + ANTI_MAGIC_SHELL = ai->getSpellIdExact("Anti Magic Shell"); + ANTI_MAGIC_ZONE = ai->getSpellIdExact("Anti Magic Zone"); + ICEBOUND_FORTITUDE = ai->getSpellIdExact("Icebound Fortitude"); + EMPOWER_WEAPON = ai->getSpellIdExact("Empower Rune Weapon"); + LICHBORNE = ai->getSpellIdExact("Lichborne"); + + // Summons + RAISE_DEAD = ai->getSpellIdExact("Raise Dead"); + ARMY_OF_THE_DEAD = ai->getSpellIdExact("Army of the Dead"); + SUMMON_GARGOYLE = ai->getSpellIdExact("Summon Gargoyle"); + GHOUL_FRENZY = ai->getSpellIdExact("Ghoul Frenzy"); + DEATH_PACT = ai->getSpellIdExact("Death Pact"); + DANCING_WEAPON = ai->getSpellIdExact("Dancing Rune Weapon"); + + // Presences + BLOOD_PRESENCE = ai->getSpellIdExact("Blood Presence"); + FROST_PRESENCE = ai->getSpellIdExact("Frost Presence"); + UNHOLY_PRESENCE = ai->getSpellIdExact("Unholy Presence"); + + // Talent + TALENT_BLOOD = HEART_STRIKE; + TALENT_FROST = FROST_STRIKE; + TALENT_UNHOLY = SCOURGE_STRIKE; + + uint8 talentCounter = 0; + if (TALENT_BLOOD) talentCounter++; + if (TALENT_FROST) talentCounter++; + if (TALENT_UNHOLY) talentCounter++; + if (talentCounter > 1) { TALENT_BLOOD = 0; TALENT_FROST = 0; TALENT_UNHOLY = 0; } //Unreliable Talent detection. + #pragma endregion +} +void PlayerbotDeathKnightAI::DoNextCombatManeuver(Unit *pTarget) +{ + if (!pTarget || pTarget->isDead()) return; + PlayerbotAI *ai = GetAI(); + if (!ai) return; + Player *m_bot = GetPlayerBot(); + if (!m_bot || m_bot->isDead()) return; + Unit *pVictim = pTarget->getVictim(); + Unit *m_tank = FindMainTankInRaid(GetMaster()); + if (!m_tank && m_bot->GetGroup() && GetMaster()->GetGroup() != m_bot->GetGroup()) { FindMainTankInRaid(m_bot); } + if (!m_tank) { m_tank = m_bot; } + uint32 masterHP = GetMaster()->GetHealth()*100 / GetMaster()->GetMaxHealth(); + float pDist = m_bot->GetDistance(pTarget); + uint8 pThreat = GetThreatPercent(pTarget); + + Pet *pet = m_bot->GetPet(); + std::ostringstream out; + + + if (!m_pulling) + { + m_role = BOT_ROLE_DPS_MELEE; + #pragma region Choose Role/Presence + + // Choose Presence + if (m_tank->GetGUID() == m_bot->GetGUID()) // Hey! I am Main Tank + { + if (CastSpell(FROST_PRESENCE,m_bot)) { m_role = BOT_ROLE_TANK; return; } + } + else if (isUnderAttack()) // I am under attack + { + if (pVictim && pVictim->GetGUID() == m_bot->GetGUID() && pDist <= 2) {} // My target is almost up to me, no need to search + else //Have to select nearest target + { + Unit *curAtt = GetNearestAttackerOf(m_bot); + if (curAtt && curAtt->GetGUID() != pTarget->GetGUID()) + { + m_bot->SetSelection(curAtt->GetGUID()); + //ai->AddLootGUID(curAtt->GetGUID()); + DoNextCombatManeuver(curAtt); //Restart new update to get variables fixed.. + return; + } + } + //my target is attacking me + //if (m_bot->getRace() == (uint8) RACE_NIGHTELF && CastSpell(R_SHADOWMELD,m_bot) ) { return; } + if (CastSpell(FROST_PRESENCE,m_bot)) { m_role = BOT_ROLE_OFFTANK; return; } + } + else if (TALENT_UNHOLY) + { + if (CastSpell(UNHOLY_PRESENCE,m_bot)) return; + } + else if (CastSpell(BLOOD_PRESENCE,m_bot)) return; + #pragma endregion + } + + // Cast CC breakers if any match found (does not work yet) + // uint32 ccSpells[6] = { LICHBORNE, ICEBOUND_FORTITUDE, R_ESCAPE_ARTIST, R_EVERY_MAN_FOR_HIMSELF, R_WILL_OF_FORSAKEN, R_STONEFORM }; + // if (castSelfCCBreakers(ccSpells)) { } //most of them dont have gcd + + TakePosition(pTarget); + + + if (m_pulling) { + if (GetAI()->CastSpell(DEATH_GRIP,pTarget)) { + m_pulling = false; + GetAI()->SetCombatOrder(ORDERS_NONE); + GetAI()->Follow(*GetMaster()); + GetAI()->SetIgnoreUpdateTime(2); + + if (m_bot->GetPet()) pet->SetReactState (REACT_DEFENSIVE); + } + return; + } + + // If there's a cast stop + if(m_bot->HasUnitState(UNIT_STAT_CASTING)) { return; } + + #pragma region Buff Heal Interrupt + //Buff UP + if (CastSpell(HORN_OF_WINTER,m_bot)) { return; } + if (CastSpell(BONE_SHIELD,m_bot)) { return; } + + //HEAL UP && PROTECT UP + if (ai->GetHealthPercent() < 80 && ai->GetHealthPercent() > 20 && CastSpell(VAMPIRIC_BLOOD,m_bot)) { } //NO GCD + if (ai->GetHealthPercent() < 65 && CastSpell(RUNE_TAP,m_bot)) { } //NO GCD + if (CanCast(DEATH_STRIKE,pTarget,true) && ai->GetHealthPercent() < 90 && + (pTarget->HasAura(FROST_FEVER,m_bot->GetGUID()) ||pTarget->HasAura(BLOOD_PLAGUE,m_bot->GetGUID()) ) + && CastSpell(DEATH_STRIKE,pTarget,false) ) {return;} + if (m_bot->getRace() == (uint8) RACE_DWARF && ai->GetHealthPercent() < 75 && CastSpell(R_STONEFORM,m_bot)) { } //no gcd + if (m_bot->getRace() == (uint8) RACE_DRAENEI && ai->GetHealthPercent() < 55 && CastSpell(R_GIFT_OF_NAARU,m_bot)) { return; } //no Gcd, but has cast + if (pet && ai->GetHealthPercent() < 50 && CastSpell(DEATH_PACT,m_bot)) { return; } + if (pet && ai->GetHealthPercent() < 60 && CastSpell(MARK_OF_BLOOD,pTarget)) { return; } + if (ai->GetHealthPercent() < 65 && CastSpell(ICEBOUND_FORTITUDE,m_bot)) { } //No GCD + if (ai->GetHealthPercent() < 65 && CastSpell(UNBREAKABLE_ARMOR,m_bot)) { return; } + + //Break spells being cast + if (pTarget->IsNonMeleeSpellCasted(true)) + { + if (CastSpell(MIND_FREEZE,pTarget)) {} // No GCD + if (CastSpell(STRANGULATE,pTarget)) { return; } + if (m_bot->getRace() == (uint8) RACE_BLOODELF && pDist < 8 && CastSpell(R_ARCANE_TORRENT, pTarget)) { } //no gcd + if (CastSpell(ANTI_MAGIC_ZONE,m_bot)) { return; } + if (CastSpell(ANTI_MAGIC_SHELL,m_bot)) {} //NO GCD + } + + //Catch + if (pTarget->HasUnitMovementFlag(UNIT_FLAG_FLEEING)) + { + if (CastSpell(DEATH_GRIP,pTarget)) return; + if (CastSpell(CHAINS_OF_ICE,pTarget)) return; + } + #pragma endregion + + #pragma region Taunt / Threat + // if i am main tank, protect master by taunt + if(m_tank->GetGUID() == m_bot->GetGUID()) + { + // Taunt if needed (Only for master) + Unit *curAtt = GetAttackerOf(GetMaster()); + if (curAtt && CastSpell(DARK_COMMAND, curAtt)) { } //No gcd + // My target is not attacking me, taunt.. + if (pVictim && pVictim->GetGUID() != m_bot->GetGUID() && CastSpell(DARK_COMMAND, pTarget) ) { } // No gcd + } + + // If not in Frost Presence slow down due to threat + if (pThreat > threatThreshold && !m_bot->HasAura(FROST_PRESENCE) && m_tank->GetGUID() != m_bot->GetGUID() && !isUnderAttack()) + { + if (m_tank->getVictim() && m_tank->getVictim()->GetGUID() != pTarget->GetGUID()) // I am attacking wrong target!! + { + m_bot->SetSelection(m_tank->getVictim()->GetGUID()); + return; + } + else { return; } //DK has no threat reducing spells, just slow down + } + #pragma endregion + + #pragma region Dps + //Dps up + if (CastSpell(EMPOWER_WEAPON,m_bot)) {} //NO GCD + if (ai->GetHealthPercent() > 90 && CastSpell(HYSTERIA,m_bot)) {} //NO GCD + if (m_bot->getRace() == (uint8) RACE_TROLL && CastSpell(R_BERSERKING,m_bot)) {} //no GCD + if (m_bot->getRace() == (uint8) RACE_ORC && CastSpell(R_BLOOD_FURY,m_bot)) {} //no GCD + + // Use up excess Runic Power + if (ai->GetRunicPower() > 60 && CastSpell(FROST_STRIKE,pTarget)) { return; } + else if (ai->GetRunicPower() > 60 && CastSpell(DEATH_COIL,pTarget,true,true,true)) { return; } + if ((isUnderAttack() || ai->GetRunicPower() > 70) && CastSpell(RUNE_STRIKE,pTarget)) {} //Next attack spell + + // Build Diseases + if (!pTarget->HasAura(FROST_FEVER,m_bot->GetGUID()) && CastSpell(ICY_TOUCH,pTarget)) { return; } + if (!pTarget->HasAura(BLOOD_PLAGUE,m_bot->GetGUID()) && CastSpell(PLAGUE_STRIKE,pTarget)) { return; } + + // Use AOEs summons + if (isUnderAttack(m_tank,4) && CastSpell(DEATH_AND_DECAY,pTarget)) { return; } + if (isUnderAttack(m_tank,4) && CastSpell(HOWLING_BLAST,pTarget)) { return; } + if (CanCast(PESTILENCE,pTarget,true) && isUnderAttack(m_tank,4) && + (pTarget->HasAura(FROST_FEVER,m_bot->GetGUID()) && pTarget->HasAura(BLOOD_PLAGUE,m_bot->GetGUID()) ) + && CastSpell(PESTILENCE,pTarget,false)) { return; } + if (CanCast(BLOOD_BOIL,pTarget,true) && isUnderAttack(m_tank,4) && + (pTarget->HasAura(FROST_FEVER,m_bot->GetGUID()) || pTarget->HasAura(BLOOD_PLAGUE,m_bot->GetGUID()) ) + && CastSpell(BLOOD_BOIL,pTarget,false)) { return; } + if (m_bot->getRace() == (uint8) RACE_TAUREN && pDist < 8 && CastSpell(R_WAR_STOMP, pTarget)) { return; } //no gcd but is cast + if (isUnderAttack(m_tank,6) && CastSpell(ARMY_OF_THE_DEAD,m_bot)) { return; } + if (isUnderAttack(m_tank,4) && CastSpell(SUMMON_GARGOYLE,pTarget)) { return; } //This should be somewhat different + + // Use standard damage spells + if (CastSpell(HEART_STRIKE,pTarget,true,true)) { return; } + if (CastSpell(BLOOD_STRIKE,pTarget)) { return; } + if (TALENT_FROST && CastSpell(OBLITERATE,pTarget)) { return; } + else if (TALENT_UNHOLY && CastSpell(SCOURGE_STRIKE,pTarget)) { return; } + else if (CastSpell(DEATH_STRIKE,pTarget)) { return; } + #pragma endregion + +} // end DoNextCombatManeuver + +void PlayerbotDeathKnightAI::DoNonCombatActions() +{ + PlayerbotAI *ai = GetAI(); + Player *m_bot = GetPlayerBot(); + if (!m_bot || !ai || m_bot->isDead()) { return; } + + //If Casting or Eating/Drinking return + if (m_bot->HasUnitState(UNIT_STAT_CASTING)) { return; } + if (m_bot->getStandState() == UNIT_STAND_STATE_SIT) { return; } + + //Buff UP + if (CastSpell(HORN_OF_WINTER,m_bot)) { return; } + if (CastSpell(BONE_SHIELD,m_bot)) { return; } + + //mana/hp check + if (m_bot->getRace() == (uint8) RACE_UNDEAD_PLAYER && ai->GetHealthPercent() < 75 && CastSpell(R_CANNIBALIZE,m_bot)) { return; } + if (m_bot->GetHealth() < m_bot->GetMaxHealth() && CastSpell(RUNE_TAP,m_bot)) { return; } //no gcd but lets give the others a time to heal + if (ai->GetHealthPercent() < 30) { ai->Feast(); } + //Item* fItem = ai->FindBandage(); + /*if (pItem == NULL && fItem != NULL && !m_bot->HasAura(RECENTLY_BANDAGED, 0) && ai->GetHealthPercent() < 70) + { + ai->TellMaster("I could use first aid."); + ai->UseItem(*fItem); + ai->SetIgnoreUpdateTime(8); + return; + } */ +} // end DoNonCombatActions + +void PlayerbotDeathKnightAI::Pull() +{ + Unit* pTarget = ObjectAccessor::GetUnit(*GetMaster(), GetMaster()->GetSelection()); + if (pTarget==NULL || pTarget->IsFriendlyTo(GetMaster())) + { + GetPlayerBot()->Say("Invalid target", LANG_UNIVERSAL); + m_pulling = false; + GetAI()->Follow(*GetMaster()); + return; + } + + m_role = BOT_ROLE_DPS_RANGED; + m_pulling = true; + GetAI()->SetIgnoreUpdateTime(0); + + if (GetPlayerBot()->GetPet()) GetPlayerBot()->GetPet()->SetReactState (REACT_PASSIVE); +} diff --git a/src/server/game/AI/Bots/PlayerbotDeathKnightAI.h b/src/server/game/AI/Bots/PlayerbotDeathKnightAI.h new file mode 100644 index 0000000..9c897bb --- /dev/null +++ b/src/server/game/AI/Bots/PlayerbotDeathKnightAI.h @@ -0,0 +1,60 @@ +#ifndef _PLAYERDEATHKNIGHTAI_H +#define _PLAYERDEATHKNIGHTAI_H + +#include "PlayerbotClassAI.h" + +//class Player; + +class PlayerbotDeathKnightAI : PlayerbotClassAI +{ + public: + PlayerbotDeathKnightAI(Player *const master, Player *const bot, PlayerbotAI *const ai); + virtual ~PlayerbotDeathKnightAI(); + + virtual void LoadSpells(); + + //all combat actions go here + void DoNextCombatManeuver(Unit *); + + //all non combat actions go here, ex buffs, heals, rezzes + void DoNonCombatActions(); + + void Pull(); + + private: + // Unholy + uint32 PLAGUE_STRIKE, DEATH_STRIKE, SCOURGE_STRIKE; + + // Frost + uint32 ICY_TOUCH, OBLITERATE; + + // Blood + uint32 BLOOD_STRIKE, HEART_STRIKE, RUNE_TAP, DARK_COMMAND; + + // AOE + uint32 HOWLING_BLAST, BLOOD_BOIL, PESTILENCE, CORPSE_EXPLOSION, DEATH_AND_DECAY; + + // Rune attacks + uint32 FROST_STRIKE, DEATH_COIL, RUNE_STRIKE; + + // CC Interrupt + uint32 DEATH_GRIP, CHAINS_OF_ICE, MIND_FREEZE, HUNGERING_COLD, STRANGULATE; + + // Debuffs + uint32 FROST_FEVER, BLOOD_PLAGUE, CRYPT_FEVER, EBON_PLAGUE, MARK_OF_BLOOD; + + // Buffs + uint32 HORN_OF_WINTER, BONE_SHIELD, VAMPIRIC_BLOOD, HYSTERIA, UNBREAKABLE_ARMOR, ANTI_MAGIC_SHELL, ANTI_MAGIC_ZONE, ICEBOUND_FORTITUDE, EMPOWER_WEAPON, LICHBORNE; + + // Summons + uint32 RAISE_DEAD, ARMY_OF_THE_DEAD, SUMMON_GARGOYLE, GHOUL_FRENZY, DEATH_PACT, DANCING_WEAPON; + + // Presences + uint32 BLOOD_PRESENCE, FROST_PRESENCE, UNHOLY_PRESENCE; + + // Talent + uint32 TALENT_BLOOD, TALENT_FROST, TALENT_UNHOLY; + +}; + +#endif diff --git a/src/server/game/AI/Bots/PlayerbotDruidAI.cpp b/src/server/game/AI/Bots/PlayerbotDruidAI.cpp new file mode 100644 index 0000000..5fc5130 --- /dev/null +++ b/src/server/game/AI/Bots/PlayerbotDruidAI.cpp @@ -0,0 +1,674 @@ +/* +Name : PlayerbotDruidAI.cpp +Complete: maybe around 70% + +Limitations: - Talent build decision is made by key talent spells, which makes them viable only after level 50-ish.. Until then default behaviour is a combination of Feral/balance type.. + - Tanking bots should taunt if any group member is under attack, currently only saves master + - Tree of life form transition is late and may never occur, due to healing bots attacking priority at full mana. + - Boomkin's support roles are not fully covered.. For example -> off healing + - Situations needing Abolish Disease casting : limited / non-existant.. + +Authors : SwaLLoweD, rrtn, Natsukawa +Version : 0.40 +*/ +#include "PlayerbotDruidAI.h" + +class PlayerbotAI; + +PlayerbotDruidAI::PlayerbotDruidAI(Player *const master, Player *const bot, PlayerbotAI *const ai): PlayerbotClassAI(master, bot, ai) +{ + foodDrinkSpamTimer = 0; + LoadSpells(); +} + +PlayerbotDruidAI::~PlayerbotDruidAI(){} + +void PlayerbotDruidAI::LoadSpells() { + PlayerbotAI *ai = GetAI(); + if (!ai) return; +#pragma region SpellId Fill + // Balance Spells + MOONFIRE = ai->getSpellIdExact("Moonfire"); //attacks + WRATH = ai->getSpellIdExact("Wrath"); + STARFIRE = ai->getSpellIdExact("Starfire"); + STARFALL = ai->getSpellIdExact("Starfall"); + FORCE_OF_NATURE = ai->getSpellIdExact("Force of Nature"); + TYPHOON = ai->getSpellIdExact("Typhoon"); + HURRICANE = ai->getSpellIdExact("Hurricane"); + INSECT_SWARM = ai->getSpellIdExact("Insect Swarm"); + + CYCLONE = ai->getSpellIdExact("Cyclone"); + ROOTS = ai->getSpellIdExact("Entangling Roots"); + NATURES_GRASP = ai->getSpellIdExact("Nature's Grasp"); + + HIBERNATE = ai->getSpellIdExact("Hibernate"); + FAERIE_FIRE = ai->getSpellIdExact("Faerie Fire"); + + + // Bear Form Spells + MAUL = ai->getSpellIdExact("Maul"); + BASH = ai->getSpellIdExact("Bash"); + LACERATE = ai->getSpellIdExact("Lacerate"); + MANGLE_BEAR = ai->getSpellIdExact("Mangle (Bear)"); + SWIPE_BEAR = ai->getSpellIdExact("Swipe (Bear)"); + + DEMORALIZING_ROAR = ai->getSpellIdExact("Demoralizing Roar"); + GROWL = ai->getSpellIdExact("Growl"); + CHALLENGING_ROAR = ai->getSpellIdExact("Challenging Roar"); + + ENRAGE = ai->getSpellIdExact("Enrage"); + FERAL_CHARGE_BEAR = ai->getSpellIdExact("Feral Charge - Bear"); + FRENZIED_REGENERATION = ai->getSpellIdExact("Frenzied Regeneration"); + + + //Cat Attack type's + RAKE = ai->getSpellIdExact("Rake"); //40 energy + CLAW = ai->getSpellIdExact("Claw"); //45 + MANGLE_CAT = ai->getSpellIdExact("Mangle (Cat)"); //45 + SHRED = ai->getSpellIdExact("Shred"); + + RIP = ai->getSpellIdExact("Rip"); //30 + FEROCIOUS_BITE = ai->getSpellIdExact("Ferocious Bite"); //35 + SAVAGE_ROAR = ai->getSpellIdExact("Savage Roar"); + MAIM = ai->getSpellIdExact("Maim"); //35 + + FERAL_CHARGE_CAT = ai->getSpellIdExact("Feral Charge - Cat"); + COWER = ai->getSpellIdExact("Cower"); //20 + TIGERS_FURY = ai->getSpellIdExact("Tiger's Fury"); + + // Feral General + BERSERK = ai->getSpellIdExact("Berserk"); + FAERIE_FIRE_FERAL = ai->getSpellIdExact("Faerie Fire (Feral)"); //debuffs + + //buffs + MARK_OF_THE_WILD = ai->getSpellIdExact("Mark of the Wild"); //buffs + GIFT_OF_THE_WILD = ai->getSpellIdExact("Gift of the Wild"); + THORNS = ai->getSpellIdExact("Thorns"); + SURVIVAL_INSTINCTS = ai->getSpellIdExact("Survival Instincts"); + + // Restoration Spells + LIFEBLOOM = ai->getSpellIdExact("Lifebloom"); + REJUVENATION = ai->getSpellIdExact("Rejuvenation"); //heals + REGROWTH = ai->getSpellIdExact("Regrowth"); + NOURISH = ai->getSpellIdExact("Nourish"); + SWIFTMEND = ai->getSpellIdExact("Swiftmend"); + HEALING_TOUCH = ai->getSpellIdExact("Healing Touch"); + INNERVATE = ai->getSpellIdExact("Innervate"); + WILD_GROWTH = ai->getSpellIdExact("Wild Growth"); + TRANQUILITY = ai->getSpellIdExact("Tranquility"); + NATURES_SWIFTNESS = ai->getSpellIdExact("Nature's Swiftness"); + + CURE_POISON = ai->getSpellIdExact("Abolish Poison"); + if (!CURE_POISON) CURE_POISON = ai->getSpellIdExact("Cure Poison"); + + REBIRTH = ai->getSpellIdExact("Rebirth"); + REVIVE = ai->getSpellIdExact("Revive"); + + BARKSKIN = ai->getSpellIdExact("Barkskin"); + + //Druid Forms + BEAR_FORM = ai->getSpellIdExact("Dire Bear Form"); + if (!BEAR_FORM) BEAR_FORM = ai->getSpellIdExact("Bear Form"); + CAT_FORM = ai->getSpellIdExact("Cat Form"); + MOONKIN_FORM = ai->getSpellIdExact("Moonkin Form"); + TREE_OF_LIFE_FORM = ai->getSpellIdExact("Tree of Life"); //33891;//learning spell has higher id.. + AQUATIC_FORM = ai->getSpellIdExact("Aquatic Form"); + TRAVEL_FORM = ai->getSpellIdExact("Travel Form"); + FLIGHT_FORM = ai->getSpellIdExact("Swift Flight Form"); + if (!FLIGHT_FORM) FLIGHT_FORM = ai->getSpellIdExact("Flight Form"); + + + TALENT_BALANCE = MOONKIN_FORM; + TALENT_RESTO = SWIFTMEND; + TALENT_FERAL = MANGLE_CAT; + + uint8 talentCounter = 0; + if (TALENT_BALANCE) talentCounter++; + if (TALENT_FERAL) talentCounter++; + if (TALENT_RESTO) talentCounter++; + if (talentCounter > 1) { TALENT_BALANCE = 0; TALENT_RESTO = 0; TALENT_FERAL = 0; } //Unreliable Talent detection. +#pragma endregion +} + +void PlayerbotDruidAI::DoNextCombatManeuver(Unit *pTarget) +{ + if (!pTarget || pTarget->isDead()) return; + PlayerbotAI *ai = GetAI(); + if (!ai) return; + Player *m_bot = GetPlayerBot(); + if (!m_bot || m_bot->isDead()) return; + Unit *pVictim = pTarget->getVictim(); + Unit *m_tank = FindMainTankInRaid(GetMaster()); + if (!m_tank && m_bot->GetGroup() && GetMaster()->GetGroup() != m_bot->GetGroup()) { FindMainTankInRaid(m_bot); } + if (!m_tank) { m_tank = m_bot; } + uint32 masterHP = GetMaster()->GetHealth()*100 / GetMaster()->GetMaxHealth(); + float pDist = m_bot->GetDistance(pTarget); + uint8 pThreat = GetThreatPercent(pTarget); + + uint8 reqHeal = 0; + uint8 OwnPartyHP = GetHealthPercentRaid(m_bot, reqHeal); + + + #pragma region Select behaviour + if (m_tank->GetGUID() == m_bot->GetGUID()) // Hey! I am Main Tank + { + if (TALENT_FERAL && BEAR_FORM) { m_role = BOT_ROLE_TANK; } //Just Keep Tanking dont even change forms for healing + else + { + if (TALENT_BALANCE) { + if ((ai->GetHealthPercent() <= 40 || masterHP <30 ) && (ai->GetManaPercent() >= 40)) { m_role = BOT_ROLE_SUPPORT; } + else if (OwnPartyHP < 20 && ai->GetManaPercent() >= 30) { m_role = BOT_ROLE_SUPPORT; } + else if (ai->GetManaPercent() < 25 ) { m_role = BOT_ROLE_TANK; } + else { m_role = BOT_ROLE_DPS_RANGED; } + } + else //I am both healer and tank?? Hmm + { + if ((ai->GetHealthPercent() <= 70 || masterHP <70 ) && (ai->GetManaPercent() >= 50)) { m_role = BOT_ROLE_SUPPORT; } + else if (OwnPartyHP < 20 && ai->GetManaPercent() >= 30) { m_role = BOT_ROLE_SUPPORT; } + else if (ai->GetManaPercent() < 15 ) { m_role = BOT_ROLE_TANK; } + else { m_role = BOT_ROLE_DPS_RANGED; } + } + } + } + else if (isUnderAttack() && !( ai->GetForm() == FORM_MOONKIN || ai->GetForm() == FORM_TREE) ) // if i am under attack + { + // Keep being in Cat Form if you can reduce threat + if (ai->GetForm() == FORM_CAT && CastSpell(COWER,pTarget)) {return; } + else if (TALENT_RESTO && ai->GetManaPercent() > 10 ) { m_role = BOT_ROLE_SUPPORT; } + else { m_role = BOT_ROLE_OFFTANK; } + } + else if (TALENT_FERAL && CAT_FORM) { // If has any feral forms at all + if ((ai->GetHealthPercent() <= 40 || masterHP <40 ) && (ai->GetManaPercent() >= 40)) { m_role = BOT_ROLE_SUPPORT; } + else if (OwnPartyHP < 30 && ai->GetManaPercent() >= 30) { m_role = BOT_ROLE_SUPPORT; } + else{ m_role = BOT_ROLE_DPS_MELEE; } + } + else if (TALENT_BALANCE) { + if ((ai->GetHealthPercent() <= 50 || masterHP <40 ) && (ai->GetManaPercent() >= 10)) { m_role = BOT_ROLE_SUPPORT; } + else if (OwnPartyHP < 40 && ai->GetManaPercent() >= 30) { m_role = BOT_ROLE_SUPPORT; } + else { m_role = BOT_ROLE_DPS_RANGED; } + } + else if (TALENT_RESTO) { m_role = BOT_ROLE_SUPPORT; } + else + { + // Unknown build or low level : Do not change forms rapidly.. + if ( (ai->GetManaPercent() < 30 && BEAR_FORM) || ( (ai->GetForm() == FORM_CAT || ai->GetForm() == FORM_DIREBEAR || ai->GetForm() == FORM_BEAR) && ai->GetManaPercent() < 70 ) ) m_role = BOT_ROLE_DPS_MELEE; + else { m_role = BOT_ROLE_DPS_RANGED; } + } + + if (!isUnderAttack() && m_tank->GetGUID() != m_bot->GetGUID()) + { + // Select Attacking target + if (pVictim && pVictim->GetGUID() == m_bot->GetGUID() && pDist <= 2) {} //if my target is attacking me continue + else + { + Unit *curAtt = GetNearestAttackerOf(m_bot); + if (curAtt && curAtt->GetGUID() != pTarget->GetGUID()) + { + m_bot->SetSelection(curAtt->GetGUID()); + //ai->AddLootGUID(curAtt->GetGUID()); + DoNextCombatManeuver(curAtt); //Restart new update to get variables fixed.. + return; + } + } + //my target is attacking me + } + #pragma endregion + + // If there's a cast stop + if (m_bot->HasUnitState(UNIT_STAT_CASTING)) return; + // Return to normal form from non combat forms + if (ai->GetForm() == FORM_NONE || ai->GetForm() == FORM_CAT || ai->GetForm() == FORM_TREE || ai->GetForm() == FORM_MOONKIN || ai->GetForm() == FORM_DIREBEAR || ai->GetForm() == FORM_BEAR ) { } //Those are valid incombat auras + else if (ai->GetForm() != FORM_NONE && ChangeForm(1)) { } //return to caster form + + switch(m_role) + { + #pragma region BOT_ROLE_DPS_MELEE + case BOT_ROLE_DPS_MELEE: + //ai->TellMaster("DruidCombat"); + + // Do caster form stuff + if (ai->GetForm() == FORM_NONE) + { + //We have little mana probably cant change form + if (ai->GetManaPercent() < 20 && CastSpell (INNERVATE, m_bot) ) { return; } + else if(m_bot->getRace() == (uint8) RACE_TAUREN && pDist < 8 && CastSpell(R_WAR_STOMP, pTarget)) { return;} + else if(DoSupportRaid(GetMaster(),false,false,false)) return; + else if(m_bot->GetGroup() && GetMaster()->GetGroup() != m_bot->GetGroup() && DoSupportRaid(m_bot,false,false,false)) { return; } + } + + if (CAT_FORM) { if (ChangeForm(CAT_FORM)) { return; } } + else if (BEAR_FORM) { if (ChangeForm(BEAR_FORM)) { return; } } + else if (ai->GetForm() != FORM_NONE && ChangeForm(1)) { } //Normal Form + + TakePosition(pTarget); + break; + #pragma endregion + + #pragma region BOT_ROLE_TANK / BOT_ROLE_OFFTANK + case BOT_ROLE_OFFTANK: + case BOT_ROLE_TANK: // It is a tank druid or a defending druid + + // Do what you must before getting attacked... + if (ai->GetForm() == FORM_NONE) + { + // Non tank stuff to avoid + if (m_tank->GetGUID() != m_bot->GetGUID()) + { + if (ROOTS && !pTarget->HasAura(CYCLONE) && !pTarget->HasAura(HIBERNATE) && CastSpell(ROOTS, pTarget)) { return; } + if (CYCLONE && pDist > 5 && !pTarget->HasAura(ROOTS) && !pTarget->HasAura(HIBERNATE) && CastSpell(CYCLONE, pTarget)) { return; } + if (HIBERNATE && pTarget->GetCreatureType() == (uint32) CREATURE_TYPE_BEAST && !pTarget->HasAura(ROOTS) && !pTarget->HasAura(CYCLONE) && CastSpell(HIBERNATE, pTarget)) { return; } + //if (m_bot->getRace() == (uint8) RACE_NIGHTELF && isUnderAttack() && CastSpell(R_SHADOWMELD, m_bot)) { return; } + } + // Things to do wheter Tank or not + if (m_bot->getRace() == (uint8) RACE_TAUREN && pDist < 8 && CastSpell(R_WAR_STOMP, pTarget)) { return; } //no gcd + if (ai->GetManaPercent() < 20 && CastSpell (INNERVATE, m_bot) ) { return; } //We have little mana probably cant change form + } + TakePosition(pTarget); + + if (ChangeForm(BEAR_FORM)) { return; } + + // if i am main tank, protect master by taunt + if(m_tank->GetGUID() == m_bot->GetGUID()) + { + // Taunt if needed (Only for master) + Unit *curAtt = GetAttackerOf(GetMaster()); + if (curAtt) + { + if (isUnderAttack(GetMaster(),2) && CastSpell(CHALLENGING_ROAR, curAtt)) { return; } + if (CastSpell(GROWL, curAtt)) { return; } + } + // My target is not attacking me, taunt.. + if (pVictim && pVictim->GetGUID() != m_bot->GetGUID() && CastSpell(GROWL, pTarget) ) { return; } + } + break; + #pragma endregion + + #pragma region BOT_ROLE_DPS_RANGED + case BOT_ROLE_DPS_RANGED: + if ( ai->GetManaPercent() < 20 && CastSpell (INNERVATE, m_bot)) { return; } + + // Do caster form stuff + if (ai->GetForm() == FORM_NONE) + { + if(DoSupportRaid(GetMaster())) return; + else if(m_bot->GetGroup() && GetMaster()->GetGroup() != m_bot->GetGroup() && DoSupportRaid(m_bot)) { return; } + } + + if (MOONKIN_FORM) { if (ChangeForm(MOONKIN_FORM)) { return; } } + else if (ai->GetForm() != FORM_NONE && ChangeForm(1)) { } //Normal Form + + TakePosition(pTarget); + + // BUFF UP + if(DoSupportRaid(GetMaster(),false,false,false)) return; + else if(m_bot->GetGroup() && GetMaster()->GetGroup() != m_bot->GetGroup() && DoSupportRaid(m_bot,false,false,false)) { return; } + + break; + #pragma endregion + + #pragma region BOT_ROLE_SUPPORT + case BOT_ROLE_SUPPORT: + if ( ai->GetManaPercent() < 20 && CastSpell (INNERVATE,m_bot)) { return; } + //Get to tree form only if you will no longer cast attack spells + if( TREE_OF_LIFE_FORM && (ai->GetManaPercent() < offensiveSpellThreshold || isUnderAttack()) ) + { + if (ChangeForm(TREE_OF_LIFE_FORM)) { return; } + } + else if (ai->GetForm() != FORM_NONE && ChangeForm(1)) { } //Normal Form no gcd + + TakePosition(pTarget); + + //RezGroup(REBIRTH, GetMaster()); + if (DoSupportRaid(GetMaster())) { return; } + if (m_bot->GetGroup() && GetMaster()->GetGroup() != m_bot->GetGroup() && DoSupportRaid(m_bot)) { return; } + //heal pets and bots + Unit *target = DoSelectLowestHpFriendly(30, 1000); + if(target && target->isAlive() && HealTarget(target, target->GetHealth()*100 / target->GetMaxHealth()) ) { return; } + + break; + #pragma endregion + } + + #pragma region DruidCommon + // Common Dps and protection routine + if (ai->GetHealthPercent() <= 70 && CastSpell(BARKSKIN,m_bot)) { return; } + if (isUnderAttack() && CastSpell(NATURES_GRASP,m_bot)) { return; } + + if (ai->GetForm() == FORM_CAT) + { + // If at threat limit, use Cower to reduce threat + if (pThreat > threatThreshold && m_tank->GetGUID() != m_bot->GetGUID() && !isUnderAttack()) + { + if (m_tank->getVictim() && m_tank->getVictim()->GetGUID() != pTarget->GetGUID()) // I am attacking wrong target!! + { + m_bot->SetSelection(m_tank->getVictim()->GetGUID()); + return; + } + else + { + if (CastSpell(COWER,pTarget)) { return; } //Lets see if we can manage + else { return; } //use no spells and wait threat to be reduced + } + } + if (CastSpell(FERAL_CHARGE_CAT,pTarget)) { return; } + if (m_bot->GetComboPoints() >= 1 && pTarget->IsNonMeleeSpellCasted(true) && CastSpell(MAIM, pTarget)) { return; } + + if (CastSpell(BERSERK, m_bot)) { return; } + if (ai->GetHealthPercent() <= 75 && CastSpell(SURVIVAL_INSTINCTS, m_bot)) { return; } + if (isUnderAttack() && CastSpell(NATURES_GRASP, m_bot)) { return; } + if (CastSpell(FAERIE_FIRE_FERAL, pTarget)) { return; } + + if (m_bot->GetComboPoints() < 5) + { + if (CastSpell(RAKE, pTarget)) { return; } + if (CastSpell(MANGLE_CAT, pTarget)) { return; } + if (!pTarget->HasInArc(M_PI,m_bot) && CastSpell(SHRED, pTarget)) { return; } + if (ai->GetEnergyAmount() > 65 && CastSpell(MANGLE_CAT, pTarget)) { return; } //Spam mangle if cannot cast shred + if (ai->GetEnergyAmount() > 65 && CastSpell(CLAW, pTarget) ) { return; } //Spam Claw if there is no mangle + // if (CanCast(COWER, pTarget) && CastSpell(COWER, pTarget)) { return; } //if still nothing, use COWER to reduce threat + } + else + { + if (CastSpell(SAVAGE_ROAR)) { return; } + if (CastSpell(RIP, pTarget)) { return; } + if (ai->GetEnergyAmount() >= 65 && CastSpell(FEROCIOUS_BITE, pTarget)) { return; } //maxhit for feracious bite + } + if (CastSpell(TIGERS_FURY, m_bot)) { return; } //if nothing is ready yet, use tigers fury + } + else if (ai->GetForm() == FORM_DIREBEAR || ai->GetForm() == FORM_BEAR) + { + // If at threat limit, stop + if (pThreat > threatThreshold && m_tank->GetGUID() != m_bot->GetGUID() && !isUnderAttack() ) + { + //Change to tank's target + if (m_tank->getVictim() && m_tank->getVictim()->GetGUID() != pTarget->GetGUID()) { m_bot->SetSelection(m_tank->getVictim()->GetGUID()); } + return; //use no spells and wait threat to be reduced + } + if (CastSpell(FERAL_CHARGE_BEAR,pTarget)) { return; } + if (CastSpell(BASH, pTarget,true,true)) { return; } //Need check for immunity + if (CastSpell(BERSERK, m_bot)) { return; } + if (CastSpell(DEMORALIZING_ROAR, pTarget)) { return; } + if (ai->GetHealthPercent() > 90 && ai->GetRageAmount() < 50 && CastSpell(ENRAGE, m_bot)) { return; } + if (ai->GetHealthPercent() <= 75 && CastSpell(SURVIVAL_INSTINCTS, m_bot)) { return; } + if ( ( ai->GetHealthPercent() <= 30 || (ai->GetHealthPercent() < 85 && m_tank->GetGUID() != m_bot->GetGUID()) ) + && CastSpell(FRENZIED_REGENERATION)) { return; } + if (CastSpell(FAERIE_FIRE_FERAL, pTarget)) { return; } + if (CastSpell(MANGLE_BEAR, pTarget)) { return; } + if ((ai->GetRageAmount() > 70 || m_tank->GetGUID() == m_bot->GetGUID()) && CastSpell(SWIPE_BEAR, pTarget)) { return; } + if (ai->GetRageAmount() > 50 && CastSpell(MAUL, pTarget)) {} // Low Priority, Next Attack effect + if (ai->GetRageAmount() > 60 && CastSpell(LACERATE, pTarget)) { return; } //Currently applies only 1 + } + else + { + //Defensive stuff + if (m_tank->GetGUID() != m_bot->GetGUID() && pVictim && pVictim->GetGUID() == m_bot->GetGUID() ) + { + if (ROOTS && !pTarget->HasAura(CYCLONE) && !pTarget->HasAura(HIBERNATE) && CastSpell(ROOTS, pTarget)) { return; } + if (CYCLONE && pDist > 5 && !pTarget->HasAura(ROOTS) && !pTarget->HasAura(HIBERNATE) && CastSpell(CYCLONE, pTarget)) { return; } + if (HIBERNATE && pTarget->GetCreatureType() == (uint32) CREATURE_TYPE_BEAST && !pTarget->HasAura(ROOTS) && !pTarget->HasAura(CYCLONE) && CastSpell(HIBERNATE, pTarget)) { return; } + //if (m_bot->getRace() == (uint8) RACE_NIGHTELF && isUnderAttack() && CastSpell(R_SHADOWMELD, m_bot)) { return; } + if (m_bot->getRace() == (uint8) RACE_TAUREN && pDist < 8 && CastSpell(R_WAR_STOMP, pTarget)) { return; } + } + + if (CastSpell(FAERIE_FIRE, pTarget)) { return; } + + // If at threat limit, stop + if (pThreat > threatThreshold && m_tank->GetGUID() != m_bot->GetGUID() && !isUnderAttack() ) + { + //Change to tank's target + if (m_tank->getVictim() && m_tank->getVictim()->GetGUID() != pTarget->GetGUID()) { m_bot->SetSelection(m_tank->getVictim()->GetGUID()); } + return; //use no spells and wait threat to be reduced + } + // Continue attacking if theres excess mana (for healers) + if (m_role == BOT_ROLE_SUPPORT && ai->GetManaPercent() < offensiveSpellThreshold) { return; } + if (m_role != BOT_ROLE_SUPPORT && CastSpell(NATURES_SWIFTNESS, m_bot)) { } //only balance no gcd + + if (m_bot->HasAura(NATURES_SWIFTNESS) && CastSpell(STARFIRE, pTarget)) { return; } + if (CastSpell(INSECT_SWARM, pTarget)) { return; } + if (CastSpell(TYPHOON, pTarget)) { return; } + if (isUnderAttack(m_tank,4) && CastSpell(HURRICANE, pTarget)) { ai->SetIgnoreUpdateTime(8); return; } + if (isUnderAttack(m_tank,5) && CastSpell(FORCE_OF_NATURE, m_bot)) { return; } + if (isUnderAttack(m_tank,4) && CastSpell(STARFALL, pTarget)) { return; } + if (CastSpell(MOONFIRE, pTarget)) { return; } + if (CastSpell(WRATH, pTarget)) { return; } + if (CastSpell(STARFIRE, pTarget)) { return; } + } + + // If there is nothing else to do buff UP + if (m_role == BOT_ROLE_DPS_MELEE) //Those already healed and buffed or should never buff in combat + { + if (DoSupportRaid(GetMaster(),false,false,false)) { return; } + if (m_bot->GetGroup() && GetMaster()->GetGroup() != m_bot->GetGroup() && DoSupportRaid(m_bot,false,false,false)) { return; } + } + + + // drink potion if support / healer (Other builds simply overuse mana and waste mana pots) + if(ai->GetManaPercent() < 5 && (m_role == BOT_ROLE_SUPPORT || m_role == BOT_ROLE_HEALER) ) + { + Item *pItem = ai->FindPotion(); + if(pItem != NULL) + { + if (pItem->GetSpell() && m_bot->HasSpellCooldown(pItem->GetSpell()) ) { return; } //pot is in cooldown + ai->UseItem(*pItem); + } + } + #pragma endregion +} //end DoNextCombatManeuver + +void PlayerbotDruidAI::DoNonCombatActions() +{ + PlayerbotAI *ai = GetAI(); + Player *m_bot = GetPlayerBot(); + if (!m_bot || !ai || m_bot->isDead()) { return; } + + //If Casting or Eating/Drinking return + if (m_bot->HasUnitState(UNIT_STAT_CASTING)) { return; } + if (m_bot->getStandState() == UNIT_STAND_STATE_SIT) { return; } + + //buff and heal raid + if (DoSupportRaid(GetMaster())) { return; } + if (m_bot->GetGroup() && GetMaster()->GetGroup() != m_bot->GetGroup() && DoSupportRaid(m_bot)) { return; } + + //heal pets and bots + Unit *target = DoSelectLowestHpFriendly(30, 1000); + if (target && target->isAlive() && HealTarget(target, target->GetHealth()*100 / target->GetMaxHealth())) { return; } + + //mana/hp check + //Don't bother with eating, if low on hp, just let it heal themself + if (m_bot->getRace() == (uint8) RACE_UNDEAD_PLAYER && ai->GetHealthPercent() < 75 && CastSpell(R_CANNIBALIZE,m_bot)) { return; } + if (ai->GetManaPercent() < 10 && CastSpell (INNERVATE, m_bot)) { return; } //Need mana fast + if (m_bot->GetHealth() < m_bot->GetMaxHealth() && + (ai->GetForm() != FORM_CAT && ai->GetForm() != FORM_MOONKIN && ai->GetForm() != FORM_DIREBEAR && ai->GetForm() != FORM_BEAR) + && CastSpell(REGROWTH,m_bot)) { return; } + if (ai->GetManaPercent() < 50) { ai->Feast(); } + +} //end DoNonCombatActions + +bool PlayerbotDruidAI::BuffPlayer(Unit *target) +{ + PlayerbotAI *ai = GetAI(); + Player *m_bot = GetPlayerBot(); + if (!m_bot || !ai || m_bot->isDead()) { return false; } + + if(!target || target->isDead()) { return false; } + + if (CanCast(THORNS,target,0,0,1) && !HasAuraName(target, THORNS)) { + // Decide if it is worth to change form + if( /*m_bot->HasAura(MOONKIN_FORM) ||*/ m_bot->HasAura(CAT_FORM) || m_bot->HasAura(BEAR_FORM)) + { + if(GetAI()->GetManaPercent() >= 80 ) { ChangeForm(1); } + else { return false; } + } + return CastSpell(THORNS, target, false); + } + if (CanCast(MARK_OF_THE_WILD,target,0,0,1) && !HasAuraName(target, GIFT_OF_THE_WILD) && !HasAuraName(target, MARK_OF_THE_WILD)) { + // Decide if it is worth to change form + if(/*m_bot->HasAura(MOONKIN_FORM) ||*/ m_bot->HasAura(CAT_FORM) || m_bot->HasAura(BEAR_FORM)) + { + if(GetAI()->GetManaPercent() >= 70 ) { ChangeForm(1); } + else return false; + } + return CastSpell(MARK_OF_THE_WILD, target, false); + } + return false; +} + +bool PlayerbotDruidAI::HealTarget(Unit *target, uint8 hp) +{ + PlayerbotAI *ai = GetAI(); + Player *m_bot = GetPlayerBot(); + if (!m_bot || !ai || m_bot->isDead()) { return false; } + if (!target || target->isDead()) { return false; } + + // Decide if it is worth to change form + if (ai->GetForm() == FORM_MOONKIN || ai->GetForm() == FORM_CAT || ai->GetForm() == FORM_DIREBEAR || ai->GetForm() == FORM_BEAR) + { + if (hp < 75 && GetAI()->GetManaPercent() >= 70 ) { ChangeForm(1); } + else if (hp < 40 && GetAI()->GetManaPercent() >= 50) { ChangeForm(1); } + else if (hp < 25 && GetAI()->GetManaPercent() >= 30) { ChangeForm(1); } + else return false; + } + + // if(m_bot->HasAura(TRAVEL_FORM)) ChangeForm(1); + + if(hp < 60 && m_bot->HasAura(NATURES_SWIFTNESS) && CastSpell(HEALING_TOUCH, target)) { return true; } + if(hp < 90 && CastSpell(LIFEBLOOM, target)) { return true; } + if(hp < 80 && CastSpell(REJUVENATION, target)) { return true; } + if(hp < 60 && CastSpell(REGROWTH, target)) { return true; } + if(hp < 70 && CanCast(NOURISH,target) && + (HasAuraName(target,REJUVENATION,m_bot->GetGUID()) || HasAuraName(target,LIFEBLOOM,m_bot->GetGUID()) || HasAuraName(target,REGROWTH,m_bot->GetGUID())) + ) { return CastSpell(NOURISH, target, false); } + if(hp < 50 && CanCast(SWIFTMEND,target) && + (HasAuraName(target,REJUVENATION,m_bot->GetGUID()) || HasAuraName(target,REGROWTH,m_bot->GetGUID())) + ) { return CastSpell(SWIFTMEND, target, false); } + if(hp < 40 && m_bot->isInCombat() && CastSpell(NATURES_SWIFTNESS, m_bot)) { } // NO gcd + if(hp < 40 && CastSpell(HEALING_TOUCH, target)) { return true; } + return false; +} //end HealTarget + +bool PlayerbotDruidAI::HealGroup(Unit *target, uint8 hp, uint8 &countNeedHeal) +{ + PlayerbotAI *ai = GetAI(); + Player *m_bot = GetPlayerBot(); + if (!m_bot || !ai || m_bot->isDead()) { return false; } + if (!target || target->isDead()) { return false; } + + if (countNeedHeal < 2) { return false; } + + // Decide if it is worth to change form + if (ai->GetForm() == FORM_MOONKIN || ai->GetForm() == FORM_CAT || ai->GetForm() == FORM_DIREBEAR || ai->GetForm() == FORM_BEAR) + { + if (hp > 70) { return false; } + if (!CanCast(TRANQUILITY,target,0,0,1) && !WILD_GROWTH) { return false; } + if (!WILD_GROWTH && hp > 35) { return false; } + if (hp < 65 && GetAI()->GetManaPercent() >= 70 ) { ChangeForm(1); } + else if (hp < 40 && GetAI()->GetManaPercent() >= 50) { ChangeForm(1); } + else if (hp < 25 && GetAI()->GetManaPercent() >= 30) { ChangeForm(1); } + else return false; + } + + if (hp < 36 && m_bot->isInCombat() && CanCast(TRANQUILITY,target)) + { + bool sc = CastSpell(TRANQUILITY, target, false); + if (sc) GetAI()->SetIgnoreUpdateTime(10); + return sc; + } + if (hp < 75 && CastSpell(WILD_GROWTH,target)) { return true; } + return false; +} + +bool PlayerbotDruidAI::CureTarget(Unit *target) +{ + PlayerbotAI *ai = GetAI(); + Player *m_bot = GetPlayerBot(); + if (!m_bot || !ai || m_bot->isDead()) { return false; } + if (!target || target->isDead()) { return false; } + // Decide if it is worth to change form (they cange forms even if theres nothing to cure..) + if (ai->GetForm() == FORM_CAT || ai->GetForm() == FORM_DIREBEAR || ai->GetForm() == FORM_BEAR) + { + //if(GetAI()->GetManaPercent() >= 80 ) { ChangeForm(1); } + //else { return false; } + return false; + } + if (castDispel(CURE_POISON, target)) { return true; } + // if(HasAuraName(target, "Venom Spit") || HasAuraName(target, "Poison")) return CastSpell(CURE_POISON, target); + + return false; +} //end HealTarget + +bool PlayerbotDruidAI::RezTarget (Unit *target) +{ + if(!target || target->isAlive()) return false; + Player *m_bot = GetPlayerBot(); + if (target->IsNonMeleeSpellCasted(true)) { return false; } //Already resurrected + + if (m_bot->isInCombat()) + { + if (!CanCast(REBIRTH,target)) return false; + Unit *m_tank = FindMainTankInRaid(m_bot); + if (!m_tank) m_tank = m_bot; + if (target->GetGUID() != m_tank->GetGUID() && + (target->getClass() != (uint8) CLASS_PRIEST || target->getClass() != (uint8) CLASS_DRUID || target->getClass() != (uint8) CLASS_PALADIN) ) return false; + std::string msg = "Rezzing "; + msg += target->GetName(); + // msg += " with "; + // msg += *REZZSpell->SpellName; + GetPlayerBot()->Say(msg, LANG_UNIVERSAL); + return CastSpell(REBIRTH, target); + } + else + { + if (!CanCast(REVIVE,target)) return false; + std::string msg = "Rezzing "; + msg += target->GetName(); + // msg += " with "; + // msg += *REZZSpell->SpellName; + GetPlayerBot()->Say(msg, LANG_UNIVERSAL); + return CastSpell(REVIVE, target); + } + return false; +} + +/*bool PlayerbotDruidAI::FindMount() { + if (TRAVEL_FORM) { + if (GetPlayerBot()) CastSpell(TRAVEL_FORM, GetPlayerBot()); + return true; + } else return false; +} + +bool PlayerbotDruidAI::Unmount() { + GetPlayerBot()->RemoveAurasDueToSpell(TRAVEL_FORM); + return true; +} + +bool PlayerbotDruidAI::IsMounted() { + return GetPlayerBot()->IsMounted() || HasAuraName(GetPlayerBot(), TRAVEL_FORM); +} */ + +bool PlayerbotDruidAI::ChangeForm(uint32 form) +{ + PlayerbotAI *ai = GetAI(); + Player *m_bot = GetPlayerBot(); + if (!m_bot || !ai || m_bot->isDead()) { return false; } + if (!form) return false; + + if (form == 1 && ai->GetForm() == FORM_NONE) return false; + + if (form != 1) + { + if (!CanCast(form,m_bot,0,0,1)) return false; + if (m_bot->HasAura(form)) { return false; } + } + + if (ai->GetForm() == FORM_TREE) m_bot->RemoveAurasDueToSpell(TREE_OF_LIFE_FORM); + else if (ai->GetForm() == FORM_CAT) m_bot->RemoveAurasDueToSpell(CAT_FORM); + else if (ai->GetForm() == FORM_MOONKIN) m_bot->RemoveAurasDueToSpell(MOONKIN_FORM); + else if (ai->GetForm() == FORM_DIREBEAR || ai->GetForm() == FORM_BEAR) m_bot->RemoveAurasDueToSpell(BEAR_FORM); + else if (ai->GetForm() == FORM_TRAVEL) m_bot->RemoveAurasDueToSpell(TRAVEL_FORM); + else if (ai->GetForm() == FORM_FLIGHT || ai->GetForm() == FORM_FLIGHT_EPIC) m_bot->RemoveAurasDueToSpell(FLIGHT_FORM); + else if (ai->GetForm() == FORM_AQUA) m_bot->RemoveAurasDueToSpell(AQUATIC_FORM); + + if (form == 1) { return true; } + + return CastSpell(form,m_bot,false); +} diff --git a/src/server/game/AI/Bots/PlayerbotDruidAI.h b/src/server/game/AI/Bots/PlayerbotDruidAI.h new file mode 100644 index 0000000..35f4d54 --- /dev/null +++ b/src/server/game/AI/Bots/PlayerbotDruidAI.h @@ -0,0 +1,70 @@ +#ifndef _PLAYERBOTDRUIDAI_H +#define _PLAYERBOTDRUIDAI_H + +#include "PlayerbotClassAI.h" + +class PlayerbotDruidAI : PlayerbotClassAI +{ + public: + PlayerbotDruidAI(Player *const master, Player *const bot, PlayerbotAI *const ai); + virtual ~PlayerbotDruidAI(); + + virtual void LoadSpells(); + + //all combat actions go here + void DoNextCombatManeuver(Unit *); + + //all non combat actions go here, ex buffs, heals, rezzes + void DoNonCombatActions(); + + //buff a specific player, usually a real PC who is not in group + bool BuffPlayer(Unit *target); + + //Heals the target based off its HP + bool HealTarget(Unit *target, uint8 hp); + + bool HealGroup(Unit *target, uint8 hp, uint8 &countNeedHeal); + + //Cures the target + bool CureTarget(Unit *target); + + bool RezTarget(Unit *target); + + // find any specific mount spells, ie druids=cat, shaman=ghost wolf etc + /* virtual bool FindMount(); + virtual bool Unmount(); + virtual bool IsMounted(); */ + + //Change Form + bool ChangeForm(uint32 form); + + private: + + // BALANCE Attacks + uint32 MOONFIRE, WRATH, STARFALL, STARFIRE, TYPHOON, HURRICANE, FORCE_OF_NATURE, INSECT_SWARM, CYCLONE, ROOTS, NATURES_GRASP, HIBERNATE, FAERIE_FIRE; + + // RESTORATION Spells + uint32 LIFEBLOOM, REJUVENATION, REGROWTH, NOURISH, SWIFTMEND, HEALING_TOUCH, NATURES_SWIFTNESS, INNERVATE, WILD_GROWTH, TRANQUILITY, REBIRTH, REVIVE, CURE_POISON, BARKSKIN; + + // BEAR SPELLS + uint32 MAUL, BASH, LACERATE, MANGLE_BEAR, SWIPE_BEAR, DEMORALIZING_ROAR, GROWL, CHALLENGING_ROAR , ENRAGE, FERAL_CHARGE_BEAR, FRENZIED_REGENERATION; + + // CAT SPELLS + uint32 CLAW, RAKE, SHRED, MANGLE_CAT, RIP, FEROCIOUS_BITE, SAVAGE_ROAR, MAIM, FERAL_CHARGE_CAT, COWER, TIGERS_FURY; + + // FERAL General + uint32 BERSERK, FAERIE_FIRE_FERAL; + + // BUFFS + uint32 MARK_OF_THE_WILD, GIFT_OF_THE_WILD, THORNS, SURVIVAL_INSTINCTS; + + // FORMS + uint32 CAT_FORM, BEAR_FORM, MOONKIN_FORM, TREE_OF_LIFE_FORM, TRAVEL_FORM, FLIGHT_FORM, AQUATIC_FORM; + + // Key TALENT SPELLS + uint32 TALENT_BALANCE, TALENT_RESTO, TALENT_FERAL; + +}; + + +#endif diff --git a/src/server/game/AI/Bots/PlayerbotHunterAI.cpp b/src/server/game/AI/Bots/PlayerbotHunterAI.cpp new file mode 100644 index 0000000..06c5bc9 --- /dev/null +++ b/src/server/game/AI/Bots/PlayerbotHunterAI.cpp @@ -0,0 +1,561 @@ +/* +Name : PlayerbotHunterAI.cpp +Complete: maybe around 70% + +Limitations: - Talent build decision is made by key talent spells, which makes them viable only after level 50-ish.. Behaviour does not change though.. + - AI always assumes pet is the tank if there are no higher hp people in group than the hunter.. + - Possible threat build / reduce race between pet and hunter if attacking to same target.. Needs checking + - Possible target changing loop between pet and hunter if attacking to same target and getting aggro repeatedly.. Needs checking + - Disarm and Nature resist aspect, Disengage, Scorpid sting are not used right now.. + +Authors : SwaLLoweD +Version : 0.40 +*/ + +#include "PlayerbotHunterAI.h" + + +class PlayerbotAI; + +PlayerbotHunterAI::PlayerbotHunterAI(Player* const master, Player* const bot, PlayerbotAI* const ai): PlayerbotClassAI(master, bot, ai) +{ + m_petSummonFailed = false; + LoadSpells(); +} + +PlayerbotHunterAI::~PlayerbotHunterAI() {} + +void PlayerbotHunterAI::LoadSpells() { + PlayerbotAI *ai = GetAI(); + if (!ai) return; + #pragma region SpellId Fill + // PET CONTROL + PET_SUMMON = ai->getSpellIdExact("Call Pet"); + PET_DISMISS = ai->getSpellIdExact("Dismiss Pet"); + PET_REVIVE = ai->getSpellIdExact("Revive Pet"); + PET_MEND = ai->getSpellIdExact("Mend Pet"); + PET_FEED = 1539; //ai->getSpellIdExact("Feed Pet"); + KILL_COMMAND = ai->getSpellIdExact("Kill Command"); + INTIMIDATION = ai->getSpellIdExact("Intimidation"); + BESTIAL_WRATH = ai->getSpellIdExact("Bestial Wrath"); + + // PET SPELL (master does not have these spells anymore) + GROWL = ai->getSpellIdExact("Growl"); + COWER = ai->getSpellIdExact("Cower"); + BAD_ATTITUDE = ai->getSpellIdExact("Bad Attitude"); + SONIC_BLAST = ai->getSpellIdExact("Sonic Blast"); + NETHER_SHOCK = ai->getSpellIdExact("Nether Shock"); + DEMORALIZING_SCREECH = ai->getSpellIdExact("Demoralizing Screech"); + + // RANGED ATTACK + AUTO_SHOT = ai->getSpellIdExact("Auto Shot"); + ARCANE_SHOT = ai->getSpellIdExact("Arcane Shot"); + EXPLOSIVE_SHOT = ai->getSpellIdExact("Explosive Shot"); + STEADY_SHOT = ai->getSpellIdExact("Steady Shot"); + AIMED_SHOT = ai->getSpellIdExact("Aimed Shot"); + SCATTER_SHOT = ai->getSpellIdExact("Scatter Shot"); + KILL_SHOT = ai->getSpellIdExact("Kill Shot"); + CHIMERA_SHOT = ai->getSpellIdExact("Chimera Shot"); + CONCUSSIVE_SHOT = ai->getSpellIdExact("Concussive Shot"); + DISTRACTING_SHOT = ai->getSpellIdExact("Distracting Shot"); + SILENCING_SHOT = ai->getSpellIdExact("Silencing Shot"); + + // STINGS + SERPENT_STING = ai->getSpellIdExact("Serpent Sting"); + SCORPID_STING = ai->getSpellIdExact("Scorpid Sting"); + WYVERN_STING = ai->getSpellIdExact("Wyvern Sting"); + VIPER_STING = ai->getSpellIdExact("Viper Sting"); + + // DEBUFF + HUNTERS_MARK = ai->getSpellIdExact("Hunter's Mark"); + SCARE_BEAST = ai->getSpellIdExact("Scare Beast"); + + //AOE + VOLLEY = ai->getSpellIdExact("Volley"); + MULTI_SHOT = ai->getSpellIdExact("Multi Shot"); + + //MELEE + RAPTOR_STRIKE = ai->getSpellIdExact("Raptor Strike"); + WING_CLIP = ai->getSpellIdExact("Wing Clip"); + MONGOOSE_BITE = ai->getSpellIdExact("Mongoose Bite"); + COUNTERATTACK = ai->getSpellIdExact("Counterattack"); + + //TRAP + FREEZING_TRAP = ai->getSpellIdExact("Freezing Trap"); + IMMOLATION_TRAP = ai->getSpellIdExact("Immolation Trap"); + FROST_TRAP = ai->getSpellIdExact("Frost Trap"); + EXPLOSIVE_TRAP = ai->getSpellIdExact("Explosive Trap"); + SNAKE_TRAP = ai->getSpellIdExact("Snake Trap"); + ARCANE_TRAP = ai->getSpellIdExact("Arcane Trap"); + FREEZING_ARROW = ai->getSpellIdExact("Freezing Arrow"); + BLACK_ARROW = ai->getSpellIdExact("Black Arrow"); + + //BUFF + TRUESHOT_AURA = ai->getSpellIdExact("Trueshot Aura"); + DETERRENCE = ai->getSpellIdExact("Deterrence"); + FEIGN_DEATH = ai->getSpellIdExact("Feign Death"); + DISENGAGE = ai->getSpellIdExact("Disengage"); + RAPID_FIRE = ai->getSpellIdExact("Rapid Fire"); + READINESS = ai->getSpellIdExact("Readiness"); + MISDIRECTION = ai->getSpellIdExact("Misdirection"); + + //ASPECT + ASPECT_OF_THE_HAWK = ai->getSpellIdExact("Aspect of the Dragonhawk"); + ASPECT_OF_THE_MONKEY = ASPECT_OF_THE_HAWK; + if (!ASPECT_OF_THE_HAWK) ASPECT_OF_THE_HAWK = ai->getSpellIdExact("Aspect of the Hawk"); + if (!ASPECT_OF_THE_MONKEY) ASPECT_OF_THE_MONKEY = ai->getSpellIdExact("Aspect of the Monkey"); + ASPECT_OF_THE_VIPER = ai->getSpellIdExact("Aspect of the Viper"); + + TALENT_MM = TRUESHOT_AURA; + TALENT_BM = BESTIAL_WRATH; + TALENT_SURVIVAL = WYVERN_STING; + + uint8 talentCounter = 0; + if (TALENT_MM) talentCounter++; + if (TALENT_BM) talentCounter++; + if (TALENT_SURVIVAL) talentCounter++; + if (talentCounter > 1) { TALENT_MM = 0; TALENT_BM = 0; TALENT_SURVIVAL = 0; } //Unreliable Talent detection. + + #pragma endregion +} + +bool PlayerbotHunterAI::HasPet(Player* bot) +{ + QueryResult result = CharacterDatabase.PQuery("SELECT * FROM character_pet WHERE owner = '%u' AND (slot = '%u' OR slot = '%u')",bot->GetGUIDLow(),PET_SAVE_AS_CURRENT,PET_SAVE_NOT_IN_SLOT); + + if(result) + return true; //hunter has current pet + else + return false; //hunter either has no pet or stabled +}// end HasPet + +void PlayerbotHunterAI::DoNextCombatManeuver(Unit *pTarget) +{ + if (!pTarget || pTarget->isDead()) return; + PlayerbotAI *ai = GetAI(); + if (!ai) return; + Player *m_bot = GetPlayerBot(); + if (!m_bot || m_bot->isDead()) return; + Unit *pVictim = pTarget->getVictim(); + Unit *m_tank = FindMainTankInRaid(GetMaster()); + if (!m_tank && m_bot->GetGroup() && GetMaster()->GetGroup() != m_bot->GetGroup()) { FindMainTankInRaid(m_bot); } + if (!m_tank) { m_tank = m_bot; } + uint32 masterHP = GetMaster()->GetHealth()*100 / GetMaster()->GetMaxHealth(); + float pDist = m_bot->GetDistance(pTarget); + uint8 pThreat = GetThreatPercent(pTarget); + + Pet *pet = m_bot->GetPet(); + if (m_tank->GetGUID() == m_bot->GetGUID() && pet && pet->isAlive() && pet->isInCombat()) { m_tank = pet; } + uint8 petThreat = 0; + if (pet) { GetThreatPercent(pTarget,pet); } + + // switch (ai->GetScenarioType()) +// { +// case PlayerbotAI::SCENARIO_DUEL: + // ai->CastSpell(RAPTOR_STRIKE); + // return; +// } + + // ------- Non Duel combat ---------- + + + #pragma region Choose Target + // Choose Target + if (isUnderAttack()) // I am under attack + { + if (pVictim && pVictim->GetGUID() == m_bot->GetGUID() && pDist <= 2) { } // My target is almost up to me, no need to search + else //Have to select nearest target + { + Unit *curAtt = GetNearestAttackerOf(m_bot); + if (curAtt && curAtt->GetGUID() != pTarget->GetGUID()) + { + m_bot->SetSelection(curAtt->GetGUID()); + //ai->AddLootGUID(curAtt->GetGUID()); + DoNextCombatManeuver(curAtt); //Restart new update to get variables fixed.. + return; + } + } + //my target is attacking me + } + + #pragma endregion + + #pragma region Pet Actions + // Pet's own Actions + if( pet && pet->isAlive() ) + { + // Setup pet + if (pet->GetCharmInfo()->IsAtStay()) {pet->GetCharmInfo()->SetCommandState(COMMAND_FOLLOW); } + + //Heal pet + if ( ( ((float)pet->GetHealth()/(float)pet->GetMaxHealth()) < 0.5f ) + && ( PET_MEND>0 && !pet->getDeathState() != ALIVE && pVictim != m_bot + && CastSpell(PET_MEND,m_bot) )) { return; } + + // Set pet to attack hunter's attacker > its own attackers > hunter's target + if (!pet->getVictim()) { pet->AI()->AttackStart(pTarget); } + else if (isUnderAttack(m_bot)) { pet->AI()->AttackStart(pTarget); } //Always help hunter if she's under attack + else if (pet->getVictim()->GetGUID() != pTarget->GetGUID() && !isUnderAttack(pet)) { pet->AI()->AttackStart(pTarget); } + else if (isUnderAttack(pet)) // Pet is under attack and hunter has no attackers + { + if ( pet->getVictim()->getVictim() && pet->getVictim()->getVictim()->GetGUID() == pet->GetGUID() && pDist <= 2) { } // My target is almost up to me, no need to search + else //Have to select nearest target + { + Unit *curAtt = GetNearestAttackerOf(pet,true); + if (curAtt && (!pet->getVictim() || curAtt->GetGUID() != pet->getVictim()->GetGUID())) + { + pet->AI()->AttackStart(curAtt); //Attack nearest attacker + } + } + //Actions to do under attack (Always tank it, and try to kill it, until someone (!= hunter) takes aggro back) + //Hunter should help her pet whether main tank or not, unless she's being attacked (BEWARE Targeting Loop possibility) + if (pet->getVictim() && !isUnderAttack(m_bot) && pet->getVictim()->GetGUID() != pTarget->GetGUID()) + { + m_bot->SetSelection(pet->getVictim()->GetGUID()); + DoNextCombatManeuver(pet->getVictim()); //Restart new update to get variables fixed.. + return; + } + + } + // Pet tanking behaviour + if (pet->GetGUID() == m_tank->GetGUID() || isUnderAttack(m_bot) || isUnderAttack(pet)) + { + if (GROWL) pet->GetCharmInfo()->SetSpellAutocast(GROWL,true); //Autocast growl + if (BAD_ATTITUDE) pet->GetCharmInfo()->SetSpellAutocast(BAD_ATTITUDE,true); + if (COWER) pet->GetCharmInfo()->SetSpellAutocast(COWER,false); + if (CastSpell(INTIMIDATION,m_bot)) { return; } + } + else + { + if (GROWL) pet->GetCharmInfo()->SetSpellAutocast(GROWL,false); //Do not try to get aggro + if (BAD_ATTITUDE) pet->GetCharmInfo()->SetSpellAutocast(BAD_ATTITUDE,false); + if (COWER) pet->GetCharmInfo()->SetSpellAutocast(COWER,true); //Autocast cower + } + // NORMAL PET dps attacks + if (petThreat < threatThreshold || pet->GetGUID() == m_tank->GetGUID() || isUnderAttack(m_bot)) + { + if (CastSpell(KILL_COMMAND,m_bot)) { } + else if (CastSpell(BESTIAL_WRATH,m_bot)) { } + } + // NETHERSHOCK DEMORALIZINGSCREECH + } + #pragma endregion + + // If there's a cast stop + if(m_bot->HasUnitState(UNIT_STAT_CASTING)) return; + + // Cast CC breakers if any match found (does not work yet) + // uint32 ccSpells[4] = { R_ESCAPE_ARTIST, R_EVERY_MAN_FOR_HIMSELF, R_WILL_OF_FORSAKEN, R_STONEFORM }; + // if (castSelfCCBreakers(ccSpells)) { } //most of them dont have gcd + + #pragma region Evasive manuevers + // Do evasive manuevers if under attack + if (isUnderAttack()) + { + if (m_tank->GetGUID() == m_bot->GetGUID()) { } // i am tank and my pet is probably dead, so i have to face the attackers + else if (CastSpell(FEIGN_DEATH,m_bot)) { return; } //avoid attack + //else if (m_bot->getRace() == (uint8) RACE_NIGHTELF && CastSpell(R_SHADOWMELD,m_bot) ) { return; } + else if (CastSpell(CONCUSSIVE_SHOT,pTarget)) { return; } + else if (CastSpell(WYVERN_STING,pTarget)) { return; } + else if (CastSpell(SCATTER_SHOT,pTarget)) { return; } + else if (CastSpell(FREEZING_ARROW,pTarget)) { return; } + else if (CastSpell(MISDIRECTION,m_tank)) { return; } + else if (m_bot->getRace() == (uint8) RACE_TAUREN && pDist < 8 && CastSpell(R_WAR_STOMP, pTarget) ) { return; } //no gcd but is cast + else if (pTarget->GetCreatureType() == (uint32) CREATURE_TYPE_BEAST && CastSpell(SCARE_BEAST,pTarget)) { return; } + else if (pDist <= 2 && CastSpell(FREEZING_TRAP,pTarget)) { return; } + } + #pragma endregion + + //Select combat mode + m_role = BOT_ROLE_DPS_RANGED; + if ((isUnderAttack() && pDist <= ATTACK_DISTANCE) || !m_bot->GetUInt32Value(PLAYER_AMMO_ID) ) { m_role = BOT_ROLE_DPS_MELEE; } + + TakePosition(pTarget); + + #pragma region Buff / Protect + //Buff UP + if (m_bot->getRace() == (uint8) RACE_TROLL && CastSpell(R_BERSERKING,m_bot) ) { } //no GCD + if (m_bot->getRace() == (uint8) RACE_ORC && CastSpell(R_BLOOD_FURY,m_bot) ) { } //no GCD + if (CastSpell(TRUESHOT_AURA, m_bot)) { return; } + if (CastSpell(RAPID_FIRE,m_bot)) { return; } + if (CastSpell(HUNTERS_MARK,pTarget)) { return; } + if ((ai->GetHealthPercent() < 80 || ai->GetManaPercent() < 60 ) && CastSpell(READINESS,m_bot)) { } //no gcd + + + //Protect yourself if needed + if (m_bot->getRace() == (uint8) RACE_DWARF && ai->GetHealthPercent() < 75 && CastSpell(R_STONEFORM,m_bot) ) { } //no gcd + if (ai->GetHealthPercent() < 20 && CastSpell(DETERRENCE,m_bot)) {} //No GCD + if (m_bot->getRace() == (uint8) RACE_DRAENEI && ai->GetHealthPercent() < 55 && CastSpell(R_GIFT_OF_NAARU,m_bot)) { return; } + + //Break Spells + if (m_bot->getRace() == (uint8) RACE_BLOODELF && pDist < 8 && ( pTarget->IsNonMeleeSpellCasted(true) || ai->GetManaPercent() < 20 ) && CastSpell(R_ARCANE_TORRENT, pTarget) ) { } //no gcd + if (pTarget->IsNonMeleeSpellCasted(true) && CastSpell(SILENCING_SHOT, pTarget) ) { return; } + if (pTarget->IsNonMeleeSpellCasted(true) && CastSpell(SCATTER_SHOT, pTarget) ) { return; } + + //Catch + if (pTarget->HasUnitMovementFlag(UNIT_FLAG_FLEEING)) + { + if (CastSpell(WING_CLIP,pTarget)) return; + if (CastSpell(CONCUSSIVE_SHOT,pTarget)) return; + if (CastSpell(SCATTER_SHOT, pTarget) ) { return; } + } + #pragma endregion + + //Do combat + switch (m_role) + { + #pragma region BOT_ROLE_DPS_MELEE + case BOT_ROLE_DPS_MELEE: + if (AUTO_SHOT) { m_bot->InterruptNonMeleeSpells( true, AUTO_SHOT ); } //Stop autoshot + if (CastSpell(ASPECT_OF_THE_MONKEY,m_bot)) { return; } //Get Monkey aspect + + if (m_bot->getRace() == (uint8) RACE_TAUREN && pDist < 8 && CastSpell(R_WAR_STOMP, pTarget)) { return; } //no gcd but is cast + + // Threat control + if (pThreat < threatThreshold || m_tank->GetGUID() == m_bot->GetGUID() || m_bot->HasAura(MISDIRECTION) ) { } //Continue attack + else + { + if (pet && isUnderAttack(pet) && pet->getVictim() && pet->getVictim()->GetGUID() != pTarget->GetGUID()) //Should be helping pet + { + m_bot->SetSelection(pet->getVictim()->GetGUID()); + return; + } + else if (m_tank->getVictim() && m_tank->getVictim()->GetGUID() != pTarget->GetGUID()) // I am attacking wrong target!! + { + m_bot->SetSelection(m_tank->getVictim()->GetGUID()); + return; + } + else if (CastSpell(FEIGN_DEATH,m_bot)) { return; } + else { return; } // No more threat reducing spells, just slow down + } + + if (CastSpell(RAPTOR_STRIKE,pTarget,true,true)) {} //No gcd + if (CastSpell(MONGOOSE_BITE,pTarget,true,true)) { return; } // Cannot be sure if casted or not + else if (CastSpell(COUNTERATTACK,pTarget,true,true)) { return; } // Cannot be sure if casted or not + if (CastSpell(WING_CLIP,pTarget)) { return; } + if (isUnderAttack(m_tank,6) && CastSpell(SNAKE_TRAP,m_bot)) { return; } + if (isUnderAttack(m_tank,4) && CastSpell(EXPLOSIVE_TRAP,m_bot)) { return; } + if (CastSpell(IMMOLATION_TRAP,m_bot)) { return; } + break; + #pragma endregion + + #pragma region BOT_ROLE_DPS_RANGED + case BOT_ROLE_DPS_RANGED: + if (m_pulling) { + if (GetAI()->CastSpell(CONCUSSIVE_SHOT,pTarget) || + GetAI()->CastSpell(AUTO_SHOT,pTarget)) { + m_pulling = false; + GetAI()->SetCombatOrder(ORDERS_NONE); + GetAI()->Follow(*GetMaster()); + GetAI()->SetIgnoreUpdateTime(2); + + if(HasPet(GetPlayerBot())) + m_bot->GetPet()->SetReactState(REACT_DEFENSIVE); + } + return; + } + if (AUTO_SHOT && !m_bot->FindCurrentSpellBySpellId(AUTO_SHOT)) { ai->CastSpell(AUTO_SHOT,pTarget); } //Start autoshot + if (!(ai->GetManaPercent() < 85 && m_bot->HasAura(ASPECT_OF_THE_VIPER)) && CastSpell(ASPECT_OF_THE_HAWK,m_bot)) { return; } //Get Hawk aspect + if ((ai->GetManaPercent() < 25) && CastSpell(ASPECT_OF_THE_VIPER,m_bot,true,false,true)) { return; } //Build up mana + + // if i am main tank, protect master by taunt + if(m_tank->GetGUID() == m_bot->GetGUID()) + { + // Taunt if needed (Only for master) + Unit *curAtt = GetAttackerOf(GetMaster()); + if (curAtt && CastSpell(DISTRACTING_SHOT, curAtt)) { return; } + // My target is not attacking me, taunt.. + if (pVictim && pVictim->GetGUID() != m_bot->GetGUID() && CastSpell(DISTRACTING_SHOT, pTarget) ) { return; } + } + // If i am not tank, transfer threat to tank or pet.. + else + { + if (CastSpell(MISDIRECTION,m_tank)) { return; } + if (pet && pet->isAlive() && CastSpell(MISDIRECTION,pet)) { return; } + + // Threat control + if (pThreat < threatThreshold || m_bot->HasAura(MISDIRECTION) ) { } //Continue attack + else + { + if (pet && isUnderAttack(pet) && pet->getVictim() && pet->getVictim()->GetGUID() != pTarget->GetGUID()) //Should be helping pet + { + m_bot->SetSelection(pet->getVictim()->GetGUID()); + return; + } + else if (m_tank->getVictim() && m_tank->getVictim()->GetGUID() != pTarget->GetGUID()) // I am attacking wrong target!! + { + m_bot->SetSelection(m_tank->getVictim()->GetGUID()); + return; + } + else if (CastSpell(FEIGN_DEATH,m_bot)) { return; } + else { return; } // No more threat reducing spells, just slow down + } + } + + // DO dps + if (ai->GetHealthPercent(*pTarget) < 20 && CastSpell(KILL_SHOT,pTarget)) { return; } + if (isUnderAttack(m_tank,4) && CastSpell(MULTI_SHOT,pTarget)) { return; } + if (isUnderAttack(m_tank,4) && CastSpell(VOLLEY,pTarget)) { GetAI()->SetIgnoreUpdateTime(7); return; } + if (CanCast(CHIMERA_SHOT,pTarget) && + (pTarget->HasAura(VIPER_STING,m_bot->GetGUID()) || pTarget->HasAura(SERPENT_STING,m_bot->GetGUID()) ) + && CastSpell(CHIMERA_SHOT,pTarget,false) ) { return; } + if (ai->GetManaPercent() < 60 && ai->GetManaPercent(*pTarget) > 4 && CastSpell(VIPER_STING,pTarget)) { return; } + if (!pTarget->HasAura(VIPER_STING,m_bot->GetGUID()) && CastSpell(SERPENT_STING,pTarget)) { return; } + if (CastSpell(ARCANE_SHOT,pTarget)) { return; } + if (CastSpell(BLACK_ARROW,pTarget)) { return; } + if (CastSpell(EXPLOSIVE_SHOT,pTarget)) { return; } + if (CastSpell(STEADY_SHOT,pTarget)) { return; } + break; + #pragma endregion + } + + /*// drink potion if support / healer (Other builds simply overuse mana and waste mana pots) + if(ai->GetManaPercent() < 5 && (m_role == BOT_ROLE_SUPPORT || m_role == BOT_ROLE_HEALER) ) + { + Item *pItem = ai->FindPotion(); + if(pItem != NULL) + { + if (pItem->GetSpell() && m_bot->HasSpellCooldown(pItem->GetSpell()) ) { return; } //pot is in cooldown + ai->UseItem(*pItem); + } + }*/ +} // end DoNextCombatManeuver + +void PlayerbotHunterAI::DoNonCombatActions() +{ + PlayerbotAI *ai = GetAI(); + Player *m_bot = GetPlayerBot(); + if (!m_bot || !ai || m_bot->isDead()) { return; } + + //If Casting or Eating/Drinking return + if (m_bot->HasUnitState(UNIT_STAT_CASTING)) { return; } + if (m_bot->getStandState() == UNIT_STAND_STATE_SIT) { return; } + + // buff group + if (CastSpell(TRUESHOT_AURA, m_bot)) { return; } + + //mana/hp check + //Don't bother with eating, if low on hp, just let it heal themself + if (m_bot->getRace() == (uint8) RACE_UNDEAD_PLAYER && ai->GetHealthPercent() < 75 && CastSpell(R_CANNIBALIZE,m_bot)) { return; } + if (ai->GetManaPercent() < 20 || ai->GetHealthPercent() < 30) { ai->Feast(); } + + #pragma region Check Pet + // check for pet + if( PET_SUMMON>0 && !m_petSummonFailed && HasPet(m_bot) ) + { + // we can summon pet, and no critical summon errors before + Pet *pet = m_bot->GetPet(); + if( !pet ) + { + // summon pet + if( PET_SUMMON>0 && ai->CastSpell(PET_SUMMON,m_bot) ) + ai->TellMaster( "summoning pet." ); + else + { + m_petSummonFailed = true; + ai->TellMaster( "summon pet failed!" ); + } + } + else if( pet->getDeathState() != ALIVE ) + { + // revive pet + if( PET_REVIVE>0 && ai->GetManaPercent()>=80 && ai->CastSpell(PET_REVIVE,m_bot) ) + ai->TellMaster( "reviving pet." ); + } + else if( ((float)pet->GetHealth()/(float)pet->GetMaxHealth()) < 0.5f ) + { + // heal pet when health lower 50% + if( PET_MEND>0 && !pet->getDeathState() != ALIVE && !pet->HasAura(PET_MEND,0) && ai->GetManaPercent()>=13 && ai->CastSpell(PET_MEND,m_bot) ) + ai->TellMaster( "healing pet." ); + } + else if(pet->GetHappinessState() != HAPPY) // if pet is hungry + { + Unit *caster = (Unit*)m_bot; + // list out items in main backpack + for (uint8 slot = INVENTORY_SLOT_ITEM_START; slot < INVENTORY_SLOT_ITEM_END; slot++) + { + Item* const pItem = m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot); + if (pItem) + { + const ItemPrototype* const pItemProto = pItem->GetProto(); + if (!pItemProto ) + continue; + if(pet->HaveInDiet(pItemProto)) // is pItem in pets diet + { + //sLog->outDebug("Food for pet: %s",pItemProto->Name1); + caster->CastSpell(caster,51284,true); // pet feed visual + uint32 count = 1; // number of items used + int32 benefit = pet->GetCurrentFoodBenefitLevel(pItemProto->ItemLevel); // nutritional value of food + m_bot->DestroyItemCount(pItem,count,true); // remove item from inventory + m_bot->CastCustomSpell(m_bot,PET_FEED,&benefit,NULL,NULL,true); // feed pet + ai->TellMaster( "feeding pet." ); + ai->SetIgnoreUpdateTime(10); + return; + } + } + } + // list out items in other removable backpacks + for (uint8 bag = INVENTORY_SLOT_BAG_START; bag < INVENTORY_SLOT_BAG_END; ++bag) + { + const Bag* const pBag = (Bag*) m_bot->GetItemByPos(INVENTORY_SLOT_BAG_0, bag); + if (pBag) + { + for (uint8 slot = 0; slot < pBag->GetBagSize(); ++slot) + { + Item* const pItem = m_bot->GetItemByPos(bag, slot); + if (pItem) + { + const ItemPrototype* const pItemProto = pItem->GetProto(); + if (!pItemProto ) + continue; + if(pet->HaveInDiet(pItemProto)) // is pItem in pets diet + { + //sLog->outDebug("Food for pet: %s",pItemProto->Name1); + caster->CastSpell(caster,51284,true); // pet feed visual + uint32 count = 1; // number of items used + int32 benefit = pet->GetCurrentFoodBenefitLevel(pItemProto->ItemLevel); // nutritional value of food + m_bot->DestroyItemCount(pItem,count,true); // remove item from inventory + m_bot->CastCustomSpell(m_bot,PET_FEED,&benefit,NULL,NULL,true); // feed pet + ai->TellMaster( "feeding pet." ); + ai->SetIgnoreUpdateTime(10); + return; + } + } + } + } + } + if( pet->HasAura(PET_MEND, 0) && !pet->HasAura(PET_FEED, 0)) + + ai->TellMaster( "..no pet food!" ); + ai->SetIgnoreUpdateTime(7); + } + #pragma endregion + } +} // end DoNonCombatActions + +void PlayerbotHunterAI::Pull() +{ + if (!AUTO_SHOT) return; + + // check ammo + uint32 ammo_id = GetPlayerBot()->GetUInt32Value(PLAYER_AMMO_ID); + if (!ammo_id) { + GetPlayerBot()->Say("I'm out of ammo.", LANG_UNIVERSAL); + return; + } + + Unit* pTarget = ObjectAccessor::GetUnit(*GetMaster(), GetMaster()->GetSelection()); + if (pTarget==NULL || pTarget->IsFriendlyTo(GetMaster())) + { + GetPlayerBot()->Say("Invalid target", LANG_UNIVERSAL); + GetAI()->Follow(*GetMaster()); + return; + } + + m_role = BOT_ROLE_DPS_RANGED; + m_pulling = true; + GetAI()->SetIgnoreUpdateTime(0); + + if(GetPlayerBot()->GetPet()) + GetPlayerBot()->GetPet()->SetReactState(REACT_PASSIVE); +} diff --git a/src/server/game/AI/Bots/PlayerbotHunterAI.h b/src/server/game/AI/Bots/PlayerbotHunterAI.h new file mode 100644 index 0000000..aae7a9f --- /dev/null +++ b/src/server/game/AI/Bots/PlayerbotHunterAI.h @@ -0,0 +1,68 @@ +#ifndef _PLAYERHUNTERAI_H +#define _PLAYERHUNTERAI_H + +#include "PlayerbotClassAI.h" + +//class Player; + +class PlayerbotHunterAI : PlayerbotClassAI +{ + public: + PlayerbotHunterAI(Player *const master, Player *const bot, PlayerbotAI *const ai); + virtual ~PlayerbotHunterAI(); + + virtual void LoadSpells(); + + //all combat actions go here + void DoNextCombatManeuver(Unit *); + + //all non combat actions go here, ex buffs, heals, rezzes + void DoNonCombatActions(); + + bool HasPet(Player* bot); + + virtual void Pull(); + + //buff a specific player, usually a real PC who is not in group + //void BuffPlayer(Player *target); + + private: + //Hunter + bool m_petSummonFailed; + bool m_petFeedPetFailed; + + // PET CONTROL + uint32 PET_SUMMON, PET_DISMISS, PET_REVIVE, PET_MEND, PET_FEED, KILL_COMMAND, INTIMIDATION, BESTIAL_WRATH; + + // PET SPELL + uint32 GROWL, COWER, BAD_ATTITUDE, SONIC_BLAST, NETHER_SHOCK, DEMORALIZING_SCREECH; + + // RANGED ATTACK + uint32 AUTO_SHOT, ARCANE_SHOT, EXPLOSIVE_SHOT, STEADY_SHOT, AIMED_SHOT, SCATTER_SHOT, KILL_SHOT, CHIMERA_SHOT, CONCUSSIVE_SHOT, DISTRACTING_SHOT, SILENCING_SHOT; + + // STINGS + uint32 SERPENT_STING, SCORPID_STING, WYVERN_STING, VIPER_STING; + + // DEBUFF + uint32 HUNTERS_MARK, SCARE_BEAST; + + //AOE + uint32 VOLLEY, MULTI_SHOT; + + //MELEE + uint32 RAPTOR_STRIKE, WING_CLIP, MONGOOSE_BITE, COUNTERATTACK; + + //TRAP + uint32 FREEZING_TRAP, IMMOLATION_TRAP, FROST_TRAP, EXPLOSIVE_TRAP, SNAKE_TRAP, ARCANE_TRAP, FREEZING_ARROW, BLACK_ARROW; + + //BUFF + uint32 TRUESHOT_AURA, DETERRENCE, FEIGN_DEATH, DISENGAGE, RAPID_FIRE, READINESS, MISDIRECTION; + + //ASPECT + uint32 ASPECT_OF_THE_HAWK, ASPECT_OF_THE_MONKEY, ASPECT_OF_THE_VIPER; + + uint32 TALENT_MM, TALENT_BM, TALENT_SURVIVAL; + +}; + +#endif diff --git a/src/server/game/AI/Bots/PlayerbotMageAI.cpp b/src/server/game/AI/Bots/PlayerbotMageAI.cpp new file mode 100644 index 0000000..a3c510c --- /dev/null +++ b/src/server/game/AI/Bots/PlayerbotMageAI.cpp @@ -0,0 +1,384 @@ +#include "PlayerbotMageAI.h" +class PlayerbotAI; +PlayerbotMageAI::PlayerbotMageAI(Player *const master, Player *const bot, PlayerbotAI *const ai): PlayerbotClassAI(master, bot, ai){\ + foodDrinkSpamTimer = 0; + LoadSpells(); +} +PlayerbotMageAI::~PlayerbotMageAI(){} + +void PlayerbotMageAI::LoadSpells() { + PlayerbotAI *ai = GetAI(); + if (!ai) return; + #pragma region SpellId Fill + //arcane + ARCANE_MISSILES = ai->getSpellIdExact("Arcane Missiles"); + ARCANE_EXPLOSION = ai->getSpellIdExact("Arcane Explosion"); + ARCANE_BLAST = ai->getSpellIdExact("Arcane Blast"); + ARCANE_BARRAGE = ai->getSpellIdExact("Arcane Barrage"); + + + //fire + FIREBALL = ai->getSpellIdExact("Fireball"); + FROSTFIRE_BOLT = ai->getSpellIdExact("Frostfire Bolt"); + FIRE_BLAST = ai->getSpellIdExact("Fire Blast"); + FLAMESTRIKE = ai->getSpellIdExact("Flamestrike"); + BLAST_WAVE = ai->getSpellIdExact("Blastwave"); + SCORCH = ai->getSpellIdExact("Scorch"); + PYROBLAST = ai->getSpellIdExact("Pyroblast"); + LIVING_BOMB = ai->getSpellIdExact("Living Bomb"); + + + //cold + FROSTBOLT = ai->getSpellIdExact("Frostbolt"); + FROST_NOVA = ai->getSpellIdExact("Frost Nova"); + ICE_LANCE = ai->getSpellIdExact("Ice Lance"); + BLIZZARD = ai->getSpellIdExact("Blizzard"); + CONE_OF_COLD = ai->getSpellIdExact("Cone of Cold"); + + WATER_ELEMENTAL = ai->getSpellIdExact("Summon Water Elemental"); + + + // buffs + FROST_ARMOR = ai->getSpellIdExact("Ice Armor"); + if (!FROST_ARMOR) FROST_ARMOR = ai->getSpellIdExact("Frost Armor"); + MAGE_ARMOR = ai->getSpellIdExact("Mage Armor"); + MOLTEN_ARMOR = ai->getSpellIdExact("Molten Armor"); + FIRE_WARD = ai->getSpellIdExact("Fire Ward"); + FROST_WARD = ai->getSpellIdExact("Frost Ward"); + MANA_SHIELD = ai->getSpellIdExact("Mana Shield"); + ICE_BARRIER = ai->getSpellIdExact("Ice Barrier"); + POM = ai->getSpellIdExact("Presence of Mind"); + FOCUS_MAGIC = ai->getSpellIdExact("Focus Magic"); + ARCANE_POWER = ai->getSpellIdExact("Arance Power"); + COMBUSTION = ai->getSpellIdExact("Combustion"); + ICY_VEINS = ai->getSpellIdExact("Icy Veins"); + + ARCANE_INTELLECT = ai->getSpellIdExact("Arcane Intellect"); + ARCANE_BRILLIANCE = ai->getSpellIdExact("Arcane Brilliance"); + DALARAN_INTELLECT = ai->getSpellIdExact("Dalaran Intellect"); + DALARAN_BRILLIANCE = ai->getSpellIdExact("Dalaran Brilliance"); + DAMPEN_MAGIC = ai->getSpellIdExact("Dampen Magic"); + AMPLIFY_MAGIC = ai->getSpellIdExact("Amplify Magic"); + + + //CC + POLYMORPH = ai->getSpellIdExact("Polymorph"); + DRAGONS_BREATH = ai->getSpellIdExact("Dragon's Breath"); + DEEP_FREEZE = ai->getSpellIdExact("Deep Freeze"); + + + //other + CONJURE_REFRESHMENT = ai->getSpellIdExact("Conjure Refreshment"); + CONJURE_WATER = ai->getSpellIdExact("Conjure Water"); + CONJURE_FOOD = ai->getSpellIdExact("Conjure Food"); + CONJURE_MANA_GEM = ai->getSpellIdExact("Conjure Mana Gem"); + MIRROR_IMAGE = ai->getSpellIdExact("Mirror Image"); + BLINK = ai->getSpellIdExact("Blink"); + ICE_BLOCK = ai->getSpellIdExact("Ice Block"); + INVISIBILITY = ai->getSpellIdExact("Invisibility"); + EVOCATION = ai->getSpellIdExact("Evocation"); + REMOVE_CURSE = ai->getSpellIdExact("Remove Curse"); + COUNTER_SPELL = ai->getSpellIdExact("Counterspell"); + SLOW = ai->getSpellIdExact("Slow"); + + //Special + P_BRAIN_FREEZE = 57761; //Brain Freeze proc + P_FIRESTARTER = 54741; //Firestarter proc + P_HOT_STREAK = 48108; //Hot Sreak proc + P_ARCANE_BLAST = 36032; //Arcane blast proc + P_MISSILE_BARRAGE = 54490; //Missle Barrage proc + P_FINGERS_OF_FROST = 44545; //Fingers of Frost proc + IMP_SCORCH = 12873; //IMP SCORCH + + SHOOT = ai->getSpellIdExact("Shoot"); + + TALENT_ARCANE = ARCANE_BARRAGE; + TALENT_FIRE = COMBUSTION; + TALENT_FROST = ICE_BARRIER; + + uint8 talentCounter = 0; + if (TALENT_ARCANE) talentCounter++; + if (TALENT_FIRE) talentCounter++; + if (TALENT_FROST) talentCounter++; + //if (talentCounter > 1) { TALENT_ARCANE = 0; TALENT_FIRE = 0; TALENT_FROST = 0; } //Unreliable Talent detection. + #pragma endregion +} + +void PlayerbotMageAI::DoNextCombatManeuver(Unit *pTarget) +{ + if (!pTarget || pTarget->isDead()) return; + PlayerbotAI *ai = GetAI(); + if (!ai) return; + Player *m_bot = GetPlayerBot(); + if (!m_bot || m_bot->isDead()) return; + Unit *pVictim = pTarget->getVictim(); + Unit *m_tank = FindMainTankInRaid(GetMaster()); + if (!m_tank && m_bot->GetGroup() && GetMaster()->GetGroup() != m_bot->GetGroup()) { FindMainTankInRaid(m_bot); } + if (!m_tank) { m_tank = m_bot; } + uint32 masterHP = GetMaster()->GetHealth()*100 / GetMaster()->GetMaxHealth(); + float pDist = m_bot->GetDistance(pTarget); + uint8 pThreat = GetThreatPercent(pTarget); + + #pragma region Choose Actions + // Choose actions accoring to talents (MAGE is always ranged dps) + m_role = BOT_ROLE_DPS_RANGED; + + // if i am under attack and if i am not tank or offtank: change target if needed + if (isUnderAttack()) + { + // Keep hitting but reduce threat + //else if (m_bot->getRace() == (uint8) RACE_NIGHTELF && CastSpell(R_SHADOWMELD,m_bot)) { return; } + if (pVictim && pVictim->GetGUID() == m_bot->GetGUID() && pDist <= 2) { } // My target is almost up to me, no need to search + else //Have to select nearest target + { + Unit *curAtt = GetNearestAttackerOf(m_bot); + if (curAtt && curAtt->GetGUID() != pTarget->GetGUID()) + { + m_bot->SetSelection(curAtt->GetGUID()); + DoNextCombatManeuver(curAtt); //Restart new update to get variables fixed.. + return; + } + } + //my target is attacking me + } + #pragma endregion + + TakePosition(pTarget); + // If there's a cast stop + if (m_bot->HasUnitState(UNIT_STAT_CASTING)) { return; } + + if (DoSupportRaid(m_bot,30,0,0,0,1,1)) { return; } + + if (m_tank->GetGUID() != m_bot->GetGUID() && pVictim && pVictim->GetGUID() == m_bot->GetGUID() ) + { + //if (CastSpell(INVISIBILITY, m_bot)) { return; } + if (ai->GetHealthPercent(*pTarget) > 50 && CastSpell(POLYMORPH)) { return; } + //if (m_bot->getRace() == (uint8) RACE_NIGHTELF && isUnderAttack() && CastSpell(R_SHADOWMELD, m_bot)) { return; } + } + if (isUnderAttack() && pDist > 5 && CastSpell(FROST_NOVA, pTarget)) { return; } + if (DEEP_FREEZE && pTarget->isFrozen() && CastSpell(DEEP_FREEZE,pTarget)) { return; } + if (isUnderAttack() && CastSpell(DRAGONS_BREATH, pTarget)) { return; } + if ((isUnderAttack() || ai->GetHealthPercent() < 75 && !HasAuraName(m_bot, MANA_SHIELD)) && ai->GetManaPercent() > 40 && CastSpell(MANA_SHIELD,m_bot)) { return; } + if (m_bot->getRace() == (uint8) RACE_DWARF && ai->GetHealthPercent() < 75 && CastSpell(R_STONEFORM,m_bot)) { } //no gcd + if (m_bot->getRace() == (uint8) RACE_DRAENEI && ai->GetHealthPercent() < 55 && CastSpell(R_GIFT_OF_NAARU,m_bot)) { return; } //no Gcd, but has cast + if (m_bot->getRace() == (uint8) RACE_TAUREN && pDist < 8 && CastSpell(R_WAR_STOMP, pTarget)) { return; } //no gcd but is cast + if ((ai->GetHealthPercent() < 65 || ai->GetManaPercent() < 5) && CastSpell(ICE_BLOCK,m_bot)) { return; } + if (isUnderAttack() && CastSpell(ICE_BARRIER, pTarget)) { return; } + if (ai->GetManaPercent() < 30 && CastSpell (EVOCATION, m_bot)) { return; } + + + //Break spells + if (m_bot->getRace() == (uint8) RACE_BLOODELF && pDist < 8 && (pTarget->IsNonMeleeSpellCasted(true) || ai->GetManaPercent() < 40) && CastSpell(R_ARCANE_TORRENT, pTarget)) { } //no gcd + else if (pThreat < threatThreshold && pTarget->IsNonMeleeSpellCasted(true) && CastSpell(COUNTER_SPELL, pTarget)) { return; } //High threat + if (!m_bot->HasAura(MOLTEN_ARMOR) && CastSpell(MOLTEN_ARMOR,m_bot)) { return; } + + if (ai->GetHealthPercent(*pTarget) > 96) { return; } // dont dps too early + + //Catch + if (pTarget->HasUnitMovementFlag(UNIT_FLAG_FLEEING)) + { + if (CastSpell(FROST_NOVA,pTarget)) return; + if (CastSpell(FROSTBOLT,pTarget)) return; + } + + // If at threat limit, try to reduce threat + if (pThreat > threatThreshold && m_tank->GetGUID() != m_bot->GetGUID() && !isUnderAttack()) + { + if (m_tank->getVictim() && m_tank->getVictim()->GetGUID() != pTarget->GetGUID()) // I am attacking wrong target!! + { + m_bot->SetSelection(m_tank->getVictim()->GetGUID()); + return; + } + else + { + if (CastSpell(INVISIBILITY,m_bot)) { return; } //Lets see if we can manage + else if (m_bot->FindCurrentSpellBySpellId(SHOOT)) { m_bot->InterruptNonMeleeSpells( true, SHOOT ); return; } //Disable wand + else { return; } //use no spells and wait threat to be reduced + } + } + + + // buff up + if (CastSpell(ICY_VEINS,m_bot)) {} //nogcd + if (m_bot->getRace() == (uint8) RACE_TROLL && CastSpell(R_BERSERKING,m_bot)) {} //no GCD + if (m_bot->getRace() == (uint8) RACE_ORC && CastSpell(R_BLOOD_FURY,m_bot)) {} //no GCD + if (CastSpell(POM,m_bot)) {} //nogcd + + if (TALENT_ARCANE) + { + if (CastSpell(ARCANE_POWER,m_bot)) {} //nogcd + if (CastSpell(MIRROR_IMAGE,m_bot)) { return; } + //AOE + if (isUnderAttack(m_tank,5)) + { + if (CastSpell(BLIZZARD,pTarget)) { return; } + } + //DPS + if (ARCANE_BLAST) + { + Aura *abaura = m_bot->GetAura(P_ARCANE_BLAST); + if (abaura && abaura->GetStackAmount() >= 3) + { + if (m_bot->HasAura(P_MISSILE_BARRAGE) && CastSpell(ARCANE_MISSILES,pTarget)) { return; } + else if (CastSpell(ARCANE_BARRAGE,pTarget)) { return; } + } + } + if (CastSpell(ARCANE_BARRAGE,pTarget) ) { return; } + + } + if (TALENT_FIRE) + { + if (CastSpell(COMBUSTION,m_bot)) { } //nogcd + if (CastSpell(MIRROR_IMAGE,m_bot)) { return; } + + //AOE + if (isUnderAttack(m_tank,5)) + { + if (CastSpell(FLAMESTRIKE,pTarget)) { return; } + if (CastSpell(BLAST_WAVE,pTarget)) { return; } + if (CastSpell(LIVING_BOMB,pTarget)) { return; } + if (CastSpell(DRAGONS_BREATH,pTarget)) { return; } + } + + //DPS + if (m_bot->HasAura(P_HOT_STREAK) && CastSpell(PYROBLAST,pTarget)) { return; } + if (!pTarget->HasAura(LIVING_BOMB,m_bot->GetGUID()) && CastSpell(LIVING_BOMB,pTarget)) { return; } + //if (!pTarget->HasAura(IMP_SCORCH) && CastSpell(SCORCH,pTarget)) { return; } + if (CastSpell(FIREBALL,pTarget)) { return; } + } + if (TALENT_FROST) + { + if (CastSpell(MIRROR_IMAGE,m_bot)) { return; } + if (CastSpell(WATER_ELEMENTAL,m_bot)) { return; } + + uint64 pet_guid = m_bot->GetPetGUID(); + if (pet_guid>0){ + Pet* pet = ObjectAccessor::GetPet(*m_bot, pet_guid); + Unit *unit = ObjectAccessor::GetUnit(*m_bot, pet_guid); + if (unit!=NULL){ + if (!unit->isInCombat()) { + m_bot->GetSession()->HandlePetActionHelper(unit, pet_guid, COMMAND_ATTACK, ACT_COMMAND, pTarget->GetGUID()); + } + } + } + + //if (CastSpell(33395, pTarget)) // pet freeze spell + // sLog->outError ("successfully casted freeze"); + + //AOE + if (isUnderAttack(m_tank,5)) + { + if (CastSpell(BLIZZARD,pTarget)) { return; } + } + + //DPS + if (m_bot->HasAura(P_FINGERS_OF_FROST) && CastSpell(DEEP_FREEZE,pTarget)) { return; } + if (m_bot->HasAura(P_BRAIN_FREEZE) && CastSpell(FROSTFIRE_BOLT,pTarget)) { return; } + if (CastSpell(FROSTBOLT,pTarget,true,true)) { return; } + + } + + // Defaults especialy for lower levels + if (m_bot->HasAura(P_BRAIN_FREEZE) && CastSpell(FIREBALL,pTarget,1,1)) { return; } + if (m_bot->HasAura(P_FIRESTARTER) && CastSpell(FLAMESTRIKE,pTarget,1,1)) { return; } + if (m_bot->HasAura(P_HOT_STREAK) && CastSpell(PYROBLAST,pTarget,1,1)) { return; } + if (m_bot->HasAura(POM) && (CastSpell(PYROBLAST,pTarget,1,1) || CastSpell(FIREBALL,pTarget,1,1) || CastSpell(FROSTBOLT,pTarget,1,1))) { return; } + if (pTarget->isFrozen() && CastSpell(ICE_LANCE,pTarget)) { return; } + if (m_bot->isMoving() && (CastSpell(FIRE_BLAST,pTarget,1,1) || CastSpell(ARCANE_BARRAGE,pTarget) || CastSpell(ICE_LANCE,pTarget))) { return; } + if (CastSpell(FIREBALL,pTarget)) { return; } + if (CastSpell(FROSTBOLT,pTarget)) { return; } + if (CastSpell(ARCANE_MISSILES,pTarget)) { return; } + + // drink potion + if(ai->GetManaPercent() < 5 ) + { + Item *pItem = ai->FindPotion(); + if(pItem != NULL) + { + if (pItem->GetSpell() && m_bot->HasSpellCooldown(pItem->GetSpell()) ) { return; } //pot is in cooldown + ai->UseItem(*pItem); + } + } + + // if we get down here, it means we are out of mana, so use wand + CastSpell(SHOOT, pTarget); + +} //end DoNextCombatManeuver +void PlayerbotMageAI::DoNonCombatActions() +{ + PlayerbotAI *ai = GetAI(); + Player *m_bot = GetPlayerBot(); + if (!m_bot || !ai || m_bot->isDead()) { return; } + + // make sure pet stays by your side + uint64 pet_guid = m_bot->GetPetGUID(); + if (pet_guid>0){ + Pet* pet = ObjectAccessor::GetPet(*m_bot, pet_guid); + Unit *unit = ObjectAccessor::GetUnit(*m_bot, pet_guid); + if (unit!=NULL){ + m_bot->GetSession()->HandlePetActionHelper(unit, pet_guid, COMMAND_FOLLOW, ACT_COMMAND, 0); + m_bot->GetSession()->HandlePetActionHelper(unit, pet_guid, REACT_DEFENSIVE, ACT_REACTION, 0); + } + } + + + //If Casting or Eating/Drinking return + if (m_bot->HasUnitState(UNIT_STAT_CASTING)) { return; } + if (m_bot->getStandState() == UNIT_STAND_STATE_SIT) { return; } + + //buff and heal raid + if (DoSupportRaid(m_bot,30,0,0,0,1,1)) { return; } + + //Own Buffs + if (MOLTEN_ARMOR) { if ( CastSpell(MOLTEN_ARMOR,m_bot)) { return; } } + else if (CastSpell(MAGE_ARMOR,m_bot)) { return; } + if (CastSpell(COMBUSTION,m_bot)) { } //nogcd + if (!HasAuraName(m_bot, MANA_SHIELD)) CastSpell (MANA_SHIELD); + + //conjure food & water + Item *pItem = ai->FindDrink(); + if(pItem == NULL && ai->GetManaPercent() >= 48) + { + if (CastSpell(CONJURE_REFRESHMENT, m_bot)) { return; } + if (CastSpell(CONJURE_WATER, m_bot)) { return; } + return; + } + pItem = ai->FindFood(); + if(pItem == NULL && ai->GetManaPercent() >= 48) + { + if (CastSpell(CONJURE_REFRESHMENT, m_bot)) { return; } + if (CastSpell(CONJURE_FOOD, m_bot)) { return; } + return; + } + //Conjure mana gem?? + + //mana/hp check + //Don't bother with eating, if low on hp, just let it heal themself + if (m_bot->getRace() == (uint8) RACE_UNDEAD_PLAYER && ai->GetHealthPercent() < 75 && CastSpell(R_CANNIBALIZE,m_bot)) { return; } + if (ai->GetManaPercent() < 50 && CastSpell (EVOCATION, m_bot)) { return; } + if (ai->GetManaPercent() < 50 || ai->GetHealthPercent() < 50) { ai->Feast(); } +} //end DoNonCombatActions + + +bool PlayerbotMageAI::BuffPlayer(Unit *target) +{ + if (!target || target->isDead()) return false; + + if (target->getClass() == CLASS_WARRIOR || target->getClass() == CLASS_DEATH_KNIGHT || target->getClass() == CLASS_ROGUE) return false; + + if (!HasAuraName(target, ARCANE_INTELLECT) && !HasAuraName(target, ARCANE_BRILLIANCE) && !HasAuraName(target, DALARAN_INTELLECT) && !HasAuraName(target, DALARAN_BRILLIANCE)) + { + if (CastSpell(ARCANE_BRILLIANCE, target)) return true; + else if (CastSpell (ARCANE_INTELLECT, target)) return true; + } + return false; +} +bool PlayerbotMageAI::CureTarget(Unit *target) +{ + //Cures the target + Player *m_bot = GetPlayerBot(); + + if(!target || target->isDead()) { return false; } + if (castDispel(DISPEL_CURSE, target)) return true; + return false; +} diff --git a/src/server/game/AI/Bots/PlayerbotMageAI.h b/src/server/game/AI/Bots/PlayerbotMageAI.h new file mode 100644 index 0000000..307e519 --- /dev/null +++ b/src/server/game/AI/Bots/PlayerbotMageAI.h @@ -0,0 +1,69 @@ +#ifndef _PLAYERBOTMAGEAI_H +#define _PLAYERBOTMAGEAI_H + +#include "PlayerbotClassAI.h" + +enum +{ + SPELL_SCORCH, + SPELL_POM, + SPELL_ARCANE_POWER, + SPELL_FIREBALL, + SPELL_MISSILES, + SPELL_FROSTBOLT +}; + +//class Player; + +class PlayerbotMageAI : PlayerbotClassAI +{ + public: + PlayerbotMageAI(Player *const master, Player *const bot, PlayerbotAI *const ai); + virtual ~PlayerbotMageAI(); + + virtual void LoadSpells(); + + //all combat actions go here + void DoNextCombatManeuver(Unit *); + + //all non combat actions go here, ex buffs, heals, rezzes + void DoNonCombatActions(); + + //buff a specific player, usually a real PC who is not in group + bool BuffPlayer(Unit *target); + + //Cures the target + bool CureTarget(Unit *target); + + typedef std::set<Unit *> AttackerSet; + private: + //arcane + uint32 ARCANE_MISSILES, ARCANE_EXPLOSION, ARCANE_BLAST, ARCANE_BARRAGE; + + //fire + uint32 FIREBALL, FROSTFIRE_BOLT, FIRE_BLAST, FLAMESTRIKE, BLAST_WAVE, SCORCH, PYROBLAST, LIVING_BOMB; + + //cold + uint32 FROSTBOLT, FROST_NOVA, ICE_LANCE, BLIZZARD, CONE_OF_COLD, WATER_ELEMENTAL; + + // buffs + uint32 FROST_ARMOR, ICE_ARMOR, MAGE_ARMOR, MOLTEN_ARMOR, FIRE_WARD, FROST_WARD, MANA_SHIELD, ICE_BARRIER, POM, FOCUS_MAGIC, ARCANE_POWER, COMBUSTION, ICY_VEINS, + ARCANE_INTELLECT, ARCANE_BRILLIANCE, DALARAN_INTELLECT, DALARAN_BRILLIANCE, DAMPEN_MAGIC, AMPLIFY_MAGIC; + + //CC + uint32 POLYMORPH, DRAGONS_BREATH, DEEP_FREEZE; + + //other + uint32 CONJURE_REFRESHMENT, CONJURE_WATER, CONJURE_FOOD, CONJURE_MANA_GEM, MIRROR_IMAGE, BLINK, ICE_BLOCK, INVISIBILITY, EVOCATION, REMOVE_CURSE, COUNTER_SPELL, SLOW, SHOOT; + + //special + uint32 P_BRAIN_FREEZE, P_FIRESTARTER, P_HOT_STREAK, P_ARCANE_BLAST, P_MISSILE_BARRAGE, P_FINGERS_OF_FROST, IMP_SCORCH; + + + uint32 TALENT_ARCANE, TALENT_FIRE, TALENT_FROST; + +}; + + + +#endif diff --git a/src/server/game/AI/Bots/PlayerbotPaladinAI.cpp b/src/server/game/AI/Bots/PlayerbotPaladinAI.cpp new file mode 100644 index 0000000..fd5bcab --- /dev/null +++ b/src/server/game/AI/Bots/PlayerbotPaladinAI.cpp @@ -0,0 +1,535 @@ +#include "PlayerbotPaladinAI.h" + +class PlayerbotAI; +PlayerbotPaladinAI::PlayerbotPaladinAI(Player *const master, Player *const bot, PlayerbotAI *const ai): PlayerbotClassAI(master, bot, ai) +{ + foodDrinkSpamTimer = 0; + LoadSpells(); +} +PlayerbotPaladinAI::~PlayerbotPaladinAI(){} + +void PlayerbotPaladinAI::LoadSpells() { + PlayerbotAI *ai = GetAI(); + if (!ai) return; + #pragma region SpellId Fill + //heals + FLASH_OF_LIGHT = ai->getSpellIdExact("Flash of Light"); + HOLY_LIGHT = ai->getSpellIdExact("Holy Light"); + HOLY_SHOCK = ai->getSpellIdExact("Holy Shock"); + CLEANSE = ai->getSpellIdExact("Cleanse"); + if (!CLEANSE) CLEANSE = ai->getSpellIdExact("Purify"); + LOH = ai->getSpellIdExact("Lay on Hands"); + SACRED_SHIELD = ai->getSpellIdExact("Sacred Shield"); + BEACON_OF_LIGHT = ai->getSpellIdExact("Beacon of Light"); + DIVINE_FAVOR = ai->getSpellIdExact("Divine Favor"); + REDEMPTION = ai->getSpellIdExact("Redemption"); + + //Damages + JOL = ai->getSpellIdExact("Judgement of Light"); + JOW = ai->getSpellIdExact("Judgement of Wisdom"); + JOJ = ai->getSpellIdExact("Judgement of Justice"); + HAMMER_OF_WRATH = ai->getSpellIdExact("Hammer of Wrath"); + EXORCISM = ai->getSpellIdExact("Exorcism"); + HOLY_WRATH = ai->getSpellIdExact("Holy Wrath"); + CONSECRATION = ai->getSpellIdExact("Consecration"); + AVENGERS_SHIELD = ai->getSpellIdExact("Avenger's Shield"); + SHIELD_OF_RIGHTEOUSNESS = ai->getSpellIdExact("Shield of Righteousness"); + HOTR = ai->getSpellIdExact("Hammer of the Righteous"); + CRUSADER_STRIKE = ai->getSpellIdExact("Crusader Strike"); + DIVINE_STORM = ai->getSpellIdExact("Divine Storm"); + + //CC + HAMMER_OF_JUSTICE = ai->getSpellIdExact("Hammer of Justice"); + REPENTANCE = ai->getSpellIdExact("Repentance"); + + //Self buffs + SOL = ai->getSpellIdExact("Seal of Light"); + SOW = ai->getSpellIdExact("Seal of Wisdom"); + SOR = ai->getSpellIdExact("Seal of Righteousness"); + SOC = ai->getSpellIdExact("Seal of Command"); + SOV = ai->getSpellIdExact("Seal of Vengeance"); + if (!SOV) SOV = ai->getSpellIdExact("Seal of Corruption"); + DIVINE_PLEA = ai->getSpellIdExact("Divine Plea"); + HOLY_SHIELD = ai->getSpellIdExact("Holy Shield"); + RIGHTEOUS_FURY = ai->getSpellIdExact("Righteous Fury"); + DIVINE_SHIELD = ai->getSpellIdExact("Divine Shield"); + if (!DIVINE_SHIELD) DIVINE_SHIELD = ai->getSpellIdExact("Divine Protection"); + AVENGING_WRATH = ai->getSpellIdExact("Avenging Wrath"); + + //AURAS + DEVOTION_AURA = ai->getSpellIdExact("Devotion Aura"); + RETRIBUTION_AURA = ai->getSpellIdExact("Retribution Aura"); + CONCENTRATION_AURA = ai->getSpellIdExact("Concentration Aura"); + FIRE_AURA = ai->getSpellIdExact("Fire Resistance Aura"); + FROST_AURA = ai->getSpellIdExact("Frost Resistance Aura"); + SHADOW_AURA = ai->getSpellIdExact("Shadow Resistance Aura"); + CRUSADER_AURA = ai->getSpellIdExact("Crusader Aura"); + + //Blessings + BOW = ai->getSpellIdExact("Blessing of Wisdom"); + BOM = ai->getSpellIdExact("Blessing of Might"); + BOS = ai->getSpellIdExact("Blessing of Sanctuary"); + BOK = ai->getSpellIdExact("Blessing of Kings"); + GBOW = ai->getSpellIdExact("Greater Blessing of Wisdom"); + GBOM = ai->getSpellIdExact("Greater Blessing of Might"); + GBOS = ai->getSpellIdExact("Greater Blessing of Sanctuary"); + GBOK = ai->getSpellIdExact("Greater Blessing of Kings"); + + //Hands + HOF = ai->getSpellIdExact("Hand of Freedom"); + HOR = ai->getSpellIdExact("Hand of Reckoning"); + HOS = ai->getSpellIdExact("Hand of Salvation"); + HOP = ai->getSpellIdExact("Hand of Protection"); + DIVINE_SACRIFICE = ai->getSpellIdExact("Divine Sacrifice"); + + //Taunt + RIGHTEOUS_DEFENSE = ai->getSpellIdExact("Righteous Defense"); + + FORBEARANCE = 25771; + AOW = 53488; + + TALENT_RETRI = CRUSADER_STRIKE; + TALENT_PROT = HOLY_SHIELD; + TALENT_HOLY = HOLY_SHOCK; + + uint8 talentCounter = 0; + if (TALENT_RETRI) talentCounter++; + if (TALENT_PROT) talentCounter++; + if (TALENT_HOLY) talentCounter++; + //if (talentCounter > 1) { TALENT_RETRI = 0; TALENT_PROT = 0; TALENT_HOLY = 0; } //Unreliable Talent detection. + + #pragma endregion +} + +void PlayerbotPaladinAI::DoNextCombatManeuver(Unit *pTarget) +{ + if (!pTarget || pTarget->isDead()) return; + PlayerbotAI *ai = GetAI(); + if (!ai) return; + Player *m_bot = GetPlayerBot(); + if (!m_bot || m_bot->isDead()) return; + Unit *pVictim = pTarget->getVictim(); + Unit *m_tank = FindMainTankInRaid(GetMaster()); + if (!m_tank && m_bot->GetGroup() && GetMaster()->GetGroup() != m_bot->GetGroup()) { FindMainTankInRaid(m_bot); } + if (!m_tank) { m_tank = m_bot; } + uint32 masterHP = GetMaster()->GetHealth()*100 / GetMaster()->GetMaxHealth(); + float pDist = m_bot->GetDistance(pTarget); + uint8 pThreat = GetThreatPercent(pTarget); + + uint8 reqHeal = 0; + uint8 OwnPartyHP = GetHealthPercentRaid(m_bot, reqHeal); + + + // Fill mana if needed + if (m_bot->getRace() == (uint8) RACE_BLOODELF && pDist < 8 && ai->GetManaPercent() < 20 && CastSpell(R_ARCANE_TORRENT, pTarget)) { } //no gcd + if (ai->GetManaPercent() < 30 && CastSpell (DIVINE_PLEA, m_bot)) { return; } + + // If hp is too low divine shield + if (ai->GetHealthPercent() < 20 && (!m_bot->HasAura(DIVINE_SHIELD) || !m_bot->HasAura(HOP) || !m_bot->HasAura(SACRED_SHIELD))) + { + if (!m_bot->HasAura(FORBEARANCE)) + { + if (CastSpell(DIVINE_SHIELD,m_bot)) { return; } + if (CastSpell(HOP,m_bot)) { return; } + } + else if (CastSpell(SACRED_SHIELD,m_bot)) { return; } + } + + // if i am under attack and if i am not tank or offtank: change target if needed + if (m_tank->GetGUID() != m_bot->GetGUID() && !TALENT_PROT && isUnderAttack() ) + { + // Keep hitting but reduce threat + if (CastSpell(HOS,m_bot,true,true)) { } + //else if (m_bot->getRace() == (uint8) RACE_NIGHTELF && CastSpell(R_SHADOWMELD,m_bot)) { return; } + else //I cannot reduce threat so + { + if (pVictim && pVictim->GetGUID() == m_bot->GetGUID() && pDist <= 2) { } // My target is almost up to me, no need to search + else //Have to select nearest target + { + Unit *curAtt = GetNearestAttackerOf(m_bot); + if (curAtt && curAtt->GetGUID() != pTarget->GetGUID()) + { + m_bot->SetSelection(curAtt->GetGUID()); + //ai->AddLootGUID(curAtt->GetGUID()); + DoNextCombatManeuver(curAtt); //Restart new update to get variables fixed.. + return; + } + } + //my target is attacking me + } + } + + + #pragma region Choose Actions + // Choose actions accoring to talents + if (m_tank->GetGUID() == m_bot->GetGUID()) // Hey! I am Main Tank + { + if (TALENT_PROT) { m_role=BOT_ROLE_TANK; } //Just Keep Tanking + else + { + if (TALENT_RETRI) { + if ((ai->GetHealthPercent() <= 40 || masterHP <40 ) && (ai->GetManaPercent() >= 40)) { m_role = BOT_ROLE_SUPPORT; } + else if (OwnPartyHP < 40 && ai->GetManaPercent() >= 30) { m_role = BOT_ROLE_SUPPORT; } + else { m_role = BOT_ROLE_TANK; } //have no shield but can tank if you think so + } + else if (TALENT_HOLY) //I am both healer and tank?? Hmm + { + if ((ai->GetHealthPercent() <= 70 || masterHP <70 ) && (ai->GetManaPercent() >= 50))m_role = BOT_ROLE_SUPPORT; + else if (OwnPartyHP < 20 && ai->GetManaPercent() >= 30) { m_role = BOT_ROLE_SUPPORT; } + else m_role = BOT_ROLE_TANK; + } + else { m_role = BOT_ROLE_TANK; } //Unknown build or low level + } + } + else if (TALENT_RETRI) { + if ((ai->GetHealthPercent() <= 40 || masterHP <40 ) && (ai->GetManaPercent() >= 40)) { m_role = BOT_ROLE_SUPPORT; } + else if (OwnPartyHP < 40 && ai->GetManaPercent() >= 30) {m_role = BOT_ROLE_SUPPORT;} + else { m_role = BOT_ROLE_DPS_MELEE; } + } + else if (TALENT_PROT) { + if ((ai->GetHealthPercent() <= 30 || masterHP <40 ) && (ai->GetManaPercent() >= 20)) { m_role = BOT_ROLE_SUPPORT; } + else if (OwnPartyHP < 40 && ai->GetManaPercent() >= 40) { m_role = BOT_ROLE_SUPPORT; } + else { m_role = BOT_ROLE_OFFTANK; } + } + else if (TALENT_HOLY) { m_role = BOT_ROLE_SUPPORT; } + else { m_role = BOT_ROLE_DPS_MELEE; } //Unknown build or low level.. Mainly attack + + //takepos + if (m_role == BOT_ROLE_SUPPORT || m_role == BOT_ROLE_HEALER) TakePosition(pTarget,BOT_ROLE_DPS_MELEE,0.5f); + else TakePosition(pTarget,m_role); + + // If there's a cast stop + if(m_bot->HasUnitState(UNIT_STAT_CASTING)) return; + + Unit *target = DoSelectLowestHpFriendly(40, 1000); + switch(m_role) + { + #pragma region BOT_ROLE_SUPPORT + case BOT_ROLE_SUPPORT: + + ChangeAura(CONCENTRATION_AURA); + if (!TALENT_PROT && m_tank->GetGUID() != m_bot->GetGUID()) m_bot->RemoveAurasDueToSpell(RIGHTEOUS_FURY); + // Choose Seal + if (SOW && ai->GetManaPercent() <= 30) { if (CastSpell(SOW,m_bot)) { return; } } + else if (m_bot->HasAura(SOW) && ai->GetManaPercent() < 85) { } // Paladin was striving for mana, keep until he got most of his mana back + else if(SOL && ai->GetHealthPercent() < 40) { if(CastSpell(SOL,m_bot)) { return; } } + else if(CastSpell(SOR, m_bot)) { return; } + + if (!m_bot->HasAura(FORBEARANCE) && CastSpell(AVENGING_WRATH,m_bot)) { } // no gcd + + if (DoSupportRaid(m_bot)) { return; } + //heal pets and bots + if(target && target->isAlive() && HealTarget(target, target->GetHealth()*100 / target->GetMaxHealth()) ) { return; } + + if (ai->GetManaPercent() <= 80 && CastSpell(JOW,pTarget,true,true)) { return; } + + // Use Spells only if mana is sufficient.. + if(ai->GetManaPercent() < offensiveSpellThreshold ) return; + + break; + #pragma endregion + + #pragma region BOT_ROLE_TANK / BOT_ROLE_OFFTANK + case BOT_ROLE_TANK: + case BOT_ROLE_OFFTANK: + + ChangeAura(DEVOTION_AURA); + if (CastSpell(RIGHTEOUS_FURY,m_bot)) { return; } + // Choose Seal + if (SOW && ai->GetManaPercent() <= 30) { if (CastSpell(SOW,m_bot)) { return; } } + else if (m_bot->HasAura(SOW) && ai->GetManaPercent() < 85) { } // Paladin was striving for mana, keep until he got most of his mana back + else if (SOL && ai->GetHealthPercent() < 40) { if (CastSpell(SOL,m_bot)) { return; } } + else if (CastSpell(SOR,m_bot)) { return; } + + // We are tank/offtank threat is not an issiue; + // Use taunts only if helping target is not main tank.. + // Taunt if needed (Only for master) + if(GetMaster()->GetGUID() != m_tank->GetGUID()) + { + // Taunt if needed (Only for master) + Unit *curAtt = GetAttackerOf(GetMaster()); + if (curAtt) + { + if (isUnderAttack(GetMaster(),2) && CastSpell(RIGHTEOUS_DEFENSE, GetMaster())) { return; } + if (CastSpell(HOR, curAtt,true,true)) { } //No GCD + } + } + // My target is not attacking me, taunt.. + if ( m_tank->GetGUID() == m_bot->GetGUID() && pVictim && pVictim->GetGUID() != m_bot->GetGUID() && CastSpell(HOR, pTarget,true,true) ) { } //NO GCD + + // Tank specials + if (TALENT_PROT && ai->GetManaPercent() < 90 && CastSpell (DIVINE_PLEA, m_bot)) { return; } //Prot paladin always uses this.. + if (pVictim && pVictim->GetGUID() == m_bot->GetGUID() && CastSpell(HOLY_SHIELD,m_bot)) { return; } + if (CastSpell(AVENGERS_SHIELD,pTarget,true,true)) { return; } + if (CastSpell(HOTR,pTarget,true,true)) { return; } + if (CastSpell(HOLY_WRATH,pTarget,true,true)){ return; } + if (CastSpell(CONSECRATION,pTarget)) { return; } + if (m_bot->getRace() == (uint8) RACE_DWARF && CastSpell(R_STONEFORM,m_bot)) { return; } + + if (DoSupportRaid(m_bot)) { return; } + //heal pets and bots + if(target && target->isAlive() && HealTarget(target, target->GetHealth()*100 / target->GetMaxHealth()) ) { return; } + + + break; + #pragma endregion + + #pragma region BOT_ROLE_DPS_MELEE + case BOT_ROLE_DPS_MELEE: + + ChangeAura(RETRIBUTION_AURA); + if (!TALENT_PROT && m_tank->GetGUID() != m_bot->GetGUID()) m_bot->RemoveAurasDueToSpell(RIGHTEOUS_FURY); + if (CastSpell(SOV,m_bot)) { return; } + + if (CastSpell (HAMMER_OF_JUSTICE, pTarget)) { return; } + if (!m_bot->HasAura(FORBEARANCE) && CastSpell(AVENGING_WRATH,m_bot)) {} //no gcd + if (CastSpell(JOW,pTarget)) { return; } + if (CastSpell(DIVINE_STORM, pTarget)) { return; } + if (CastSpell(CRUSADER_STRIKE, pTarget)) { return; } + if (GetAI()->GetHealthPercent(*pTarget)<20 && CastSpell(HAMMER_OF_WRATH, pTarget)) { return; } + if (CastSpell(CONSECRATION,pTarget)) { return; } + if (m_bot->HasAura(AOW) && CastSpell(EXORCISM,pTarget)) { return; } + if (CastSpell(HOLY_WRATH,pTarget)) { return; } + + break; + #pragma endregion + + } + #pragma region PaladinCommon + // Shared dps spells + if (pTarget->GetCreatureType() == (uint32) CREATURE_TYPE_HUMANOID && pTarget->IsNonMeleeSpellCasted(true) && CastSpell (REPENTANCE, pTarget)) { return; } + if (m_bot->getRace() == (uint8) RACE_BLOODELF && pDist < 8 && pTarget->IsNonMeleeSpellCasted(true) && CastSpell(R_ARCANE_TORRENT, pTarget)) { } //no gcd + if (m_bot->getRace() == (uint8) RACE_TROLL && CastSpell(R_BERSERKING,m_bot)) {} // no GCD + if (m_bot->getRace() == (uint8) RACE_ORC && CastSpell(R_BLOOD_FURY,m_bot)) {} // no GCD + + // If at threat limit, stop + if(pThreat > threatThreshold && !TALENT_PROT && m_tank->GetGUID() != m_bot->GetGUID() && !isUnderAttack()) + { + if (m_tank->getVictim() && m_tank->getVictim()->GetGUID() != pTarget->GetGUID()) // I am attacking wrong target!! + { + m_bot->SetSelection(m_tank->getVictim()->GetGUID()); + return; + } + else + { + if (CastSpell(HOS,m_bot)) { return; } //Lets see if we can manage with HOS + else { return; } //use no spells and wait threat to be reduced + } + } + // Continue attacking if theres excess mana (for healers) + if (m_role == BOT_ROLE_SUPPORT && ai->GetManaPercent() < offensiveSpellThreshold) { return; } + + if (GetAI()->GetHealthPercent(*pTarget)<20 && CastSpell(HAMMER_OF_WRATH, pTarget,true,true)) { return; } //no gcd but cast + if (CastSpell (HAMMER_OF_JUSTICE, pTarget)) { return; } + if (CanCast(JOW,pTarget,true) && + ( ( ai->GetManaPercent() <= 70 && ai->GetHealthPercent() > 90) + || ( ai->GetManaPercent() <= 50 && ai->GetHealthPercent() > 75) + || ( ai->GetManaPercent() <= 20 && ai->GetHealthPercent() > 20) ) + && CastSpell(JOW,pTarget,false)) { return; } + else if (CastSpell(JOL,pTarget),true,true) { return; } + if (CastSpell(SHIELD_OF_RIGHTEOUSNESS,pTarget,true,true)) { return; } + if (CastSpell (DIVINE_STORM, pTarget,true,true)) { return; } + if (CastSpell (CRUSADER_STRIKE, pTarget,true,true)) { return; } + if (m_bot->getRace() == (uint8) RACE_TAUREN && pDist < 8 && CastSpell(R_WAR_STOMP, pTarget)) { return; } //no GCD but cast + if (isUnderAttack(m_tank,4) && CastSpell(HOLY_WRATH,pTarget,true,true)) { return; } + if (isUnderAttack(m_tank,4) && CastSpell(CONSECRATION,pTarget)) { return; } + if (CastSpell(HOLY_SHOCK,pTarget,true,true)) { return; } + if (m_role != BOT_ROLE_SUPPORT && ai->GetManaPercent() > 60 && OwnPartyHP < 65 && DoSupportRaid(m_bot)) { return; } //if there is spare time and mana, do healz and other stuff.. + else if (m_role != BOT_ROLE_SUPPORT && ai->GetManaPercent() > 30 && DoSupportRaid(m_bot,30,false,false,false,true,false)) { return; } + if (CastSpell(EXORCISM,pTarget,true,true)) { return; } + + + // drink potion if support / healer (Other builds simply overuse mana and waste mana pots) + if(ai->GetManaPercent() < 5 && (m_role == BOT_ROLE_SUPPORT || m_role == BOT_ROLE_HEALER) ) + { + Item *pItem = ai->FindPotion(); + if(pItem != NULL) + { + if (pItem->GetSpell() && m_bot->HasSpellCooldown(pItem->GetSpell()) ) { return; } //pot is in cooldown + ai->UseItem(*pItem); + } + } + #pragma endregion + +} //end DoNextCombatManeuver + +void PlayerbotPaladinAI::DoNonCombatActions() +{ + PlayerbotAI *ai = GetAI(); + Player *m_bot = GetPlayerBot(); + if (!m_bot || !ai || m_bot->isDead()) { return; } + + //If Casting or Eating/Drinking return + if (m_bot->HasUnitState(UNIT_STAT_CASTING)) { return; } + if (m_bot->getStandState() == UNIT_STAND_STATE_SIT) { return; } + + //buff and heal raid + if (DoSupportRaid(m_bot)) { return; } + + //heal pets and bots + Unit *target = DoSelectLowestHpFriendly(40, 1000); + if (target && target->isAlive() && HealTarget(target, target->GetHealth()*100 / target->GetMaxHealth())) { return; } + + //mana/hp check + //Don't bother with eating, if low on hp, just let it heal themself + if (m_bot->getRace() == (uint8) RACE_UNDEAD_PLAYER && ai->GetHealthPercent() < 75 && CastSpell(R_CANNIBALIZE,m_bot)) { return; } + if (m_bot->GetHealth() < m_bot->GetMaxHealth() && CastSpell(FLASH_OF_LIGHT,m_bot)) { return; } + if (ai->GetManaPercent() < 70) { ai->Feast(); } +} //end DoNonCombatActions + +bool PlayerbotPaladinAI::HealTarget(Unit *target, uint8 hp) +{ + if(!target || target->isDead()) return false; + Player *m_bot = GetPlayerBot(); + + if(hp < 10 && m_bot->isInCombat() && CastSpell(LOH, target)) { return true; } + if(hp < 10 && m_bot->isInCombat() && CastSpell(SACRED_SHIELD,target)) { return true; } + if(hp < 15 && m_bot->isInCombat() && CastSpell(HOP,target)) { return true; } + if(hp < 20 && m_bot->isInCombat() && CastSpell(BEACON_OF_LIGHT,target)) { return true; } + if(hp < 30 && CastSpell(HOLY_SHOCK,target,true,true,true)) { return true; } + if(hp < 30 && m_bot->isInCombat() && CanCast(DIVINE_FAVOR,m_bot,true) && CanCast(HOLY_LIGHT,target,true) ) { CastSpell(DIVINE_FAVOR, m_bot,false); return CastSpell(HOLY_LIGHT,target,false); } //No gcd + if(hp < 30 && CastSpell(FLASH_OF_LIGHT,target,true,true)) { return true; } + if(hp < 40 && m_bot->getRace() == (uint8) RACE_DRAENEI && CastSpell(R_GIFT_OF_NAARU,target)) { return true; } // no GCD but has cast + if(hp < 65 && CastSpell(HOLY_LIGHT,target,true,true)) { return true; } + if(hp < 85 && CastSpell(FLASH_OF_LIGHT,target,true,true)) { return true; } + if(hp < 95 && m_bot->isInCombat() && CastSpell(BEACON_OF_LIGHT,target)) { return true; } + + return false; +} //end HealTarget + +bool PlayerbotPaladinAI::CureTarget(Unit *target) +{ + if (!target || target->isDead()) { return false; } + if (castDispel(CLEANSE, target)) { return true; } + return false; + +} //end CureTarget + +bool PlayerbotPaladinAI::BuffPlayer(Unit *target) +{ + if(!target || target->isDead()) return false; + Player *m_bot = GetPlayerBot(); + + // Check if target already has a blessing by me.. + if (HasAuraName(target,BOW,m_bot->GetGUID()) || + HasAuraName(target,BOK,m_bot->GetGUID()) || + HasAuraName(target,BOM,m_bot->GetGUID()) || + HasAuraName(target,BOS,m_bot->GetGUID()) || + HasAuraName(target,GBOW,m_bot->GetGUID()) || + HasAuraName(target,GBOK,m_bot->GetGUID()) || + HasAuraName(target,GBOM,m_bot->GetGUID()) || + HasAuraName(target,GBOS,m_bot->GetGUID()) + ) return false; + +#pragma region Choose Buff > Class + switch(target->getClass()) + { + case CLASS_MAGE: + case CLASS_WARLOCK: + if (CanCast(GBOW,target) && !HasAuraName(target,BOW) && !HasAuraName(target,GBOW) ) return CastSpell(GBOW,target,false); + else if (CanCast(GBOK,target) && !HasAuraName(target,BOK) && !HasAuraName(target,GBOK) ) return CastSpell(GBOK,target,false); + else if (CanCast(GBOS,target) && !HasAuraName(target,BOS) && !HasAuraName(target,GBOS) ) return CastSpell(GBOS,target,false); + break; + case CLASS_PRIEST: + if (CanCast(GBOK,target) && !HasAuraName(target,BOK) && !HasAuraName(target,GBOK) ) return CastSpell(GBOK,target,false); + else if (CanCast(GBOW,target) && !HasAuraName(target,BOW) && !HasAuraName(target,GBOW) ) return CastSpell(GBOW,target,false); + else if (CanCast(GBOS,target) && !HasAuraName(target,BOS) && !HasAuraName(target,GBOS) ) return CastSpell(GBOS,target,false); + break; + case CLASS_HUNTER: + if (CanCast(GBOM,target) && !HasAuraName(target,BOM) && !HasAuraName(target,GBOM) ) return CastSpell(GBOM,target,false); + else if (CanCast(GBOK,target) && !HasAuraName(target,BOK) && !HasAuraName(target,GBOK) ) return CastSpell(GBOK,target,false); + else if (CanCast(GBOW,target) && !HasAuraName(target,BOW) && !HasAuraName(target,GBOW) ) return CastSpell(GBOW,target,false); + else if (CanCast(GBOS,target) && !HasAuraName(target,BOS) && !HasAuraName(target,GBOS) ) return CastSpell(GBOS,target,false); + break; + case CLASS_ROGUE: + if (CanCast(GBOM,target) && !HasAuraName(target,BOM) && !HasAuraName(target,GBOM) ) return CastSpell(GBOM,target,false); + else if (CanCast(GBOK,target) && !HasAuraName(target,BOK) && !HasAuraName(target,GBOK) ) return CastSpell(GBOK,target,false); + else if (CanCast(GBOS,target) && !HasAuraName(target,BOS) && !HasAuraName(target,GBOS) ) return CastSpell(GBOS,target,false); + break; + case CLASS_WARRIOR: + case CLASS_DEATH_KNIGHT: + if (target->GetUnitDodgeChance() + target->GetUnitParryChance() > 40) + { + if (CanCast(GBOS,target) && !HasAuraName(target,BOS) && !HasAuraName(target,GBOS) ) return CastSpell(GBOS,target,false); + else if (CanCast(GBOK,target) && !HasAuraName(target,BOK) && !HasAuraName(target,GBOK) ) return CastSpell(GBOK,target,false); + else if (CanCast(GBOM,target) && !HasAuraName(target,BOM) && !HasAuraName(target,GBOM) ) return CastSpell(GBOM,target,false); + } + else + { + if (CanCast(GBOK,target) && !HasAuraName(target,BOK) && !HasAuraName(target,GBOK) ) return CastSpell(GBOK,target,false); + else if (CanCast(GBOM,target) && !HasAuraName(target,BOM) && !HasAuraName(target,GBOM) ) return CastSpell(GBOM,target,false); + else if (CanCast(GBOS,target) && !HasAuraName(target,BOS) && !HasAuraName(target,GBOS) ) return CastSpell(GBOS,target,false); + } + break; + case CLASS_DRUID: + case CLASS_SHAMAN: + case CLASS_PALADIN: + if (target->GetMaxPower(target->getPowerType()) > target->GetMaxHealth()) + { + if (CanCast(GBOW,target) && !HasAuraName(target,BOW) && !HasAuraName(target,GBOW) ) return CastSpell(GBOW,target,false); + else if (CanCast(GBOK,target) && !HasAuraName(target,BOK) && !HasAuraName(target,GBOK) ) return CastSpell(GBOK,target,false); + else if (CanCast(GBOS,target) && !HasAuraName(target,BOS) && !HasAuraName(target,GBOS) ) return CastSpell(GBOS,target,false); + else if (CanCast(GBOM,target) && !HasAuraName(target,BOM) && !HasAuraName(target,GBOM) ) return CastSpell(GBOM,target,false); + } + else if (target->GetUnitDodgeChance() + target->GetUnitParryChance() > 40) + { + if (CanCast(GBOS,target) && !HasAuraName(target,BOS) && !HasAuraName(target,GBOS) ) return CastSpell(GBOS,target,false); + else if (CanCast(GBOK,target) && !HasAuraName(target,BOK) && !HasAuraName(target,GBOK) ) return CastSpell(GBOK,target,false); + else if (CanCast(GBOM,target) && !HasAuraName(target,BOM) && !HasAuraName(target,GBOM) ) return CastSpell(GBOM,target,false); + else if (CanCast(GBOW,target) && !HasAuraName(target,BOW) && !HasAuraName(target,GBOW) ) return CastSpell(GBOW,target,false); + } + else + { + if (CanCast(GBOK,target) && !HasAuraName(target,BOK) && !HasAuraName(target,GBOK) ) return CastSpell(GBOK,target,false); + else if (CanCast(GBOM,target) && !HasAuraName(target,BOM) && !HasAuraName(target,GBOM) ) return CastSpell(GBOM,target,false); + else if (CanCast(GBOW,target) && !HasAuraName(target,BOW) && !HasAuraName(target,GBOW) ) return CastSpell(GBOW,target,false); + else if (CanCast(GBOS,target) && !HasAuraName(target,BOS) && !HasAuraName(target,GBOS) ) return CastSpell(GBOS,target,false); + } + break; + + default: + if (CanCast(GBOK,target) && !HasAuraName(target,BOK) && !HasAuraName(target,GBOK) ) return CastSpell(GBOK,target,false); + else if (CanCast(GBOM,target) && !HasAuraName(target,BOM) && !HasAuraName(target,GBOM) ) return CastSpell(GBOM,target,false); + else if (CanCast(GBOW,target) && !HasAuraName(target,BOW) && !HasAuraName(target,GBOW) ) return CastSpell(GBOW,target,false); + else if (CanCast(GBOS,target) && !HasAuraName(target,BOS) && !HasAuraName(target,GBOS) ) return CastSpell(GBOS,target,false); + break; + } +#pragma endregion + + return false; +} + +bool PlayerbotPaladinAI::RezTarget (Unit *target) +{ + if(!target || target->isAlive()) return false; + Player *m_bot = GetPlayerBot(); + if (target->IsNonMeleeSpellCasted(true)) { return false; } //Already resurrected + if (m_bot->isInCombat()) { return false; } + + if (!CanCast(REDEMPTION,target)) return false; + std::string msg = "Rezzing "; + msg += target->GetName(); + GetPlayerBot()->Say(msg, LANG_UNIVERSAL); + return CastSpell(REDEMPTION, target, false); +} + +bool PlayerbotPaladinAI::ChangeAura(uint32 aura) +{ + Player *m_bot = GetPlayerBot(); + if(!aura) return false; + + if(!CanCast(aura,m_bot)) return false; + + if(m_bot->HasAura(aura)) + { + if (aura == DEVOTION_AURA) + { + if (ChangeAura(FIRE_AURA)) return true; + if (ChangeAura(FROST_AURA)) return true; + if (ChangeAura(SHADOW_AURA)) return true; + return true; + } + else return ChangeAura(DEVOTION_AURA); + } + return CastSpell(aura,m_bot,false); +} diff --git a/src/server/game/AI/Bots/PlayerbotPaladinAI.h b/src/server/game/AI/Bots/PlayerbotPaladinAI.h new file mode 100644 index 0000000..72c6beb --- /dev/null +++ b/src/server/game/AI/Bots/PlayerbotPaladinAI.h @@ -0,0 +1,70 @@ +#ifndef _PLAYERBOTPALADINAI_H +#define _PLAYERBOTPALADINAI_H + +#include "PlayerbotClassAI.h" +#include "SharedDefines.h" + +class PlayerbotPaladinAI : PlayerbotClassAI +{ + public: + PlayerbotPaladinAI(Player *const master, Player *const bot, PlayerbotAI *const ai); + virtual ~PlayerbotPaladinAI(); + + virtual void LoadSpells(); + + //all combat actions go here + void DoNextCombatManeuver(Unit *); + + //all non combat actions go here, ex buffs, heals, rezzes + void DoNonCombatActions(); + + //buff a specific player, usually a real PC who is not in group + bool BuffPlayer(Unit *target); + + //Heals the target based off its HP + bool HealTarget(Unit *target, uint8 hp); + + //Cures the target + bool CureTarget(Unit *target); + + bool RezTarget (Unit *target); + + bool ChangeAura(uint32 aura); + + private: + //heals + uint32 FLASH_OF_LIGHT, HOLY_LIGHT, HOLY_SHOCK, REZZ, CLEANSE, LOH, SACRED_SHIELD, BEACON_OF_LIGHT, DIVINE_FAVOR, REDEMPTION; + + //Damages + uint32 JOL, JOW, JOJ, HAMMER_OF_WRATH, EXORCISM, HOLY_WRATH, CONSECRATION, AVENGERS_SHIELD, SHIELD_OF_RIGHTEOUSNESS, HOTR, CRUSADER_STRIKE, DIVINE_STORM; + + //CC + uint32 HAMMER_OF_JUSTICE, REPENTANCE; + + //Self buffs + uint32 SOL, SOW, SOR, SOC, SOV, DIVINE_PLEA, HOLY_SHIELD, RIGHTEOUS_FURY, DIVINE_SHIELD, AVENGING_WRATH; + + //AURAS + uint32 DEVOTION_AURA, RETRIBUTION_AURA, CONCENTRATION_AURA, FIRE_AURA, FROST_AURA, SHADOW_AURA, CRUSADER_AURA ; + + //Blessings + uint32 BOW, BOM, BOS, BOK, GBOW, GBOM, GBOS, GBOK; + + //Hands + uint32 HOF, HOR, HOS, HOP, DIVINE_SACRIFICE; + + //Taunt + uint32 RIGHTEOUS_DEFENSE; + + uint32 FORBEARANCE; + + uint32 TALENT_HOLY, TALENT_PROT, TALENT_RETRI; + + //procs + uint32 AOW; + +}; + + + +#endif diff --git a/src/server/game/AI/Bots/PlayerbotPriestAI.cpp b/src/server/game/AI/Bots/PlayerbotPriestAI.cpp new file mode 100644 index 0000000..729316f --- /dev/null +++ b/src/server/game/AI/Bots/PlayerbotPriestAI.cpp @@ -0,0 +1,400 @@ +/* +Name : PlayerbotPriest.cpp +Complete: maybe around 55% + +Limitations: - Talent build decision is made by key talent spells, which makes them viable only after level 50-ish.. Behaviour does not change though.. + - Holy And Disc builds do not cast any offensive spells requiring cast time..(To compensate for the fact that Healing decision is not that intelligent) + - Priest breaks her own CCs.. Need a check for bots to not attack CC ed mobs.. + - Wand usage is not very smooth.. + + +Authors : SwaLLoweD +Version : 0.40 +*/ +#include "PlayerbotPriestAI.h" + +class PlayerbotAI; +PlayerbotPriestAI::PlayerbotPriestAI(Player *const master, Player *const bot, PlayerbotAI *const ai): PlayerbotClassAI(master, bot, ai) +{ + foodDrinkSpamTimer = 0; + LoadSpells(); +} +PlayerbotPriestAI::~PlayerbotPriestAI(){} + +void PlayerbotPriestAI::LoadSpells() { + PlayerbotAI *ai = GetAI(); + if (!ai) return; + #pragma region SpellId Fill + //heals + RENEW = ai->getSpellIdExact("Renew"); + FLASH_HEAL = ai->getSpellIdExact("Flash Heal"); + if (!FLASH_HEAL) FLASH_HEAL = ai->getSpellIdExact("Lesser Heal"); + HEAL = ai->getSpellIdExact("Greater Heal"); + if (!HEAL) HEAL = ai->getSpellIdExact("Heal"); + if (!HEAL) HEAL = FLASH_HEAL; + BINDING_HEAL = ai->getSpellIdExact("Binding Heal"); + PO_MENDING = ai->getSpellIdExact("Prayer of Mending"); + DESPERATE_PRAYER = ai->getSpellIdExact("Desperate Prayer"); + PO_HEALING = ai->getSpellIdExact("Prayer of Healing"); + CIRCLE_OF_HEALING = ai->getSpellIdExact("Circle of Healing"); + DIVINE_HYMN = ai->getSpellIdExact("Divine Hymn"); + RESURRECTION = ai->getSpellIdExact("Resurrection"); + HYMN_OF_HOPE = ai->getSpellIdExact("Hymn of Hope"); + CURE_DISEASE = ai->getSpellIdExact("Abolish Disease"); + if (!CURE_DISEASE) CURE_DISEASE = ai->getSpellIdExact("Abolish Disease"); + DISPEL_MAGIC = ai->getSpellIdExact("Dispel Magic"); + MASS_DISPEL = ai->getSpellIdExact("Mass Dispel"); + + //Holy Offensive + SMITE = ai->getSpellIdExact("Smite"); + HOLY_FIRE = ai->getSpellIdExact("Holy Fire"); + PENANCE = ai->getSpellIdExact("Penance"); + HOLY_NOVA = ai->getSpellIdExact("Holy Nova"); + + //Shadow Offensive + MIND_BLAST = ai->getSpellIdExact("Mind Blast"); + SW_PAIN = ai->getSpellIdExact("Shadow Word: Pain"); + DEVOURING_PLAGUE = ai->getSpellIdExact("Devouring Plague"); + MIND_FLAY = ai->getSpellIdExact("Mind Flay"); + VAMPIRIC_EMBRACE = ai->getSpellIdExact("Vampiric Embrace"); + VAMPIRIC_TOUCH = ai->getSpellIdExact("Vampiric Touch"); + SW_DEATH = ai->getSpellIdExact("Shadow Word: Death"); + MIND_SEAR = ai->getSpellIdExact("Mind Sear"); + MANA_BURN = ai->getSpellIdExact("Mana Burn"); + SHADOWFIEND = ai->getSpellIdExact("Shadowfiend"); + + //CC - Breaker + PSYCHIC_SCREAM = ai->getSpellIdExact("Psychic Scream"); + PSYCHIC_HORROR = ai->getSpellIdExact("Psychic Horror"); + MIND_SOOTHE = ai->getSpellIdExact("Mind Soothe"); + SHACKLE_UNDEAD = ai->getSpellIdExact("Shackle Undead"); + SILENCE = ai->getSpellIdExact("Silence"); + MIND_CONTROL = ai->getSpellIdExact("Mind Control"); + + //buffs + PW_SHIELD = ai->getSpellIdExact("Power Word: Shield"); + INNER_FIRE = ai->getSpellIdExact("Inner Fire"); + GUARDIAN_SPIRIT = ai->getSpellIdExact("Guardian Spirit"); + FADE = ai->getSpellIdExact("Fade"); + INNER_FOCUS = ai->getSpellIdExact("Inner Focus"); + POWER_INFUSION = ai->getSpellIdExact("Power Infusion"); + PAIN_SUPPRESSION = ai->getSpellIdExact("Pain Suppression"); + SHADOWFORM = ai->getSpellIdExact("Shadowform"); + DISPERSION = ai->getSpellIdExact("Dispersion"); + LIGHTWELL = ai->getSpellIdExact("Lightwell"); + + PW_FORTITUDE = ai->getSpellIdExact("Power Word: Fortitude"); + DIVINE_SPIRIT = ai->getSpellIdExact("Divine Spirit"); + SHADOW_PROTECTION = ai->getSpellIdExact("Shadow Protection"); + PO_FORTITUDE = ai->getSpellIdExact("Prayer of Fortitude"); + PO_SPIRIT = ai->getSpellIdExact("Prayer of Spirit"); + PO_SHADOW_PROTECTION = ai->getSpellIdExact("Prayer of Shadow Protection"); + FEAR_WARD = ai->getSpellIdExact("Fear Ward"); + + SHOOT = ai->getSpellIdExact("Shoot"); + + TALENT_DISC = PAIN_SUPPRESSION; + TALENT_HOLY = CIRCLE_OF_HEALING; + TALENT_SHADOW = SHADOWFORM; + + uint8 talentCounter = 0; + if (TALENT_DISC) talentCounter++; + if (TALENT_HOLY) talentCounter++; + if (TALENT_SHADOW) talentCounter++; + if (talentCounter > 1) { TALENT_DISC = 0; TALENT_HOLY = 0; TALENT_SHADOW = 0; } //Unreliable Talent detection. + #pragma endregion +} + +void PlayerbotPriestAI::DoNextCombatManeuver(Unit *pTarget) +{ + if (!pTarget || pTarget->isDead()) return; + PlayerbotAI *ai = GetAI(); + if (!ai) return; + Player *m_bot = GetPlayerBot(); + if (!m_bot || m_bot->isDead()) return; + Unit *pVictim = pTarget->getVictim(); + Unit *m_tank = FindMainTankInRaid(GetMaster()); + if (!m_tank && m_bot->GetGroup() && GetMaster()->GetGroup() != m_bot->GetGroup()) { FindMainTankInRaid(m_bot); } + if (!m_tank) { m_tank = m_bot; } + uint32 masterHP = GetMaster()->GetHealth()*100 / GetMaster()->GetMaxHealth(); + float pDist = m_bot->GetDistance(pTarget); + uint8 pThreat = GetThreatPercent(pTarget); + + uint8 reqHeal = 0; + uint8 OwnPartyHP = GetHealthPercentRaid(m_bot, reqHeal); + + /* + switch(ai->GetScenarioType()) + { + case SCENARIO_DUEL: + (ai->HasAura(SCREAM, pTarget) && ai->GetHealthPercent() < 60 && CastSpell(HEAL)) || + CastSpell(PAIN) || + (ai->GetHealthPercent() < 80 && CastSpell(RENEW)) || + (m_bot->GetDistance(pTarget) <= 5 && CastSpell(SCREAM)) || + CastSpell(MIND_BLAST) || + (ai->GetHealthPercent() < 20 && CastSpell(FLASH_HEAL)) || + CastSpell(SMITE); + return; + }*/ + + + //------- Non Duel combat ---------- + + + // Cast CC breakers if any match found (include any dispels first) does not work yet + //uint32 ccSpells[6] = { DISPEL_MAGIC, CURE_DISEASE, DISPERSION, R_ESCAPE_ARTIST, R_EVERY_MAN_FOR_HIMSELF, R_WILL_OF_FORSAKEN, R_STONEFORM }; + //if (ai->GetManaPercent() < 35) { ccSpells[0] = 0; ccSpells[1] = 0; } //We dont have any mana to waste... + //if (castSelfCCBreakers(ccSpells)) { } // Most of them don't trigger gcd + + + + #pragma region Choose Actions + // Choose actions accoring to talents + if (m_tank->GetGUID() == m_bot->GetGUID()) // Hey! I am Main Tank + { + m_role = BOT_ROLE_DPS_RANGED; + } + else if (TALENT_SHADOW) { + if ((ai->GetHealthPercent() <= 40 || masterHP <40 ) && (ai->GetManaPercent() >= 30)) { m_role = BOT_ROLE_SUPPORT; } + else if (OwnPartyHP < 40 && ai->GetManaPercent() >= 40) {m_role = BOT_ROLE_SUPPORT;} + else { m_role = BOT_ROLE_DPS_RANGED; } + } + else { m_role = BOT_ROLE_SUPPORT; } //Unknown build or low level.. Mainly attack + + // if i am under attack and if i am not tank or offtank: change target if needed + if (m_tank->GetGUID() != m_bot->GetGUID() && isUnderAttack() ) + { + // Keep hitting but reduce threat + if (CastSpell(FADE,m_bot)) { return; } + else if (CastSpell(PAIN_SUPPRESSION,m_bot)) { return; } + //else if (m_bot->getRace() == (uint8) RACE_NIGHTELF && CastSpell(R_SHADOWMELD,m_bot)) { return; } + else //I cannot reduce threat so + { + if (pVictim && pVictim->GetGUID() == m_bot->GetGUID() && pDist <= 2) { } // My target is almost up to me, no need to search + else //Have to select nearest target + { + Unit *curAtt = GetNearestAttackerOf(m_bot); + if (curAtt && curAtt->GetGUID() != pTarget->GetGUID()) + { + m_bot->SetSelection(curAtt->GetGUID()); + //ai->AddLootGUID(curAtt->GetGUID()); + DoNextCombatManeuver(curAtt); //Restart new update to get variables fixed.. + return; + } + } + //my target is attacking me + } + } + #pragma endregion + + TakePosition(pTarget); + // If there's a cast stop + if (m_bot->HasUnitState(UNIT_STAT_CASTING)) { return; } + + + if (m_role == BOT_ROLE_DPS_RANGED && CastSpell(SHADOWFORM,m_bot)) { return; } + if (m_role == BOT_ROLE_SUPPORT && ai->GetForm() == FORM_SHADOW) { m_bot->RemoveAurasDueToSpell(SHADOWFORM); } + + //Buff + if (CastSpell(INNER_FIRE,m_bot)) { } //nogcd + if (CastSpell(POWER_INFUSION,m_bot)) { return; } + if (m_bot->getRace() == (uint8) RACE_TROLL && CastSpell(R_BERSERKING,m_bot)) {} //no GCD + if (m_bot->getRace() == (uint8) RACE_ORC && CastSpell(R_BLOOD_FURY,m_bot)) {} //no GCD + + + if (ai->GetForm() != FORM_SHADOW) + { + if (PO_MENDING && ai->GetHealthPercent(*m_tank) < 90 && !HasAuraName(m_tank, "Prayer of Mending") && CastSpell(PO_MENDING,m_tank)) { return; } //MEND tank first + if (DoSupportRaid(m_bot)) { return; } + //heal pets and bots + Unit *target = DoSelectLowestHpFriendly(30, 1000); + if(target && target->isAlive() && HealTarget(target, target->GetHealth()*100 / target->GetMaxHealth()) ) { return; } + } + if (ai->GetForm() == FORM_SPIRITOFREDEMPTION) { return; } //You're dead.. + + + //PROTECT + if (m_tank->GetGUID() != m_bot->GetGUID() && pVictim && pVictim->GetGUID() == m_bot->GetGUID() ) + { + if (PSYCHIC_HORROR && CastSpell(PSYCHIC_HORROR, pTarget)) { return; } + if (PSYCHIC_SCREAM && CastSpell(PSYCHIC_SCREAM, pTarget)) { return; } + if (SHACKLE_UNDEAD && pTarget->GetCreatureType() == (uint32) CREATURE_TYPE_UNDEAD && CastSpell(SHACKLE_UNDEAD, pTarget)) { return; } + //if (m_bot->getRace() == (uint8) RACE_NIGHTELF && isUnderAttack() && CastSpell(R_SHADOWMELD, m_bot)) { return; } + } + if (ai->GetHealthPercent() < 20 && CastSpell(DESPERATE_PRAYER)) { return; } + if (ai->GetHealthPercent() < 30 && CastSpell(PW_SHIELD)) { return; } + if (m_bot->getRace() == (uint8) RACE_DWARF && ai->GetHealthPercent() < 75 && CastSpell(R_STONEFORM,m_bot)) { } //no gcd + if (ai->GetHealthPercent() < 60 && CastSpell(PAIN_SUPPRESSION,m_bot)) { return; } + if (ai->GetHealthPercent() < 50 && ai->GetManaPercent() < 10 && CastSpell(DISPERSION,m_bot)) { return; } + if (ai->GetManaPercent() < 10 && CastSpell (HYMN_OF_HOPE, m_bot)) { return; } + if (m_bot->getRace() == (uint8) RACE_TAUREN && pDist < 8 && CastSpell(R_WAR_STOMP, pTarget)) { return; } //no gcd but is cast + + + //Break Spells + if (m_bot->getRace() == (uint8) RACE_BLOODELF && pDist < 8 && (pTarget->IsNonMeleeSpellCasted(true) || ai->GetManaPercent() < 40) && CastSpell(R_ARCANE_TORRENT, pTarget)) { } //no gcd + else if (pTarget->IsNonMeleeSpellCasted(true) && CastSpell(SILENCE, pTarget)) { return; } + + + // If at threat limit, try to reduce threat + if (pThreat > threatThreshold && m_tank->GetGUID() != m_bot->GetGUID() && !isUnderAttack()) + { + if (m_tank->getVictim() && m_tank->getVictim()->GetGUID() != pTarget->GetGUID()) // I am attacking wrong target!! + { + m_bot->SetSelection(m_tank->getVictim()->GetGUID()); + return; + } + else + { + if (CastSpell(PAIN_SUPPRESSION,m_bot)) { return; } //Lets see if we can manage + else if (CastSpell(FADE,m_bot)) { return; } + else if (m_bot->FindCurrentSpellBySpellId(SHOOT)) { m_bot->InterruptNonMeleeSpells( true, SHOOT ); return; } //Disable wand + else { return; } //use no spells and wait threat to be reduced + } + } + + //WAND + if (ai->GetManaPercent() < 5 || + (m_role != BOT_ROLE_DPS_RANGED && SHOOT && !m_bot->FindCurrentSpellBySpellId(SHOOT) && ai->CastSpell(SHOOT,pTarget) ) + ) { return; } //Start autoshot + + // Continue spell attacking if theres excess mana (for healers) + if (m_role == BOT_ROLE_SUPPORT && ai->GetManaPercent() < offensiveSpellThreshold) { return; } + + if (ai->GetHealthPercent(*pTarget) > 95) { return; } // dont dps too early + + if (CastSpell(VAMPIRIC_EMBRACE,pTarget)) { return; } + if (CastSpell(VAMPIRIC_TOUCH,pTarget)) { return; } + if (CastSpell(DEVOURING_PLAGUE,pTarget)) { return; } + if (CastSpell(SW_PAIN,pTarget)) { return; } + + if (ai->GetForm() == FORM_SHADOW) + { + if (castDispel(DISPEL_MAGIC, pTarget)) { return; } //Dispel buffs if any + if (CastSpell(MIND_BLAST,pTarget)) { return; } + if (CastSpell(MIND_FLAY,pTarget)) { return; } + if (isUnderAttack(m_tank,4) && CastSpell(MIND_SEAR,pTarget)) { return; } + } + + if (ai->GetForm() == FORM_NONE && m_role == BOT_ROLE_DPS_RANGED) + { + if (CastSpell(PENANCE,pTarget)) { return; } + if (CastSpell(MIND_BLAST,pTarget)) { return; } + if (CastSpell(HOLY_FIRE,pTarget)) { return; } + if (CastSpell(SMITE,pTarget)) { return; } + if (isUnderAttack(m_tank,4) && CastSpell(MIND_SEAR,pTarget)) { return; } + if (isUnderAttack(m_tank,4) && CastSpell(HOLY_NOVA,pTarget)) { return; } + } + + // drink potion if support / healer (Other builds simply overuse mana and waste mana pots) + if(ai->GetManaPercent() < 5 && (m_role == BOT_ROLE_SUPPORT || m_role == BOT_ROLE_HEALER) ) + { + Item *pItem = ai->FindPotion(); + if(pItem != NULL) + { + if (pItem->GetSpell() && m_bot->HasSpellCooldown(pItem->GetSpell()) ) { return; } //pot is in cooldown + ai->UseItem(*pItem); + } + } +} //end DoNextCombatManeuver + +void PlayerbotPriestAI::DoNonCombatActions() +{ + PlayerbotAI *ai = GetAI(); + Player *m_bot = GetPlayerBot(); + if (!m_bot || !ai || m_bot->isDead()) { return; } + + //If Casting or Eating/Drinking return + if (m_bot->HasUnitState(UNIT_STAT_CASTING)) { return; } + if (m_bot->getStandState() == UNIT_STAND_STATE_SIT) { return; } + + //Own Buffs + if (CastSpell(INNER_FIRE,m_bot)) { return; } + + //buff and heal raid + if (DoSupportRaid(m_bot)) { return; } + + //heal pets and bots + Unit *target = DoSelectLowestHpFriendly(30, 1000); + if (target && target->isAlive() && HealTarget(target, target->GetHealth()*100 / target->GetMaxHealth())) { return; } + + //mana/hp check + //Don't bother with eating, if low on hp, just let it heal themself + if (m_bot->getRace() == (uint8) RACE_UNDEAD_PLAYER && ai->GetHealthPercent() < 75 && CastSpell(R_CANNIBALIZE,m_bot)) { return; } + if (m_bot->GetHealth() < m_bot->GetMaxHealth() && CastSpell(FLASH_HEAL,m_bot)) { return; } + if (ai->GetManaPercent() < 50) { ai->Feast(); } + +} //end DoNonCombatActions + + +bool PlayerbotPriestAI::HealTarget(Unit *target, uint8 hp) +{ + if(!target || target->isDead()) return false; + Player *m_bot = GetPlayerBot(); + + if (hp < 30 && m_bot->isInCombat() && CastSpell(GUARDIAN_SPIRIT,target)) { } //nogcd + if (hp < 30 && CastSpell(PENANCE,target,true,false,true)) { return true; } //Channeling Dual purpose + if (hp < 35 && m_bot->isInCombat() && CastSpell(PW_SHIELD,target)) { return true; } //Check weakened soul + if (hp < 80 && hp > 50 && GetAI()->GetHealthPercent() < 80 && CastSpell (BINDING_HEAL,target)) { return true; } + if (hp < 85 && CastSpell(RENEW,target)) { return true; } + if (hp < 40 && GetPlayerBot()->getRace() == (uint8) RACE_DRAENEI && CastSpell(R_GIFT_OF_NAARU,target)) { return true; } // no GCD, but has cast + if (hp < 55 && hp > 35 && CastSpell(HEAL,target)) { return true; } + if (hp < 75 && CastSpell(FLASH_HEAL,target)) { return true; } + + return false; +} //end HealTarget + +bool PlayerbotPriestAI::HealGroup (Unit *target, uint8 hp, uint8 &countNeedHeal) +{ + Player *m_bot = GetPlayerBot(); + if (countNeedHeal < 2) { return false; } + Unit *rTarget = DoSelectLowestHpFriendly(30,500); + if (!rTarget || rTarget->isDead() || rTarget->GetHealth() * 100 / rTarget->GetMaxHealth() > 80 ) { return false; } + + // if (hp < 75 && CastSpell(PO_MENDING, rTarget)) { return true; } //save this for tank + if (hp < 35 && m_bot->isInCombat() && CastSpell(DIVINE_HYMN,rTarget)) { /*GetAI()->SetIgnoreUpdateTime(9);*/ return true; } + if (hp < 70 && CastSpell(CIRCLE_OF_HEALING,rTarget)) { return true; } + if (hp < 75 && hp > 30 && countNeedHeal > 4 && CastSpell(PO_HEALING)) { return true; } + if (hp < 65 && CastSpell(HOLY_NOVA,rTarget,true,false,true)) { return true; } + + return false; +} + +//Cures the target +bool PlayerbotPriestAI::CureTarget(Unit *target) +{ + Player *m_bot = GetPlayerBot(); + + if(!target || target->isDead()) { return false; } + if (castDispel(DISPEL_MAGIC, target,true,false,true)) return true; + if (castDispel(CURE_DISEASE, target)) return true; + // if(HasAuraName(target, "Venom Spit") || HasAuraName(target, "Poison")) return CastSpell(CURE_POISON, target); + return false; +} + +bool PlayerbotPriestAI::RezTarget (Unit *target) +{ + if(!target || target->isAlive()) return false; + Player *m_bot = GetPlayerBot(); + if (target->IsNonMeleeSpellCasted(true)) { return false; } //Already resurrected + if (m_bot->isInCombat()) { return false; } + + if (!CanCast(RESURRECTION,target)) return false; + std::string msg = "Rezzing "; + msg += target->GetName(); + // msg += " with "; + // msg += *REZZSpell->SpellName; + GetPlayerBot()->Say(msg, LANG_UNIVERSAL); + return CastSpell(RESURRECTION, target,false); +} + +bool PlayerbotPriestAI::BuffPlayer(Unit *target) +{ + if(!target || target->isDead()) return false; + + return ( + (!HasAuraName(target, PW_FORTITUDE) && !HasAuraName(target, PO_FORTITUDE) && CastSpell (PW_FORTITUDE, target)) || + (!HasAuraName(target, SHADOW_PROTECTION) && !HasAuraName(target, PO_SHADOW_PROTECTION) && CastSpell(SHADOW_PROTECTION, target)) || + (!HasAuraName(target, DIVINE_SPIRIT) && !HasAuraName(target, PO_SPIRIT) && CastSpell (DIVINE_SPIRIT, target)) || + (!HasAuraName(target, FEAR_WARD) && CastSpell (FEAR_WARD, target)) + ); +} diff --git a/src/server/game/AI/Bots/PlayerbotPriestAI.h b/src/server/game/AI/Bots/PlayerbotPriestAI.h new file mode 100644 index 0000000..9670986 --- /dev/null +++ b/src/server/game/AI/Bots/PlayerbotPriestAI.h @@ -0,0 +1,59 @@ +#ifndef _PLAYERBOTPRIESTAI_H +#define _PLAYERBOTPRIESTAI_H + +#include "PlayerbotClassAI.h" + +//class Player; + +class PlayerbotPriestAI : PlayerbotClassAI +{ +public: + PlayerbotPriestAI(Player *const master, Player *const bot, PlayerbotAI *const ai); + virtual ~PlayerbotPriestAI(); + + virtual void LoadSpells(); + + //all combat actions go here + void DoNextCombatManeuver(Unit *); + + //all non combat actions go here, ex buffs, heals, rezzes + void DoNonCombatActions(); + + //buff a specific player, usually a real PC who is not in group + bool BuffPlayer(Unit *target); + + //Heals the target based off its HP + bool HealTarget(Unit *target, uint8 hp); + + bool HealGroup (Unit *target, uint8 hp, uint8 &countNeedHeal); + + //Cures the target + bool CureTarget(Unit *target); + + bool RezTarget (Unit *target); + +private: + //heals + uint32 RENEW, FLASH_HEAL, HEAL, BINDING_HEAL, PO_MENDING, DESPERATE_PRAYER, PO_HEALING, CIRCLE_OF_HEALING, DIVINE_HYMN, RESURRECTION, HYMN_OF_HOPE, CURE_DISEASE, DISPEL_MAGIC, MASS_DISPEL; + + //Holy Offensive + uint32 SMITE, HOLY_FIRE, PENANCE, HOLY_NOVA; + + //Shadow Offensive + uint32 MIND_BLAST, SW_PAIN, DEVOURING_PLAGUE, MIND_FLAY, VAMPIRIC_EMBRACE, VAMPIRIC_TOUCH, SW_DEATH, MIND_SEAR, MANA_BURN, SHADOWFIEND; + + //CC - Breaker + uint32 PSYCHIC_SCREAM, PSYCHIC_HORROR, MIND_SOOTHE, SHACKLE_UNDEAD, SILENCE, MIND_CONTROL; + + //buffs + uint32 PW_SHIELD, INNER_FIRE, GUARDIAN_SPIRIT, FADE, INNER_FOCUS, POWER_INFUSION, PAIN_SUPPRESSION, SHADOWFORM, DISPERSION, LIGHTWELL, + PW_FORTITUDE, DIVINE_SPIRIT, SHADOW_PROTECTION, PO_FORTITUDE, PO_SPIRIT, PO_SHADOW_PROTECTION, FEAR_WARD; + + uint32 SHOOT; + + uint32 TALENT_DISC, TALENT_HOLY, TALENT_SHADOW; +}; + + + +#endif diff --git a/src/server/game/AI/Bots/PlayerbotRogueAI.cpp b/src/server/game/AI/Bots/PlayerbotRogueAI.cpp new file mode 100644 index 0000000..4dff2db --- /dev/null +++ b/src/server/game/AI/Bots/PlayerbotRogueAI.cpp @@ -0,0 +1,266 @@ +#include "PlayerbotRogueAI.h" +#include "Spell.h" +class PlayerbotAI; +PlayerbotRogueAI::PlayerbotRogueAI(Player *const master, Player *const bot, PlayerbotAI *const ai): PlayerbotClassAI(master, bot, ai) +{ + foodDrinkSpamTimer = 0; + LoadSpells(); +} +PlayerbotRogueAI::~PlayerbotRogueAI(){} + +void PlayerbotRogueAI::LoadSpells() { + PlayerbotAI *ai = GetAI(); + if (!ai) return; + #pragma region SpellId Fill + //Damage spells + BACKSTAB = ai->getSpellIdExact("Backstab"); + SINISTER_STRIKE = ai->getSpellIdExact("Sinister Strike"); + MUTILATE = ai->getSpellIdExact("Mutilate"); + HEMORRHAGE = ai->getSpellIdExact("Hemorrhage"); + GHOSTLY_STRIKE = ai->getSpellIdExact("Ghostly Strike"); + RIPOSTE = ai->getSpellIdExact("Riposte"); + SHIV = ai->getSpellIdExact("Shiv"); + FAN_OF_KNIVES = ai->getSpellIdExact("Fan of Knives"); + + //Finishing Moves + EVISCERATE = ai->getSpellIdExact("Eviscerate"); + RUPTURE = ai->getSpellIdExact("Rupture"); + KIDNEY_SHOT = ai->getSpellIdExact("Kidney Shot"); + ENVENOM = ai->getSpellIdExact("Envenom"); + SLICE_AND_DICE = ai->getSpellIdExact("Slice and Dice"); + EXPOSE_ARMOR = ai->getSpellIdExact("Expose Armor"); + DEADLY_THROW = ai->getSpellIdExact("Deadly Throw"); + + //Buffs + STEALTH = ai->getSpellIdExact("Stealth"); + VANISH = ai->getSpellIdExact("Vanish"); + EVASION = ai->getSpellIdExact("Evasion"); + CLOAK_OF_SHADOWS = ai->getSpellIdExact("Cloak of Shadows"); + SPRINT = ai->getSpellIdExact("Sprint"); + COLD_BLOOD = ai->getSpellIdExact("Cold Blood"); + HUNGER_FOR_BLOOD = ai->getSpellIdExact("Hunger for Blood"); + BLADE_FLURRY = ai->getSpellIdExact("Blade Flurry"); + ADRENALINE_RUSH = ai->getSpellIdExact("Adrenaline Rush"); + KILLING_SPREE = ai->getSpellIdExact("Killing Spree"); + SHADOW_DANCE = ai->getSpellIdExact("Shadow Dance"); + + //Openers + CHEAP_SHOT = ai->getSpellIdExact("Cheap Shot"); + GARROTE = ai->getSpellIdExact("Garrote"); + AMBUSH = ai->getSpellIdExact("Ambush"); + + //Others + GOUGE = ai->getSpellIdExact("Gouge"); + BLIND = ai->getSpellIdExact("Blind"); + DISMANTLE = ai->getSpellIdExact("Dismantle"); + SAP = ai->getSpellIdExact("Sap"); + KICK = ai->getSpellIdExact("Kick"); + PREPARATION = ai->getSpellIdExact("Preparation"); + PREMEDITATION = ai->getSpellIdExact("Premeditation"); + SHADOWSTEP = ai->getSpellIdExact("Shadowstep"); + FEINT = ai->getSpellIdExact("Feint"); + TRICKS_OF_THE_TRADE = ai->getSpellIdExact("Tricks of the Trade"); + + SHOOT = ai->getSpellIdExact("Shoot"); + THROW = ai->getSpellIdExact("Throw"); + + TALENT_ASSASSINATION = MUTILATE; + TALENT_COMBAT = ADRENALINE_RUSH; + TALENT_SUBTELTY = PREMEDITATION; + + //uint8 talentCounter = 0; + //if (TALENT_ASSASSINATION) talentCounter++; + //if (TALENT_COMBAT) talentCounter++; + //if (TALENT_SUBTELTY) talentCounter++; + //if (talentCounter > 1) { TALENT_ASSASSINATION = 0; TALENT_COMBAT = 0; TALENT_SUBTELTY = 0; } //Unreliable Talent detection. +#pragma endregion +} + +void PlayerbotRogueAI::DoNextCombatManeuver(Unit *pTarget) +{ + if (!pTarget || pTarget->isDead()) return; + PlayerbotAI *ai = GetAI(); + if (!ai) return; + Player *m_bot = GetPlayerBot(); + if (!m_bot || m_bot->isDead()) return; + Unit *pVictim = pTarget->getVictim(); + Unit *m_tank = FindMainTankInRaid(GetMaster()); + if (!m_tank && m_bot->GetGroup() && GetMaster()->GetGroup() != m_bot->GetGroup()) { FindMainTankInRaid(m_bot); } + if (!m_tank) { m_tank = m_bot; } + uint32 masterHP = GetMaster()->GetHealth()*100 / GetMaster()->GetMaxHealth(); + float pDist = m_bot->GetDistance(pTarget); + uint8 pThreat = GetThreatPercent(pTarget); + + #pragma region Choose Target + // Choose actions accoring to talents (ROGUE is always MELEE DPS) + m_role = BOT_ROLE_DPS_MELEE; + // if i am under attack and if i am not tank or offtank: change target if needed + if (m_tank->GetGUID() != m_bot->GetGUID() && isUnderAttack() ) + { + // Keep hitting but reduce threat + if (CastSpell(TRICKS_OF_THE_TRADE,m_tank)) { return; } + else if (CastSpell(VANISH,m_bot)) { return; } + else //I cannot reduce threat so + { + if (pVictim && pVictim->GetGUID() == m_bot->GetGUID() && pDist <= 2) { } // My target is almost up to me, no need to search + else //Have to select nearest target + { + Unit *curAtt = GetNearestAttackerOf(m_bot); + if (curAtt && curAtt->GetGUID() != pTarget->GetGUID()) + { + m_bot->SetSelection(curAtt->GetGUID()); + DoNextCombatManeuver(curAtt); //Restart new update to get variables fixed.. + return; + } + } + //my target is attacking me + } + } + #pragma endregion + + // wait until we actually reach our target b4 we actually do anything + if (m_bot->GetDistance(pTarget)>10.0 && + !m_bot->HasAura(STEALTH) && + !m_bot->isInCombat() && CastSpell(STEALTH)) + { return; } + + TakePosition(pTarget); + + // If there's a cast stop + if (m_bot->HasUnitState(UNIT_STAT_CASTING)) { return; } + + // wait until we actually reach our target b4 we actually do anything + /*if (GetPlayerBot()->GetDistance(pTarget)>10.0 && + !HasAuraName(GetPlayerBot(),STEALTH) && + !GetPlayerBot()->isInCombat() && CastSpell(STEALTH)) + { return; }*/ + + //Buff + if (CastSpell(PREMEDITATION,m_bot)) { return; } + if (CastSpell(COLD_BLOOD,m_bot)) { } //no gcd + + //PROTECT UP + if (m_tank->GetGUID() != m_bot->GetGUID() && pVictim && pVictim->GetGUID() == m_bot->GetGUID() ) + { + if (CastSpell(TRICKS_OF_THE_TRADE,m_tank)) { return; } + if (CastSpell(FEINT,m_bot)) { return; } + if (CastSpell(VANISH,m_bot)) { return; } + } + if (isUnderAttack() && pDist <= MELEE_RANGE && ai->GetHealthPercent() <= 85 && CastSpell(EVASION, m_bot)) { } //no GCD + if (ai->GetHealthPercent() <= 55 && CastSpell(CLOAK_OF_SHADOWS, m_bot)) { return; } + if (isUnderAttack() && ai->GetHealthPercent() <= 65 && CastSpell(GOUGE, m_bot)) { return; } + if (isUnderAttack() && ai->GetHealthPercent() <= 45 && CastSpell(BLIND, m_bot)) { return; } + if (m_bot->getRace() == (uint8) RACE_DWARF && ai->GetHealthPercent() < 75 && CastSpell(R_STONEFORM,m_bot)) { } //no gcd + + //Break spells + if (m_bot->getRace() == (uint8) RACE_BLOODELF && pDist < 8 && (pTarget->IsNonMeleeSpellCasted(true) || ai->GetManaPercent() < 40) && CastSpell(R_ARCANE_TORRENT, pTarget)) { } //no gcd + else if (pTarget->IsNonMeleeSpellCasted(true) && CastSpell(KICK, pTarget)) { return; } + else if (pTarget->IsNonMeleeSpellCasted(true) && CastSpell(GOUGE, pTarget)) { return; } + else if (pTarget->IsNonMeleeSpellCasted(true) && m_bot->GetComboPoints() >= 1 && CastSpell(KIDNEY_SHOT, pTarget)) { return; } + + //Transfer threat + if (m_tank->GetGUID() != m_bot->GetGUID() && CastSpell(TRICKS_OF_THE_TRADE,m_tank)) { return; } + if (m_bot->getRace() == (uint8) RACE_TROLL && CastSpell(R_BERSERKING,m_bot)) {} //no GCD + if (m_bot->getRace() == (uint8) RACE_ORC && CastSpell(R_BLOOD_FURY,m_bot)) {} //no GCD + + // sometimes we lose attack + if (!m_bot->isInCombat()) { + m_bot->Attack(pTarget, true); + } + + if(TALENT_ASSASSINATION) + { + if (!m_bot->HasAura(HUNGER_FOR_BLOOD) && CastSpell(HUNGER_FOR_BLOOD,m_bot)) { return; } + if (m_bot->GetComboPoints() < 5) + { + if (CastSpell(MUTILATE, pTarget)) { return; } + } + else + { + if (!pTarget->HasAura(RUPTURE) && CastSpell(RUPTURE)) { return; } + + } + } + + if(TALENT_COMBAT) + { + if (CastSpell(BLADE_FLURRY,m_bot)) { return; } + if (ai->GetEnergyAmount() < 20 && CastSpell(ADRENALINE_RUSH,m_bot)) { return; } + if (!CastSpell(ADRENALINE_RUSH) && CastSpell(KILLING_SPREE,m_bot,1,0,1)) { return; } + if (m_bot->GetComboPoints() > 5) + { + if (!pTarget->HasAura(RUPTURE) && CastSpell(RUPTURE)) { return; } + } + } + + if(TALENT_SUBTELTY) + { + if (CastSpell(PREMEDITATION,m_bot)) {} + if (CastSpell(SHADOW_DANCE,m_bot)) {} + if (!CastSpell(SHADOW_DANCE,m_bot) && CastSpell(PREPARATION,m_bot)) { return; } + if (m_bot->GetComboPoints() < 5) + { + if (m_bot->HasAura(SHADOW_DANCE) && !pTarget->HasInArc(M_PI,m_bot)) { if (CastSpell(AMBUSH, pTarget)) { return; } } + if (CastSpell(GHOSTLY_STRIKE, pTarget)) { return; } + } + else + { + if (!pTarget->HasAura(RUPTURE) && CastSpell(RUPTURE)) { return; } + if (!m_bot->HasAura(SLICE_AND_DICE) && CastSpell(SLICE_AND_DICE)) { return; } + if (CastSpell(SHADOWSTEP,pTarget)) { } + } + } + + // defaults if not high enough do specialized attacks + if (m_bot->GetComboPoints() < 5) { + if (!pTarget->HasInArc(M_PI,m_bot)) { if (CastSpell(BACKSTAB, pTarget)) { return; } } + if (!MUTILATE &&CastSpell(SINISTER_STRIKE,pTarget)) { return; } // Dont cast if we have mutilate, save energy for it + } else { + if (!m_bot->HasAura(SLICE_AND_DICE) && CastSpell(SLICE_AND_DICE)) { return; } + if (CastSpell(ENVENOM,pTarget)) { return; } + if (CastSpell(EVISCERATE,pTarget)) { return; } + } +} //end DoNextCombatManeuver + + +void PlayerbotRogueAI::DoNonCombatActions() +{ + PlayerbotAI *ai = GetAI(); + Player *m_bot = GetPlayerBot(); + if (!m_bot || !ai || m_bot->isDead()) { return; } + + //If Casting or Eating/Drinking return + if (m_bot->HasUnitState(UNIT_STAT_CASTING)) { return; } + if (m_bot->getStandState() == UNIT_STAND_STATE_SIT) { return; } + + //Buff Up + if (ChangeWeaponEnchants()) { return; } +} //end DoNonCombatActions + +bool PlayerbotRogueAI::ChangeWeaponEnchants() +{ + PlayerbotAI *ai = GetAI(); + Player *m_bot = GetPlayerBot(); + if (!m_bot || !ai || m_bot->isDead()) { return false; } + + Item *weap; + Item *poison; + + weap = m_bot->GetWeaponForAttack(BASE_ATTACK); + if (weap && !weap->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT)) + { + poison = GetAI()->FindPoisonForward(); + if(poison == NULL) return false; + GetAI()->PoisonWeapon(*poison, poison->GetProto()->Spells[0].SpellId, TARGET_FLAG_ITEM, EQUIPMENT_SLOT_MAINHAND); + return true; + } + weap = m_bot->GetWeaponForAttack(OFF_ATTACK); + if (weap && !weap->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT)) + { + poison = GetAI()->FindPoisonBackward(); + if(poison == NULL) return false; + GetAI()->PoisonWeapon(*poison, poison->GetProto()->Spells[0].SpellId, TARGET_FLAG_ITEM, EQUIPMENT_SLOT_OFFHAND); + return true; + } + return false; +} diff --git a/src/server/game/AI/Bots/PlayerbotRogueAI.h b/src/server/game/AI/Bots/PlayerbotRogueAI.h new file mode 100644 index 0000000..9c400f1 --- /dev/null +++ b/src/server/game/AI/Bots/PlayerbotRogueAI.h @@ -0,0 +1,41 @@ +#ifndef _PLAYERBOTROGUEAI_H +#define _PLAYERBOTROGUEAI_H + +#include "PlayerbotClassAI.h" +#include "TargetedMovementGenerator.h" + +class PlayerbotRogueAI : PlayerbotClassAI +{ + public: + PlayerbotRogueAI(Player *const master, Player *const bot, PlayerbotAI *const ai); + virtual ~PlayerbotRogueAI(); + + virtual void LoadSpells(); + + //all combat actions go here + void DoNextCombatManeuver(Unit *); + + //all non combat actions go here, ex buffs, heals, REBIRTHes + void DoNonCombatActions(); + + //buff a specific player, usually a real PC who is not in group + bool ChangeWeaponEnchants(); + + private: + //Damage spells + uint32 BACKSTAB, SINISTER_STRIKE, MUTILATE, HEMORRHAGE, GHOSTLY_STRIKE, RIPOSTE, SHIV, FAN_OF_KNIVES; + //Finishing Moves + uint32 EVISCERATE, RUPTURE, KIDNEY_SHOT, ENVENOM, SLICE_AND_DICE, EXPOSE_ARMOR, DEADLY_THROW; + //Buffs + uint32 STEALTH, VANISH, EVASION, CLOAK_OF_SHADOWS, SPRINT, COLD_BLOOD, HUNGER_FOR_BLOOD, BLADE_FLURRY, ADRENALINE_RUSH, KILLING_SPREE, SHADOW_DANCE; + //Openers + uint32 CHEAP_SHOT, GARROTE, AMBUSH; + //Others + uint32 GOUGE, BLIND, DISMANTLE, SAP, KICK, PREPARATION, PREMEDITATION, SHADOWSTEP, FEINT, TRICKS_OF_THE_TRADE; + + uint32 TALENT_ASSASSINATION, TALENT_COMBAT, TALENT_SUBTELTY; + + uint32 THROW; + +}; +#endif diff --git a/src/server/game/AI/Bots/PlayerbotShamanAI.cpp b/src/server/game/AI/Bots/PlayerbotShamanAI.cpp new file mode 100644 index 0000000..201776e --- /dev/null +++ b/src/server/game/AI/Bots/PlayerbotShamanAI.cpp @@ -0,0 +1,555 @@ +#include "PlayerbotShamanAI.h" +class PlayerbotAI; +PlayerbotShamanAI::PlayerbotShamanAI(Player *const master, Player *const bot, PlayerbotAI *const ai): PlayerbotClassAI(master, bot, ai) +{ + foodDrinkSpamTimer = 0; + LoadSpells(); +} +PlayerbotShamanAI::~PlayerbotShamanAI(){} + +void PlayerbotShamanAI::LoadSpells() { + PlayerbotAI *ai = GetAI(); + if (!ai) return; + #pragma region SpellId Fill + //totems + HEALING_STREAM_TOTEM = ai->getSpellIdExact("Healing Stream Totem"); + MANA_SPRING_TOTEM = ai->getSpellIdExact("Mana Spring Totem"); + MANA_TIDE_TOTEM = ai->getSpellIdExact("Mana Tide Totem"); + CLEANSING_TOTEM = ai->getSpellIdExact("Cleansing Totem"); + FIRE_RESISTANCE_TOTEM = ai->getSpellIdExact("Fire Resistance Totem"); + + WINDFURY_TOTEM = ai->getSpellIdExact("Windfury Totem"); + WRATH_OF_AIR_TOTEM = ai->getSpellIdExact("Wrath of Air Totem"); + GROUNDING_TOTEM = ai->getSpellIdExact("Grounding Totem"); + NATURE_RESISTANCE_TOTEM = ai->getSpellIdExact("Nature Resistance Totem"); + + STRENGTH_OF_EARTH_TOTEM = ai->getSpellIdExact("Strength of Earth Totem"); + EARTHBIND_TOTEM = ai->getSpellIdExact("Earthbind Totem"); + STONESKIN_TOTEM = ai->getSpellIdExact("Stoneskin Totem"); + STONECLAW_TOTEM = ai->getSpellIdExact("Stoneclaw Totem"); + TREMOR_TOTEM = ai->getSpellIdExact("Tremor Totem"); + EARTH_ELEMENTAL_TOTEM = ai->getSpellIdExact("Earth Elemental Totem"); + + FLAMETONGUE_TOTEM = ai->getSpellIdExact("Flametongue Totem"); + TOTEM_OF_WRATH = ai->getSpellIdExact("Totem of Wrath"); + SEARING_TOTEM = ai->getSpellIdExact("Searing Totem"); + MAGMA_TOTEM = ai->getSpellIdExact("Magma Totem"); + FIRE_ELEMENTAL_TOTEM = ai->getSpellIdExact("Fire Elemental Totem"); + FROST_RESISTANCE_TOTEM = ai->getSpellIdExact("Frost Resistance Totem"); + + TOTEMIC_RECALL = ai->getSpellIdExact("Totemic Recall"); + CALL_OF_THE_ELEMENTS = ai->getSpellIdExact("Call of the Elements"); + CALL_OF_THE_ANCESTORS = ai->getSpellIdExact("Call of the Ancestors"); + CALL_OF_THE_SPIRITS = ai->getSpellIdExact("Call of the Spirits"); + + //restoration + HEAL = ai->getSpellIdExact("Healing Wave"); + LESSER_HEAL = ai->getSpellIdExact("Lesser Healing Wave"); + CHAIN_HEAL = ai->getSpellIdExact("Chain Heal"); + RIPTIDE = ai->getSpellIdExact("Riptide"); + ANCESTRAL_SPIRIT = ai->getSpellIdExact("Ancestral Spirit"); + CLEANSE_SPIRIT = ai->getSpellIdExact("Cleanse Spirit"); + if (CLEANSE_SPIRIT) CLEANSE_SPIRIT = ai->getSpellIdExact("Cure Toxins"); + + //offensive spells + LIGHTNING_BOLT = ai->getSpellIdExact("Lightning Bolt"); + CHAIN_LIGHTNING = ai->getSpellIdExact("Chain Lightning"); + FIRE_NOVA = ai->getSpellIdExact("Fire Nova"); + THUNDERSTORM = ai->getSpellIdExact("Thunderstorm"); + LAVA_BURST = ai->getSpellIdExact("Lava Burst"); + EARTH_SHOCK = ai->getSpellIdExact("Earth Shock"); + WIND_SHEAR = ai->getSpellIdExact("Wind Shear"); + FLAME_SHOCK = ai->getSpellIdExact("Flame Shock"); + FROST_SHOCK = ai->getSpellIdExact("Frost Shock"); + PURGE = ai->getSpellIdExact("Purge"); + HEX = ai->getSpellIdExact("Hex"); + + //buffs + LIGHTNING_SHIELD = ai->getSpellIdExact("Lightning Shield"); + WATER_SHIELD = ai->getSpellIdExact("Water Shield"); + EARTH_SHIELD = ai->getSpellIdExact("Earth Shield"); + HEROISM = ai->getSpellIdExact("Heroism"); + if (HEROISM) HEROISM = ai->getSpellIdExact("Bloodlust"); + ELEMENTAL_MASTERY = ai->getSpellIdExact("Elemental Mastery"); + NATURES_SWIFTNESS = ai->getSpellIdExact("Nature's Swiftness"); + + WINDFURY_WEAPON = ai->getSpellIdExact("Windfury Weapon"); + FLAMETONGUE_WEAPON = ai->getSpellIdExact("Flametongue Weapon"); + FROSTBRAND_WEAPON = ai->getSpellIdExact("Frostbrand Weapon"); + ROCKBITER_WEAPON = ai->getSpellIdExact("Rockbiter Weapon"); + EARTHLIVING_WEAPON = ai->getSpellIdExact("Earthliving Weapon"); + + WATER_BREATHING = ai->getSpellIdExact("Water Breathing"); + WATER_WALKING = ai->getSpellIdExact("Water Walking"); + + //melee + LAVA_LASH = ai->getSpellIdExact("Lava Lash"); + STORMSTRIKE = ai->getSpellIdExact("Stormstrike"); + SHAMANISTIC_RAGE = ai->getSpellIdExact("Shamanistic Rage"); + FERAL_SPIRIT = ai->getSpellIdExact("Feral Spirit"); + + GHOST_WOLF = ai->getSpellIdExact("Ghost Wolf"); + + EXHAUSTION = 57723; // heroism debuff + SATED = 57724; // bloodlust debuff + //MAELSTROM_WEAPON = 0; // We want the triggered aura, not the talent spell + uint32 mwtrigger = ai->getSpellIdExact("Maelstrom Weapon",true); + if (mwtrigger) + { + SpellEntry const *mwtSpell = GetSpellStore()->LookupEntry(mwtrigger); + if (mwtSpell && mwtSpell->EffectTriggerSpell[0] > 0) MAELSTROM_WEAPON = mwtSpell->EffectTriggerSpell[0]; + } + + TALENT_ELEMENTAL = ELEMENTAL_MASTERY; + TALENT_ENHANCEMENT = STORMSTRIKE; + TALENT_RESTO = EARTH_SHIELD; + + uint8 talentCounter = 0; + if (TALENT_ELEMENTAL) talentCounter++; + if (TALENT_ENHANCEMENT) talentCounter++; + if (TALENT_RESTO) talentCounter++; + //if (talentCounter > 1) { TALENT_ELEMENTAL = 0; TALENT_ENHANCEMENT = 0; TALENT_RESTO = 0; } //Unreliable Talent detection. + #pragma endregion +} +void PlayerbotShamanAI::DoNextCombatManeuver(Unit *pTarget) +{ + if (!pTarget || pTarget->isDead()) return; + PlayerbotAI *ai = GetAI(); + if (!ai) return; + Player *m_bot = GetPlayerBot(); + if (!m_bot || m_bot->isDead()) return; + Unit *pVictim = pTarget->getVictim(); + Unit *m_tank = FindMainTankInRaid(GetMaster()); + if (!m_tank && m_bot->GetGroup() && GetMaster()->GetGroup() != m_bot->GetGroup()) { FindMainTankInRaid(m_bot); } + if (!m_tank) { m_tank = m_bot; } + uint32 masterHP = GetMaster()->GetHealth()*100 / GetMaster()->GetMaxHealth(); + float pDist = m_bot->GetDistance(pTarget); + uint8 pThreat = GetThreatPercent(pTarget); + + uint8 reqHeal = 0; + uint8 OwnPartyHP = GetHealthPercentRaid(m_bot, reqHeal); + + switch(ai->GetScenarioType()) + { + case SCENARIO_DUEL: + ((ai->GetHealthPercent() < 80 && CastSpell(LESSER_HEAL)) || + CastSpell(LIGHTNING_BOLT, pTarget)); + return; + } + + // Cast CC breakers if any match found (include any dispels first) does not work yet + //uint32 ccSpells[4] = { R_ESCAPE_ARTIST, R_EVERY_MAN_FOR_HIMSELF, R_WILL_OF_FORSAKEN, R_STONEFORM }; + //if (ai->GetManaPercent() < 35) { ccSpells[0] = 0; ccSpells[1] = 0; } //We dont have any mana to waste... + //if (castSelfCCBreakers(ccSpells)) { } // Most of them don't trigger gcd + + + #pragma region Choose Actions + // Choose actions accoring to talents + if (m_tank->GetGUID() == m_bot->GetGUID()) { m_role=BOT_ROLE_TANK; } // Hey! I am Main Tank + else if (TALENT_ENHANCEMENT) { m_role = BOT_ROLE_DPS_MELEE; } + else if (TALENT_ELEMENTAL) { m_role = BOT_ROLE_DPS_RANGED; } + else if (TALENT_RESTO) { m_role = BOT_ROLE_SUPPORT; } + else { m_role = BOT_ROLE_DPS_MELEE; } //Unknown build or low level.. Mainly attack + + // if i am under attack and if i am not tank or offtank: change target if needed + if (m_tank->GetGUID() != m_bot->GetGUID() && isUnderAttack() ) + { + if (pVictim && pVictim->GetGUID() == m_bot->GetGUID() && pDist <= 2) { } // My target is almost up to me, no need to search + else //Have to select nearest target + { + Unit *curAtt = GetNearestAttackerOf(m_bot); + if (curAtt && curAtt->GetGUID() != pTarget->GetGUID()) + { + m_bot->SetSelection(curAtt->GetGUID()); + //ai->AddLootGUID(curAtt->GetGUID()); + DoNextCombatManeuver(curAtt); //Restart new update to get variables fixed.. + return; + } + } + //my target is attacking me + } + #pragma endregion + + // Choose Weapon Enchant + if (ChangeWeaponEnchants()) return; + + if (TALENT_ELEMENTAL){ if (!m_bot->HasAura(WATER_SHIELD) && CastSpell(WATER_SHIELD,m_bot)) { return; }} + if (TALENT_ENHANCEMENT){ if (!m_bot->HasAura(LIGHTNING_SHIELD) && CastSpell(LIGHTNING_SHIELD,m_bot)) { return; }} + if (TALENT_RESTO){ if (!m_bot->HasAura(WATER_SHIELD) && CastSpell(WATER_SHIELD,m_bot)) { return; }} + // Choose shield + /* + if (EARTH_SHIELD && ai->GetHealthPercent() < 80 && isUnderAttack()) { if (CastSpell(EARTH_SHIELD,m_bot)) { return; } } + else if (WATER_SHIELD && ai->GetManaPercent() < 40) { if (CastSpell(WATER_SHIELD,m_bot)) { return; } } + else if (LIGHTNING_SHIELD && + ( isUnderAttack() || m_tank->GetGUID() == m_bot->GetGUID() ) && !(m_bot->HasAura(WATER_SHIELD) && ai->GetManaPercent() < 80) + ) { if (CastSpell(LIGHTNING_SHIELD,m_bot)) { return; } } + else if (CastSpell(WATER_SHIELD,m_bot)) { return; } + */ + // If there's a cast stop + if(m_bot->HasUnitState(UNIT_STAT_CASTING)) return; + + switch(m_role) + { + #pragma region BOT_ROLE_TANK / BOT_ROLE_OFFTANK + case BOT_ROLE_TANK: + case BOT_ROLE_OFFTANK: + if (!TALENT_ELEMENTAL && !TALENT_RESTO) { TakePosition(pTarget); } + else { TakePosition(pTarget,BOT_ROLE_DPS_RANGED); } // mob will come to you sooner or later no need to hurry + + // Do support stuff + if (!m_bot->isMoving() && ChangeTotems(m_role)) { return; } + if (ai->GetManaPercent() > 70 && DoSupportRaid(m_bot)) { return; } + + break; + #pragma endregion + + #pragma region BOT_ROLE_DPS_MELEE + case BOT_ROLE_DPS_MELEE: + TakePosition(pTarget); + // Do support stuff + if (!m_bot->isMoving() && ChangeTotems(m_role)) { return; } + if (ai->GetManaPercent() > 40 && DoSupportRaid(m_bot)) { return; } + break; + #pragma endregion + + #pragma region BOT_ROLE_DPS_RANGED + case BOT_ROLE_DPS_RANGED: + TakePosition(pTarget); + // Do support stuff + if (!m_bot->isMoving() && ChangeTotems(m_role)) { return; } + if (ai->GetManaPercent() > 40 && DoSupportRaid(m_bot)) { return; } + break; + #pragma endregion + + #pragma region BOT_ROLE_SUPPORT + case BOT_ROLE_SUPPORT: + TakePosition(pTarget); + // Do support stuff + if (!m_bot->isMoving() && ChangeTotems(m_role)) { return; } + if (DoSupportRaid(m_bot)) { return; } + //heal pets and bots + Unit *target = DoSelectLowestHpFriendly(40, 1000); + if(target && target->isAlive() && HealTarget(target, target->GetHealth()*100 / target->GetMaxHealth()) ) { return; } + + break; + #pragma endregion + } + #pragma region ShamanCommon + + + //Defensive Stuff + if (m_tank->GetGUID() != m_bot->GetGUID() && pVictim && pVictim->GetGUID() == m_bot->GetGUID() ) + { + if (pDist > 5 && CastSpell(FROST_SHOCK, pTarget)) { return; } + if ((pTarget->GetCreatureType() == (uint32) CREATURE_TYPE_BEAST || pTarget->GetCreatureType() == (uint32) CREATURE_TYPE_HUMANOID) && CastSpell(HEX, pTarget)) { return; } // no gcd + if (CastSpell(WIND_SHEAR, pTarget)) { } // no gcd + } + if (m_bot->getRace() == (uint8) RACE_BLOODELF && pDist < 8 && pTarget->IsNonMeleeSpellCasted(true) && CastSpell(R_ARCANE_TORRENT, pTarget)) { } //no gcd + if (pTarget->IsNonMeleeSpellCasted(true) && CastSpell(WIND_SHEAR, pTarget)) { } //no gcd + if (m_bot->getRace() == (uint8) RACE_TAUREN && pVictim && pVictim->GetGUID() == m_bot->GetGUID() && pDist < 8 && CastSpell(R_WAR_STOMP, pTarget)) { return; } + + //Catch + if (pTarget->HasUnitMovementFlag(UNIT_FLAG_FLEEING)) + { + if (CastSpell(FROST_SHOCK,pTarget)) return; + } + + + //Buff and restores + if ( ( (ai->GetHealthPercent() < 60 && isUnderAttack()) || + (ai->GetManaPercent() < 30) ) && CastSpell(SHAMANISTIC_RAGE, m_bot)) { return; } + if (m_bot->getRace() == (uint8) RACE_TROLL && CastSpell(R_BERSERKING,m_bot)) {} // no GCD + if (m_bot->getRace() == (uint8) RACE_ORC && CastSpell(R_BLOOD_FURY,m_bot)) {} // no GCD + if (!m_bot->HasAura(HEROISM) && !m_bot->HasAura(EXHAUSTION) && !m_bot->HasAura(SATED) && CastSpell(HEROISM,m_bot)) { return; } + if (m_role != BOT_ROLE_SUPPORT && CastSpell(NATURES_SWIFTNESS, m_bot)) { } //healers keep it for healing no gcd + else if (CastSpell(ELEMENTAL_MASTERY, m_bot)) { } //no gcd + + // If at threat limit, use WIND_SHEAR to reduce threat + if (pThreat > threatThreshold && m_tank->GetGUID() != m_bot->GetGUID() && !isUnderAttack()) + { + if (m_tank->getVictim() && m_tank->getVictim()->GetGUID() != pTarget->GetGUID()) // I am attacking wrong target!! + { + m_bot->SetSelection(m_tank->getVictim()->GetGUID()); + return; + } + else + { + if (CastSpell(WIND_SHEAR,pTarget)) { return; } //Lets see if we can manage + else { return; } //use no spells and wait threat to be reduced + } + } + + + if (TALENT_ELEMENTAL) + { + if (CastSpell(ELEMENTAL_MASTERY, m_bot)) { } //no gcd + if (!pTarget->HasAura(FLAME_SHOCK,m_bot->GetGUID()) && CastSpell(FLAME_SHOCK,pTarget)) { return; } + if (CastSpell(LAVA_BURST,pTarget)) { return; } + if (CastSpell(CHAIN_LIGHTNING,pTarget)) { return; } + if (CastSpell(LIGHTNING_BOLT,pTarget)) { return; } + } + + //dps + if (MAELSTROM_WEAPON) + { + Aura *maelaura = m_bot->GetAura(MAELSTROM_WEAPON); + if (maelaura && maelaura->GetStackAmount() == 5) + { + if ((isUnderAttack(m_tank,3) || m_tank->GetGUID() == m_bot->GetGUID()) && CastSpell(CHAIN_LIGHTNING,pTarget,true,true)) { return; } + if (CastSpell(LIGHTNING_BOLT,pTarget,true,true)) { return; } + } + } + if (CastSpell(FLAME_SHOCK,pTarget)) { return; } + if (CastSpell(STORMSTRIKE,pTarget,true,true)) { return; } + + //if (!TALENT_ENHANCEMENT && CanCast(LAVA_BURST,pTarget,true) && pTarget->HasAura(FLAME_SHOCK,m_bot->GetGUID()) && CastSpell(LAVA_BURST,pTarget,false)) { return; } + if (CastSpell(FERAL_SPIRIT,m_bot)) { return; } + if (CanCast(EARTH_SHOCK,pTarget,true) && (pTarget->HasAura(STORMSTRIKE,m_bot->GetGUID()) || pTarget->HasAura(FLAME_SHOCK,m_bot->GetGUID()) ) && CastSpell(EARTH_SHOCK,pTarget)) { return; } + //if (CanCast(FLAME_SHOCK,pTarget) && CastSpell(FLAME_SHOCK,pTarget)) { return; } + if (CastSpell(LAVA_LASH,pTarget,true,true)) { return; } + if (CastSpell(FIRE_NOVA,pTarget)) { return; } + //if ((isUnderAttack(m_tank,4) || m_tank->GetGUID() == m_bot->GetGUID()) && CastSpell(FIRE_NOVA,pTarget)) { return; } + if (ai->GetManaPercent() > 60 && castDispel(PURGE,pTarget)) { return; } //PURGE but dont overpurge + + #pragma endregion + + + // drink potion if support / healer (Other builds simply overuse mana and waste mana pots) + if(ai->GetManaPercent() < 5 && (m_role == BOT_ROLE_SUPPORT || m_role == BOT_ROLE_HEALER) ) + { + Item *pItem = ai->FindPotion(); + if(pItem != NULL) + { + if (pItem->GetSpell() && m_bot->HasSpellCooldown(pItem->GetSpell()) ) { return; } //pot is in cooldown + ai->UseItem(*pItem); + } + } + + +} //end DoNextCombatManeuver + +void PlayerbotShamanAI::DoNonCombatActions() +{ + PlayerbotAI *ai = GetAI(); + Player *m_bot = GetPlayerBot(); + if (!m_bot || !ai || m_bot->isDead()) { return; } + + //If Casting or Eating/Drinking return + if (m_bot->HasUnitState(UNIT_STAT_CASTING)) { return; } + if (m_bot->getStandState() == UNIT_STAND_STATE_SIT) { return; } + + //buff and heal raid + if (DoSupportRaid(m_bot)) { return; } + + //heal pets and bots + Unit *target = DoSelectLowestHpFriendly(40, 1000); + if (target && target->isAlive() && HealTarget(target, target->GetHealth()*100 / target->GetMaxHealth())) { return; } + + //Buffs + if (ChangeWeaponEnchants()) { return; } + if (CastSpell(WATER_SHIELD,m_bot)) { return; } + if (CastSpell(EARTH_SHIELD,m_bot)) { return; } + + //mana/hp check + //Don't bother with eating, if low on hp, just let it heal themself + if (m_bot->getRace() == (uint8) RACE_UNDEAD_PLAYER && ai->GetHealthPercent() < 75 && CastSpell(R_CANNIBALIZE,m_bot)) { return; } + if (m_bot->GetHealth() < m_bot->GetMaxHealth() && CastSpell(LESSER_HEAL,m_bot)) { return; } + if (ai->GetManaPercent() < 50) { ai->Feast(); } + +} //end DoNonCombatActions +bool PlayerbotShamanAI::HealTarget(Unit *target, uint8 hp) +{ + if(!target || target->isDead()) return false; + Player *m_bot = GetPlayerBot(); + + if(hp < 30 && m_bot->isInCombat() && CastSpell(NATURES_SWIFTNESS, m_bot)) {} // NO gcd + if(hp < 60 && CanCast(HEAL,target,true) && m_bot->HasAura(NATURES_SWIFTNESS) && CastSpell(HEAL, target, false)) { return true; } + if(hp < 30 && CastSpell(LESSER_HEAL,target,true,true)) { return true; } + if(hp < 40 && m_bot->getRace() == (uint8) RACE_DRAENEI && CastSpell(R_GIFT_OF_NAARU,target)) {} // no GCD + if(hp < 65 && CanCast(EARTH_SHIELD,target) && !m_bot->HasAura(EARTH_SHIELD,m_bot->GetGUID()) && CastSpell(EARTH_SHIELD,target,false)) { return true; } + if(hp < 65 && CastSpell(HEAL,target,true,true)) { return true; } + if(hp < 85 && CastSpell(LESSER_HEAL,target,true,true)) { return true; } + + return false; +} //end HealTarget + +bool PlayerbotShamanAI::HealGroup (Unit *target, uint8 hp, uint8 &countNeedHeal) +{ + Player *m_bot = GetPlayerBot(); + if (countNeedHeal < 2) { return false; } + Unit *rTarget = DoSelectLowestHpFriendly(30,500); + if (!rTarget || rTarget->isDead() || rTarget->GetHealth() * 100 / rTarget->GetMaxHealth() > 80 ) { return false; } + + if (hp < 65 && RIPTIDE && rTarget->HasAura(RIPTIDE,m_bot->GetGUID()) && CastSpell(CHAIN_HEAL, rTarget)) { return true; } + if (hp < 85 && CastSpell(RIPTIDE, rTarget)) { return true; } + if (hp < 75 && CastSpell(CHAIN_HEAL, rTarget,true,true)) { return true; } + + return false; +} + +bool PlayerbotShamanAI::CureTarget(Unit *target) +//Cures the target +{ + Player *m_bot = GetPlayerBot(); + + if(!target || target->isDead()) { return false; } + if (castDispel(CLEANSE_SPIRIT, target)) return true; + + return false; +} + +bool PlayerbotShamanAI::RezTarget (Unit *target) +{ + if(!target || target->isAlive()) return false; + Player *m_bot = GetPlayerBot(); + if (target->IsNonMeleeSpellCasted(true)) { return false; } //Already resurrected + if (m_bot->isInCombat()) { return false; } + + if (!CanCast(ANCESTRAL_SPIRIT,target)) return false; + std::string msg = "Rezzing "; + msg += target->GetName(); + + GetPlayerBot()->Say(msg, LANG_UNIVERSAL); + return CastSpell(ANCESTRAL_SPIRIT, target,false); +} + +bool PlayerbotShamanAI::BuffPlayer(Unit *target) +{ + //std::string msg = "Mana totem, coming right up."; + //GetPlayerBot()->Say(msg, LANG_UNIVERSAL); + //if(!HasAuraName(GetPlayerBot(), "Mana Spring")) { CastSpell(MANA_SPRING_TOTEM, GetPlayerBot()); } return true; + return false; +} +#pragma region Change Totems +bool PlayerbotShamanAI::ChangeTotems(uint32 mode) +{ + uint32 earth=0, fire=0, water=0, air=0; + + PlayerbotAI *ai = GetAI(); + if(!ai) return false; + Player *m_bot = GetPlayerBot(); + if(!m_bot || m_bot->isDead()) return false; + + Unit *pTarget = m_bot->GetSelectedUnit(); + Unit *pVictim = NULL; + if (m_bot->GetSelectedUnit()->IsFriendlyTo(m_bot)) pTarget = NULL; + if (pTarget) pVictim = pTarget->getVictim(); + + //Defaults + if (!HasAuraName(m_bot,"Horn of Winter") )earth = STRENGTH_OF_EARTH_TOTEM; + if (!earth) earth = STONESKIN_TOTEM; + if (!earth) earth = EARTHBIND_TOTEM; + fire = TOTEM_OF_WRATH; + if (!fire) fire = FLAMETONGUE_TOTEM; + if (!fire) fire = SEARING_TOTEM; + water = MANA_SPRING_TOTEM; + if (!water) water = HEALING_STREAM_TOTEM; + if (TALENT_ELEMENTAL || TALENT_RESTO) air = WRATH_OF_AIR_TOTEM; + else air = WINDFURY_TOTEM; + + //Target reactive stuff + if (pTarget) + { + if (GROUNDING_TOTEM && pTarget->IsNonMeleeSpellCasted(true)) air = GROUNDING_TOTEM; + } + + if (STONESKIN_TOTEM && isUnderAttack()) earth = STONESKIN_TOTEM; + + uint32 totz[4] = {earth, fire, water, air}; + + for (int i = 0; i < 4; i++) + { + if (!totz[i]) continue; + SpellEntry const *tSpell = GetSpellStore()->LookupEntry(totz[i]); + if (!tSpell) continue; + uint32 tEntry = (uint32) tSpell->EffectMiscValue[0]; + if (!tEntry) continue; + CreatureInfo const *totemEntry = GetCreatureTemplateStore(tEntry); + if (!tEntry) continue; + + if (CanCast(totz[i], m_bot) && !m_bot->FindNearestCreature(tEntry,30)) { return CastSpell(totz[i],m_bot,false); } + } + return false; +} +#pragma endregion +#pragma region ChangeWeaponEnchants +bool PlayerbotShamanAI::ChangeWeaponEnchants() +{ + uint32 mhEnch = 0, ohEnch = 0; + + PlayerbotAI *ai = GetAI(); + if(!ai) return false; + Player *m_bot = GetPlayerBot(); + if(!m_bot || m_bot->isDead()) return false; + + + // Choose Weapon Enchant + if (TALENT_RESTO) { mhEnch = EARTHLIVING_WEAPON; } + else if (TALENT_ELEMENTAL){ mhEnch = FLAMETONGUE_WEAPON; } + else + { + if (WINDFURY_WEAPON) + { + mhEnch = WINDFURY_WEAPON; + if (m_bot->haveOffhandWeapon()) + { + if (LAVA_LASH) ohEnch = FLAMETONGUE_WEAPON; + else ohEnch = WINDFURY_WEAPON; + } + } + else if (FLAMETONGUE_WEAPON) + { + mhEnch = FLAMETONGUE_WEAPON; + if (m_bot->haveOffhandWeapon()) ohEnch = FLAMETONGUE_WEAPON; + } + + } + + Item* weap; + uint32 enchant_id = 0; + SpellEntry const *tSpell; + bool castedsomething = false; + + if (mhEnch) + { + weap = m_bot->GetWeaponForAttack(BASE_ATTACK); + if (weap) + { + tSpell = GetSpellStore()->LookupEntry(mhEnch); + if (tSpell && tSpell->EffectMiscValue[0] > 0) + { + enchant_id = (uint32) tSpell->EffectMiscValue[0]; + if (enchant_id && weap->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT) != enchant_id && sSpellItemEnchantmentStore.LookupEntry(enchant_id)) + { + m_bot->ApplyEnchantment(weap,TEMP_ENCHANTMENT_SLOT, false); //Remove old enchantment effect + weap->SetEnchantment(TEMP_ENCHANTMENT_SLOT, enchant_id, 1800 * 1000, 0); //Add for 30 mins + m_bot->ApplyEnchantment(weap, TEMP_ENCHANTMENT_SLOT, true); //Add new effect + castedsomething = true; + } + } + } + } + + if (ohEnch) + { + weap = m_bot->GetWeaponForAttack(OFF_ATTACK); + if (weap) + { + tSpell = GetSpellStore()->LookupEntry(ohEnch); + if (tSpell && tSpell->EffectMiscValue[0] > 0) + { + enchant_id = (uint32) tSpell->EffectMiscValue[0]; + if (enchant_id && weap->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT) != enchant_id && sSpellItemEnchantmentStore.LookupEntry(enchant_id)) + { + m_bot->ApplyEnchantment(weap,TEMP_ENCHANTMENT_SLOT, false); //Remove old enchantment effect + weap->SetEnchantment(TEMP_ENCHANTMENT_SLOT, enchant_id, 1800 * 1000, 0); //Add for 30 mins + m_bot->ApplyEnchantment(weap, TEMP_ENCHANTMENT_SLOT, true); //Add new effect + castedsomething = true; + } + } + } + } + return castedsomething; + +} +#pragma endregion diff --git a/src/server/game/AI/Bots/PlayerbotShamanAI.h b/src/server/game/AI/Bots/PlayerbotShamanAI.h new file mode 100644 index 0000000..e5fef23 --- /dev/null +++ b/src/server/game/AI/Bots/PlayerbotShamanAI.h @@ -0,0 +1,75 @@ +#ifndef _PLAYERBOTSHAMANAI_H +#define _PLAYERBOTSHAMANAI_H + +#include "PlayerbotClassAI.h" + +class PlayerbotShamanAI : PlayerbotClassAI +{ + public: + PlayerbotShamanAI(Player *const master, Player *const bot, PlayerbotAI *const ai); + virtual ~PlayerbotShamanAI(); + + virtual void LoadSpells(); + + //all combat actions go here + void DoNextCombatManeuver(Unit *); + + //all non combat actions go here, ex buffs, heals, REBIRTHes + void DoNonCombatActions(); + + //buff a specific player, usually a real PC who is not in group + bool BuffPlayer(Unit *target); + + //Heals the target based off its HP + bool HealTarget(Unit *target, uint8 hp); + + bool HealGroup (Unit *target, uint8 hp, uint8 &countNeedHeal); + + //Cures the target + bool CureTarget(Unit *target); + + bool RezTarget (Unit *target); + + bool ChangeTotems(uint32 mode); + + bool ChangeWeaponEnchants(); + + /*//find any specific mount spells, ie druids=cat, shaman=ghost wolf etc + virtual bool FindMount(); + virtual bool Unmount(); + virtual bool IsMounted(); */ + + private: + + //totems + uint32 HEALING_STREAM_TOTEM, MANA_SPRING_TOTEM, MANA_TIDE_TOTEM, CLEANSING_TOTEM, FIRE_RESISTANCE_TOTEM; //water + uint32 WINDFURY_TOTEM, WRATH_OF_AIR_TOTEM, GROUNDING_TOTEM, NATURE_RESISTANCE_TOTEM; //air + uint32 STRENGTH_OF_EARTH_TOTEM, EARTHBIND_TOTEM, STONESKIN_TOTEM, STONECLAW_TOTEM, TREMOR_TOTEM, EARTH_ELEMENTAL_TOTEM ; //earth + uint32 FLAMETONGUE_TOTEM, TOTEM_OF_WRATH, SEARING_TOTEM, MAGMA_TOTEM, FIRE_ELEMENTAL_TOTEM, FROST_RESISTANCE_TOTEM; //fire + uint32 TOTEMIC_RECALL, CALL_OF_THE_ELEMENTS, CALL_OF_THE_ANCESTORS, CALL_OF_THE_SPIRITS; + + //restoration + uint32 HEAL, LESSER_HEAL, CHAIN_HEAL, RIPTIDE, ANCESTRAL_SPIRIT, CLEANSE_SPIRIT; + + //offensive spells + uint32 LIGHTNING_BOLT, CHAIN_LIGHTNING, FIRE_NOVA, THUNDERSTORM, LAVA_BURST, EARTH_SHOCK, WIND_SHEAR, FLAME_SHOCK, FROST_SHOCK, PURGE, HEX ; + + //buffs + uint32 LIGHTNING_SHIELD, WATER_SHIELD, EARTH_SHIELD, HEROISM, ELEMENTAL_MASTERY, NATURES_SWIFTNESS, + WINDFURY_WEAPON, FLAMETONGUE_WEAPON, FROSTBRAND_WEAPON, ROCKBITER_WEAPON, EARTHLIVING_WEAPON, + WATER_BREATHING, WATER_WALKING ; + + //mellee + uint32 LAVA_LASH, STORMSTRIKE, SHAMANISTIC_RAGE, FERAL_SPIRIT; + + uint32 GHOST_WOLF; + + //special + uint32 EXHAUSTION, SATED, MAELSTROM_WEAPON; + + uint32 TALENT_ELEMENTAL, TALENT_ENHANCEMENT, TALENT_RESTO; + +}; + + +#endif diff --git a/src/server/game/AI/Bots/PlayerbotWarlockAI.cpp b/src/server/game/AI/Bots/PlayerbotWarlockAI.cpp new file mode 100644 index 0000000..517c6e4 --- /dev/null +++ b/src/server/game/AI/Bots/PlayerbotWarlockAI.cpp @@ -0,0 +1,409 @@ +/* +Name : PlayerbotWarlockAI.cpp +Complete: maybe around 60% + +Limitations: - Talent build decision is made by key talent spells, which makes them viable only after level 50-ish.. Behaviour does not change though.. + - Curse checks are slow, later all curses should be looked up in one loop + - Need a function to lookup pet known spells for better pet handling + - Warlock do not summon other pets than Fel hunter/Imp + +Authors : SwaLLoweD +Version : 0.40 +*/ +#include "PlayerbotWarlockAI.h" + +class PlayerbotAI; +PlayerbotWarlockAI::PlayerbotWarlockAI(Player *const master, Player *const bot, PlayerbotAI *const ai): PlayerbotClassAI(master, bot, ai) +{ + foodDrinkSpamTimer = 0; + LoadSpells(); +} +PlayerbotWarlockAI::~PlayerbotWarlockAI(){} + +void PlayerbotWarlockAI::LoadSpells() { + PlayerbotAI *ai = GetAI(); + if (!ai) return; + #pragma region SpellId Fill + //CURSES + CURSE_OF_ELEMENTS = ai->getSpellIdExact("Curse of Elements"); + CURSE_OF_WEAKNESS = ai->getSpellIdExact("Curse of Weakness"); + CURSE_OF_AGONY = ai->getSpellIdExact("Curse of Agony"); + CURSE_OF_RECKLESSNESS = ai->getSpellIdExact("Curse of Recklessness"); + CURSE_OF_TONGUES = ai->getSpellIdExact("Curse of Tongues"); + CURSE_OF_DOOM = ai->getSpellIdExact("Curse of Doom"); + CURSE_OF_EXHAUSTION = ai->getSpellIdExact("Curse of Exhaustion"); + + + //AFFLICTION + CORRUPTION = ai->getSpellIdExact("Corruption"); + DRAIN_SOUL = ai->getSpellIdExact("Drain Soul"); + DRAIN_LIFE = ai->getSpellIdExact("Drain Life"); + DRAIN_MANA = ai->getSpellIdExact("Drain Mana"); + SIPHON_LIFE = ai->getSpellIdExact("Siphon Life"); + UNSTABLE_AFFLICTION = ai->getSpellIdExact("Unstable Affliction"); + HAUNT = ai->getSpellIdExact("Haunt"); + SEED_OF_CORRUPTION = ai->getSpellIdExact("Seed of Corruption"); + DEATH_COIL = ai->getSpellIdExact("Death Coil"); + + + //DESTRUCTION + SHADOW_BOLT = ai->getSpellIdExact("Shadow Bolt"); + IMMOLATE = ai->getSpellIdExact("Immolate"); + INCINERATE = ai->getSpellIdExact("Incinerate"); + SEARING_PAIN = ai->getSpellIdExact("Searing Pain"); + CONFLAGRATE = ai->getSpellIdExact("Conflagrate"); + SOUL_FIRE = ai->getSpellIdExact("Soul Fire"); + SHADOWBURN = ai->getSpellIdExact("Shadowburn"); + SHADOWFURY = ai->getSpellIdExact("Shadowfury"); + CHAOS_BOLT = ai->getSpellIdExact("Chaos Bolt"); + SHADOWFLAME = ai->getSpellIdExact("Shadowflame"); + RAIN_OF_FIRE = ai->getSpellIdExact("Rain of Fire"); + HELLFIRE = ai->getSpellIdExact("Hellfire"); + + + //DEMONOLOGY + DEMON_ARMOR = ai->getSpellIdExact("Demon Armor"); + if (!DEMON_ARMOR) DEMON_ARMOR = ai->getSpellIdExact("Demon Skin"); + FEL_ARMOR = ai->getSpellIdExact("Fel Armor"); + SOULSHATTER = ai->getSpellIdExact("Soulshatter"); + HEALTH_FUNNEL = ai->getSpellIdExact("Health Funnel"); + DARK_PACT = ai->getSpellIdExact("Dark Pact"); + SOUL_LINK = ai->getSpellIdExact("Soul Link"); + DEMONIC_EMPOWERMENT = ai->getSpellIdExact("Demonic Empowerment"); + METAMORPHOSIS = ai->getSpellIdExact("Metamorphosis"); //Original is learn spell + SUMMON_IMP = ai->getSpellIdExact("Summon Imp"); + SUMMON_VOIDWALKER = ai->getSpellIdExact("Summon Voidwalker"); + SUMMON_SUCCUBUS = ai->getSpellIdExact("Summon Succubus"); + SUMMON_FELHUNTER = ai->getSpellIdExact("Summon Felhunter"); + SUMMON_FELGUARD = ai->getSpellIdExact("Summon Felguard"); + + + //CC + FEAR = ai->getSpellIdExact("Fear"); + HOWL_OF_TERROR = ai->getSpellIdExact("Howl of Terror"); + BANISH = ai->getSpellIdExact("Banish"); + ENSLAVE_DEMON = ai->getSpellIdExact("Enslave Demon"); + + + //Buff + UNENDING_BREATH = ai->getSpellIdExact("Unending Breath"); + DETECT_INVISIBILITY = ai->getSpellIdExact("Detect Invisibility"); + SHADOW_WARD = ai->getSpellIdExact("Shadow Ward"); + + + //Other + LIFE_TAP = ai->getSpellIdExact("Life Tap"); + CREATE_SOULSTONE = ai->getSpellIdExact("Create Soulstone"); + + + SOUL_SHARD = 6265; //Soul Shard Item id + P_BACKLASH = 34936; //Backlash proc + P_NIGHTFALL= 17941; //Nightfall proc + SHOOT = ai->getSpellIdExact("Shoot"); + + TALENT_DEMONOLOGY = SUMMON_FELGUARD; + TALENT_AFFLICTION = UNSTABLE_AFFLICTION; + TALENT_DESTRUCTION = CONFLAGRATE; + + uint8 talentCounter = 0; + if (TALENT_DEMONOLOGY) talentCounter++; + if (TALENT_AFFLICTION) talentCounter++; + if (TALENT_DESTRUCTION) talentCounter++; + if (talentCounter > 1) { TALENT_DEMONOLOGY = 0; TALENT_AFFLICTION = 0; TALENT_DESTRUCTION = 0; } //Unreliable Talent detection. + #pragma endregion +} + +void PlayerbotWarlockAI::DoNextCombatManeuver(Unit *pTarget) +{ + if (!pTarget || pTarget->isDead()) return; + PlayerbotAI *ai = GetAI(); + if (!ai) return; + Player *m_bot = GetPlayerBot(); + if (!m_bot || m_bot->isDead()) return; + Unit *pVictim = pTarget->getVictim(); + Unit *m_tank = FindMainTankInRaid(GetMaster()); + if (!m_tank && m_bot->GetGroup() && GetMaster()->GetGroup() != m_bot->GetGroup()) { FindMainTankInRaid(m_bot); } + if (!m_tank) { m_tank = m_bot; } + uint32 masterHP = GetMaster()->GetHealth()*100 / GetMaster()->GetMaxHealth(); + float pDist = m_bot->GetDistance(pTarget); + uint8 pThreat = GetThreatPercent(pTarget); + + Pet *pet = m_bot->GetPet(); + if (m_tank->GetGUID() == m_bot->GetGUID() && pet && pet->isAlive() && pet->isInCombat()) { m_tank = pet; } + uint8 petThreat = 0; + if (pet) { GetThreatPercent(pTarget,pet); } + + /*switch(ai->GetScenarioType()) + { + case SCENARIO_DUEL: + if(SHADOW_BOLT > 0) CastSpell(SHADOW_BOLT); + return; + }*/ + + //------- Non Duel combat ---------- + + //ai->Follow(*GetMaster()); //don't want to melee mob + + #pragma region Choose Target + // Choose actions accoring to talents (WARLOCK is always ranged dps) + m_role = BOT_ROLE_DPS_RANGED; + + // if i am under attack and if i am not tank or offtank: change target if needed + if (m_tank->GetGUID() != m_bot->GetGUID() && isUnderAttack() ) + { + // Keep hitting but reduce threat + if (CastSpell(SOULSHATTER,m_bot)) { return; } + //else if (m_bot->getRace() == (uint8) RACE_NIGHTELF && CastSpell(R_SHADOWMELD,m_bot)) { return; } + else //I cannot reduce threat so + { + if (pVictim && pVictim->GetGUID() == m_bot->GetGUID() && pDist <= 2) { } // My target is almost up to me, no need to search + else //Have to select nearest target + { + Unit *curAtt = GetNearestAttackerOf(m_bot); + if (curAtt && curAtt->GetGUID() != pTarget->GetGUID()) + { + m_bot->SetSelection(curAtt->GetGUID()); + //ai->AddLootGUID(curAtt->GetGUID()); + DoNextCombatManeuver(curAtt); //Restart new update to get variables fixed.. + return; + } + } + //my target is attacking me + } + } + + #pragma endregion + + #pragma region Pet Actions + // Pet's own Actions + if( pet && pet->isAlive() ) + { + // Setup pet + if (pet->GetCharmInfo()->IsAtStay()) {pet->GetCharmInfo()->SetCommandState(COMMAND_FOLLOW); } + //if (pet->HasSpell(BLOOD_PACT) && ); //Cast Blood Pact + + if ( ( ((float)pet->GetHealth()/(float)pet->GetMaxHealth()) < 0.5f ) + && ( HEALTH_FUNNEL>0 && !pet->getDeathState() != ALIVE && pVictim != m_bot + && CastSpell(HEALTH_FUNNEL,m_bot) )) { return; } //Heal pet + + // Set pet to attack warlock's attacker > its own attackers > warlock's target + if (!pet->getVictim()) { pet->AI()->AttackStart(pTarget); } + else if (isUnderAttack(m_bot)) { pet->AI()->AttackStart(pTarget); } //Always help warlock if he's under attack + else if (pet->getVictim()->GetGUID() != pTarget->GetGUID() && !isUnderAttack(pet)) { pet->AI()->AttackStart(pTarget); } + else if (isUnderAttack(pet)) // Pet is under attack and warlock has no attackers + { + if ( pet->getVictim()->getVictim() && pet->getVictim()->getVictim()->GetGUID() == pet->GetGUID() && pDist <= 2) { } // My target is almost up to me, no need to search + else //Have to select nearest target + { + Unit *curAtt = GetNearestAttackerOf(pet,true); + if (curAtt && (!pet->getVictim() || curAtt->GetGUID() != pet->getVictim()->GetGUID())) + { + pet->AI()->AttackStart(curAtt); //Attack nearest attacker + } + } + //Actions to do under attack (Always tank it, and try to kill it, until someone (!= warlock) takes aggro back) + //Warlock should help her pet whether main tank or not, unless he's being attacked (BEWARE Targeting Loop possibility) + if (pet->getVictim() && !isUnderAttack(m_bot) && pet->getVictim()->GetGUID() != pTarget->GetGUID()) + { + m_bot->SetSelection(pet->getVictim()->GetGUID()); + DoNextCombatManeuver(pet->getVictim()); //Restart new update to get variables fixed.. + return; + } + + } + // Pet tanking behaviour + if (pet->GetGUID() == m_tank->GetGUID() || isUnderAttack(m_bot) || isUnderAttack(pet)) + { + //need pet tanking spells + //if (GROWL) pet->GetCharmInfo()->SetSpellAutocast(GROWL,true); //Autocast growl + } + else + { + //if (GROWL) pet->GetCharmInfo()->SetSpellAutocast(GROWL,false); //Do not try to get aggro + } + // NORMAL PET dps attacks + if (petThreat < threatThreshold || pet->GetGUID() == m_tank->GetGUID() || isUnderAttack(m_bot)) + { + //if (CastSpell(KILL_COMMAND,m_bot)) { } + } + } + #pragma endregion + + TakePosition(pTarget); + // If there's a cast stop + if (m_bot->HasUnitState(UNIT_STAT_CASTING)) { return; } + + //Buff + if (isUnderAttack()) { if (CastSpell (DEMON_ARMOR, m_bot)) { return; } } + else if (FEL_ARMOR) { if (CastSpell(FEL_ARMOR,m_bot)) { return; } } + else if (CastSpell(DEMON_ARMOR, m_bot)) { return; } + if (CastSpell(METAMORPHOSIS,m_bot)) { return; } + if (CastSpell(DEMONIC_EMPOWERMENT,m_bot)) { return; } + + //Protect + if (m_tank->GetGUID() != m_bot->GetGUID() && pVictim && pVictim->GetGUID() == m_bot->GetGUID() ) + { + if (CastSpell(SOULSHATTER, m_bot)) { return; } + if (pTarget->GetCreatureType() == CREATURE_TYPE_DEMON && CastSpell(BANISH,pTarget)) { return; } + //if (m_bot->getRace() == (uint8) RACE_NIGHTELF && isUnderAttack() && CastSpell(R_SHADOWMELD, m_bot)) { return; } + } + if (isUnderAttack() && CastSpell(FEAR, pTarget)) { return; } + if (isUnderAttack() && CastSpell(HOWL_OF_TERROR, pTarget)) { return; } + if (isUnderAttack() && CastSpell(SHADOWFURY, pTarget)) { return; } + if (m_bot->getRace() == (uint8) RACE_DWARF && ai->GetHealthPercent() < 75 && CastSpell(R_STONEFORM,m_bot)) { } //no gcd + if (m_bot->getRace() == (uint8) RACE_DRAENEI && ai->GetHealthPercent() < 55 && CastSpell(R_GIFT_OF_NAARU,m_bot)) { return; } //no Gcd, but has cast + if (m_bot->getRace() == (uint8) RACE_TAUREN && pDist < 8 && CastSpell(R_WAR_STOMP, pTarget)) { return; } //no gcd but is cast + //Void Walker shield? + if (ai->GetHealthPercent() < 70 && CastSpell(DEATH_COIL,pTarget)) { return; } + if (ai->GetHealthPercent() < 70 && CastSpell(DRAIN_LIFE,pTarget)) { return; } + if (ai->GetManaPercent() < 70 && ai->GetManaPercent(*pTarget) > 10 && CastSpell(DRAIN_MANA,pTarget)) { return; } + if (ai->GetManaPercent() < 50 && pet && ai->GetManaPercent(*pet) > 50 && CastSpell(DARK_PACT,pet,1,0,1)) { return; } + if (ai->GetManaPercent() < 30 && ai->GetHealthPercent() > 60 && CastSpell(LIFE_TAP,m_bot,1,0,1)) { return; } + //Use Health stone + + //Break spells + if (m_bot->getRace() == (uint8) RACE_BLOODELF && pDist < 8 && (pTarget->IsNonMeleeSpellCasted(true) || ai->GetManaPercent() < 40) && CastSpell(R_ARCANE_TORRENT, pTarget)) { } //no gcd + else if (pTarget->IsNonMeleeSpellCasted(true) && CastSpell(CURSE_OF_TONGUES, pTarget)) { return; } + + //Catch + if (pTarget->HasUnitMovementFlag(UNIT_FLAG_FLEEING)) + { + if (CastSpell(CURSE_OF_EXHAUSTION,pTarget)) return; + } + + // Threat control + if (pThreat < threatThreshold || m_tank->GetGUID() == m_bot->GetGUID() ) { } //Continue attack + else + { + if (pet && isUnderAttack(pet) && pet->getVictim() && pet->getVictim()->GetGUID() != pTarget->GetGUID()) //Should be helping pet + { + m_bot->SetSelection(pet->getVictim()->GetGUID()); + return; + } + else if (m_tank->getVictim() && m_tank->getVictim()->GetGUID() != pTarget->GetGUID()) // I am attacking wrong target!! + { + m_bot->SetSelection(m_tank->getVictim()->GetGUID()); + return; + } + else if (CastSpell(SOULSHATTER,m_bot)) { return; } + else if (m_bot->FindCurrentSpellBySpellId(SHOOT)) { m_bot->InterruptNonMeleeSpells( true, SHOOT ); return; } //Disable wand + else { return; } // No more threat reducing spells, just slow down + } + + + + //Urgent DPS + if ((m_bot->HasAura(P_NIGHTFALL) || m_bot->HasAura(P_BACKLASH)) && CastSpell(SHADOW_BOLT,pTarget)) { return; } + if (INCINERATE && pTarget->HasAura(IMMOLATE,m_bot->GetGUID()) && CastSpell(INCINERATE,pTarget)) { return; } + if (CONFLAGRATE && (pTarget->HasAura(IMMOLATE,m_bot->GetGUID()) || pTarget->HasAura(SHADOWFLAME,m_bot->GetGUID())) && CastSpell(CONFLAGRATE,pTarget)) { return; } + + if (ai->GetHealthPercent(*pTarget) < 2 && CastSpell(SHADOWBURN,pTarget)) { return; } + if (ai->GetHealthPercent(*pTarget) < 5 && m_bot->GetItemCount(SOUL_SHARD) < 20 && CastSpell(DRAIN_SOUL,pTarget)) { return; } + + //Dps up + if (ai->GetHealthPercent(*pTarget) > 95) { return; } // dont dps too early + + if (m_bot->getRace() == (uint8) RACE_TROLL && CastSpell(R_BERSERKING,m_bot)) {} //no GCD + if (m_bot->getRace() == (uint8) RACE_ORC && CastSpell(R_BLOOD_FURY,m_bot)) {} //no GCD + if (CastSpell(HAUNT,pTarget)) { return; } + + //AOE + if (isUnderAttack(m_tank,4) && CastSpell(SHADOWFLAME,pTarget)) { return; } + if (isUnderAttack(m_tank,4) && CastSpell(RAIN_OF_FIRE,pTarget)) { return; } + + //Dps Main + if(CURSE_OF_ELEMENTS) { if (!CheckCurse(pTarget) && CastSpell(CURSE_OF_ELEMENTS, pTarget)) { return; } } //curse of elements trumps any other curses + else if (CURSE_OF_AGONY) { if (!CheckCurse(pTarget) && CastSpell(CURSE_OF_AGONY, pTarget)) { return; } } + + if (SEED_OF_CORRUPTION && isUnderAttack(m_tank,4)) { if (CastSpell(SEED_OF_CORRUPTION,pTarget)) { return;} } + else if (CastSpell(CORRUPTION,pTarget)) { return; } + + if (CastSpell(CHAOS_BOLT,pTarget)) { return; } + + if (UNSTABLE_AFFLICTION) { if (CastSpell(UNSTABLE_AFFLICTION,pTarget)) { return; } } + else if (CastSpell(IMMOLATE,pTarget)) { return; } + + if (CastSpell(SHADOW_BOLT,pTarget)) { return; } + + + + + //Use healthstone?? + // drink poition + if(ai->GetManaPercent() < 5 ) + { + Item *pItem = ai->FindPotion(); + if(pItem != NULL) + { + if (pItem->GetSpell() && m_bot->HasSpellCooldown(pItem->GetSpell()) ) { return; } //pot is in cooldown + ai->UseItem(*pItem); + } + } + +} //end DoNextCombatManeuver + +void PlayerbotWarlockAI::DoNonCombatActions() +{ + PlayerbotAI *ai = GetAI(); + Player *m_bot = GetPlayerBot(); + if (!m_bot || !ai || m_bot->isDead()) { return; } + + //If Casting or Eating/Drinking return + if (m_bot->HasUnitState(UNIT_STAT_CASTING)) { return; } + if (m_bot->getStandState() == UNIT_STAND_STATE_SIT) { return; } + + //buff and heal raid + if (DoSupportRaid(m_bot,30,0,0,0,0,1)) { return; } + + //Own Buffs + if (CastSpell (FEL_ARMOR, m_bot)) { return; } + if (!FEL_ARMOR && CastSpell(DEMON_ARMOR, m_bot)) { return; } + if (SOUL_LINK && m_bot->GetPet() && !m_bot->HasAuraType(SPELL_AURA_SPLIT_DAMAGE_PCT) && CastSpell(SOUL_LINK,m_bot)) { return; } + + if(m_bot->GetPet() == NULL) { + if (SUMMON_FELGUARD) + CastSpell(SUMMON_FELGUARD, m_bot); + else + CastSpell(SUMMON_IMP, m_bot); + } else { + m_bot->GetPet()->GetCharmInfo()->SetCommandState(COMMAND_FOLLOW); + m_bot->GetPet()->GetCharmInfo()->SetIsCommandAttack(false); + } + + //Create Healthstone? + + //mana/hp check + //Don't bother with eating, if low on hp, just let it heal themself + if (m_bot->getRace() == (uint8) RACE_UNDEAD_PLAYER && ai->GetHealthPercent() < 75 && CastSpell(R_CANNIBALIZE,m_bot)) { return; } + if (ai->GetManaPercent() < 50 && ai->GetHealthPercent() > 60 && CastSpell (LIFE_TAP, m_bot)) { return; } + if (ai->GetManaPercent() < 50 || ai->GetHealthPercent() < 50) { ai->Feast(); } + + +} //end DoNonCombatActions + +bool PlayerbotWarlockAI::BuffPlayer(Unit *target) +{ + if (!target || target->isDead()) return false; + + if (!HasAuraName(target, DETECT_INVISIBILITY) && CastSpell(DETECT_INVISIBILITY, target)) { return true; } + if (!HasAuraName(target, UNENDING_BREATH) && CastSpell(UNENDING_BREATH, target)) { return true; } + return false; +} + +uint32 PlayerbotWarlockAI::CheckCurse(Unit *target) +{ + Player *m_bot = GetPlayerBot(); + if (!m_bot || m_bot->isDead() || !target || target->isDead() ) { return 0; } + uint64 mGuid = m_bot->GetGUID(); + + if (CURSE_OF_ELEMENTS && target->HasAura(CURSE_OF_ELEMENTS,mGuid)) { return CURSE_OF_ELEMENTS; } + if (CURSE_OF_AGONY && target->HasAura(CURSE_OF_AGONY,mGuid)) { return CURSE_OF_AGONY; } + if (CURSE_OF_TONGUES && target->HasAura(CURSE_OF_TONGUES,mGuid)) { return CURSE_OF_TONGUES; } + if (CURSE_OF_WEAKNESS && target->HasAura(CURSE_OF_WEAKNESS,mGuid)) { return CURSE_OF_WEAKNESS; } + if (CURSE_OF_DOOM && target->HasAura(CURSE_OF_DOOM,mGuid)) { return CURSE_OF_DOOM; } + if (CURSE_OF_RECKLESSNESS && target->HasAura(CURSE_OF_RECKLESSNESS,mGuid)) { return CURSE_OF_RECKLESSNESS; } + if (CURSE_OF_EXHAUSTION && target->HasAura(CURSE_OF_EXHAUSTION,mGuid)) { return CURSE_OF_EXHAUSTION; } + return 0; +} + +//void PlayerbotWarlockAI::BuffPlayer(Player *target){} diff --git a/src/server/game/AI/Bots/PlayerbotWarlockAI.h b/src/server/game/AI/Bots/PlayerbotWarlockAI.h new file mode 100644 index 0000000..f5a2cb0 --- /dev/null +++ b/src/server/game/AI/Bots/PlayerbotWarlockAI.h @@ -0,0 +1,55 @@ +#ifndef _PlayerbotWarlockAI_H +#define _PlayerbotWarlockAI_H + +#include "PlayerbotClassAI.h" + +//class Player; + +class PlayerbotWarlockAI : PlayerbotClassAI +{ + public: + PlayerbotWarlockAI(Player *const master, Player *const bot, PlayerbotAI *const ai); + virtual ~PlayerbotWarlockAI(); + + virtual void LoadSpells(); + + //all combat actions go here + void DoNextCombatManeuver(Unit *); + + //all non combat actions go here, ex buffs, heals, rezzes + void DoNonCombatActions(); + + //buff a specific player, usually a real PC who is not in group + bool BuffPlayer(Unit *target); + + uint32 CheckCurse(Unit *target); + + private: + //CURSES + uint32 CURSE_OF_ELEMENTS, CURSE_OF_WEAKNESS, CURSE_OF_AGONY, CURSE_OF_RECKLESSNESS, CURSE_OF_TONGUES, CURSE_OF_DOOM, CURSE_OF_EXHAUSTION; + + //AFFLICTION + uint32 CORRUPTION, DRAIN_SOUL, DRAIN_LIFE, DRAIN_MANA, SIPHON_LIFE, UNSTABLE_AFFLICTION, HAUNT, SEED_OF_CORRUPTION, DEATH_COIL; + + //DESTRUCTION + uint32 SHADOW_BOLT, IMMOLATE, INCINERATE, SEARING_PAIN, CONFLAGRATE, SOUL_FIRE, SHADOWBURN, SHADOWFURY, CHAOS_BOLT, SHADOWFLAME, RAIN_OF_FIRE, HELLFIRE; + + //DEMONOLOGY + uint32 DEMON_ARMOR, FEL_ARMOR, SOULSHATTER, HEALTH_FUNNEL, DARK_PACT, SOUL_LINK, DEMONIC_EMPOWERMENT, METAMORPHOSIS, SUMMON_IMP, SUMMON_VOIDWALKER, SUMMON_SUCCUBUS, SUMMON_FELHUNTER, SUMMON_FELGUARD; + + //CC + uint32 FEAR, HOWL_OF_TERROR, BANISH, ENSLAVE_DEMON; + + //Buff + uint32 UNENDING_BREATH, DETECT_INVISIBILITY, SHADOW_WARD; + + //Other + uint32 LIFE_TAP, CREATE_SOULSTONE; + + //Special + uint32 SOUL_SHARD, P_BACKLASH, P_NIGHTFALL, SHOOT; + + uint32 TALENT_DEMONOLOGY, TALENT_AFFLICTION, TALENT_DESTRUCTION; +}; + +#endif diff --git a/src/server/game/AI/Bots/PlayerbotWarriorAI.cpp b/src/server/game/AI/Bots/PlayerbotWarriorAI.cpp new file mode 100644 index 0000000..29ad3ad --- /dev/null +++ b/src/server/game/AI/Bots/PlayerbotWarriorAI.cpp @@ -0,0 +1,400 @@ +/* +Name : PlayerbotWarrior.cpp +Complete: maybe around 75% + +Limitations: - Talent build decision is made by key talent spells, which makes them viable only after level 50-ish.. Until then default behaviour is Blood dps/offtank type + - Tanking bots should taunt if any group member is under attack, currently only saves master + - Situations needing Intervene casting : limited / non-existant.. + - Intervene / Piercing Howl / Hamstring are not used.. + +Authors : SwaLLoweD +Version : 0.40 +*/ + +#include "PlayerbotWarriorAI.h" + +class PlayerbotAI; +PlayerbotWarriorAI::PlayerbotWarriorAI(Player *const master, Player *const bot, PlayerbotAI *const ai): PlayerbotClassAI(master, bot, ai) +{ + foodDrinkSpamTimer = 0; + LoadSpells(); +} +PlayerbotWarriorAI::~PlayerbotWarriorAI(){} + +void PlayerbotWarriorAI::LoadSpells() { + PlayerbotAI *ai = GetAI(); + if (!ai) return; + #pragma region SpellId Fill + //Defensive Stance + SHIELD_WALL = ai->getSpellIdExact("Shield Wall"); + REVENGE = ai->getSpellIdExact("Revenge"); + SHIELD_BLOCK = ai->getSpellIdExact("Shield Block"); + DISARM = ai->getSpellIdExact("Disarm"); + INTERVENE = ai->getSpellIdExact("Intervene"); + + //Berserker Stance + RECKLESSNESS = ai->getSpellIdExact("Recklessness"); + WHIRLWIND = ai->getSpellIdExact("Whirlwind"); + PUMMEL = ai->getSpellIdExact("Pummel"); + INTERCEPT = ai->getSpellIdExact("Intercept"); + + //Battle Stance + RETALIATION = ai->getSpellIdExact("Retaliation"); + CHARGE = ai->getSpellIdExact("Charge"); + OVERPOWER = ai->getSpellIdExact("Overpower"); + SHATTERING_THROW = ai->getSpellIdExact("Shattering Throw"); + + //Mixed Attacks + REND = ai->getSpellIdExact("Rend"); // 1 2 + THUNDER_CLAP = ai->getSpellIdExact("Thunder Clap"); + SPELL_REFLECTION = ai->getSpellIdExact("Spell Reflection"); + SHIELD_BASH = ai->getSpellIdExact("Shield Bash"); + EXECUTE = ai->getSpellIdExact("Execute"); // 1 3 + HAMSTRING = ai->getSpellIdExact("Hamstring"); + SWEEPING_STRIKES = ai->getSpellIdExact("Sweeping Strikes"); + VICTORY_RUSH = ai->getSpellIdExact("Victory Rush"); + + + //General attacks + HEROIC_STRIKE = ai->getSpellIdExact("Heroic Strike"); + MORTAL_STRIKE = ai->getSpellIdExact("Mortal Strike"); + BLOODTHIRST = ai->getSpellIdExact("Bloodthirst"); + SHIELD_SLAM = ai->getSpellIdExact("Shield Slam"); + SHOCKWAVE = ai->getSpellIdExact("Shockwave"); + SLAM = ai->getSpellIdExact("Slam"); + CLEAVE = ai->getSpellIdExact("Cleave"); + BLADESTORM = ai->getSpellIdExact("Bladestorm"); + HEROIC_THROW = ai->getSpellIdExact("Heroic Throw"); + CONCUSSION_BLOW = ai->getSpellIdExact("Concussion Blow"); + SUNDER_ARMOR = ai->getSpellIdExact("Sunder Armor"); + DEMORALIZING_SHOUT = ai->getSpellIdExact("Demoralizing Shout"); + INTIMIDATING_SHOUT = ai->getSpellIdExact("Intimidating Shout"); + PIERCING_HOWL = ai->getSpellIdExact("Piercing Howl"); + DEVASTATE = ai->getSpellIdExact("Devastate"); + + + //buffs + COMMANDING_SHOUT = ai->getSpellIdExact("Commanding Shout"); + BATTLE_SHOUT = ai->getSpellIdExact("Battle Shout"); + VIGILANCE = ai->getSpellIdExact("Vigilance"); + BERSERKER_RAGE = ai->getSpellIdExact("Berserker Rage"); + ENRAGED_REGENERATION = ai->getSpellIdExact("Enraged Regeneration"); + BLOODRAGE = ai->getSpellIdExact("Bloodrage"); + LAST_STAND = ai->getSpellIdExact("Last Stand"); + HEROIC_FURY = ai->getSpellIdExact("Heroic Fury"); + DEATH_WISH = ai->getSpellIdExact("Death Wish"); + + + //Stances + DEFENSIVE_STANCE = ai->getSpellIdExact("Defensive Stance"); + BATTLE_STANCE = ai->getSpellIdExact("Battle Stance"); + BERSERKER_STANCE = ai->getSpellIdExact("Berserker Stance"); + + + //Taunts + TAUNT = ai->getSpellIdExact("Taunt"); + CHALLENGING_SHOUT = ai->getSpellIdExact("Challenging Shout"); + MOCKING_BLOW = ai->getSpellIdExact("Mocking Blow"); + + //Special + SLAMM = 46916; //Instant Slam (Blood Surge) + + TALENT_ARMS = MORTAL_STRIKE; + TALENT_FURY = BLOODTHIRST; + TALENT_PROT = DEVASTATE; + + SHOOT = ai->getSpellIdExact("Shoot"); + + uint8 talentCounter = 0; + if (TALENT_ARMS) talentCounter++; + if (TALENT_FURY) talentCounter++; + if (TALENT_PROT) talentCounter++; + if (talentCounter > 1) { TALENT_ARMS = 0; TALENT_FURY = 0; TALENT_PROT = 0; } //Unreliable Talent detection. + #pragma endregion +} + +void PlayerbotWarriorAI::DoNextCombatManeuver(Unit *pTarget) +{ + if (!pTarget || pTarget->isDead()) return; + PlayerbotAI *ai = GetAI(); + if (!ai) return; + Player *m_bot = GetPlayerBot(); + if (!m_bot || m_bot->isDead()) return; + Unit *pVictim = pTarget->getVictim(); + Unit *m_tank = FindMainTankInRaid(GetMaster()); + if (!m_tank && m_bot->GetGroup() && GetMaster()->GetGroup() != m_bot->GetGroup()) { FindMainTankInRaid(m_bot); } + if (!m_tank) { m_tank = m_bot; } + uint32 masterHP = GetMaster()->GetHealth()*100 / GetMaster()->GetMaxHealth(); + float pDist = m_bot->GetDistance(pTarget); + uint8 pThreat = GetThreatPercent(pTarget); + + if (!m_pulling){ + #pragma region Choose Role / Stance + + m_role = BOT_ROLE_DPS_MELEE; + + // Choose Stance + if (m_tank->GetGUID() == m_bot->GetGUID()) // Hey! I am Main Tank + { + if (ChangeStance(DEFENSIVE_STANCE)) { m_role = BOT_ROLE_TANK; return; } //m_bot->GetShield(true) + } + else if (isUnderAttack()) // I am under attack + { + if (pVictim && pVictim->GetGUID() == m_bot->GetGUID() && pDist <= 2) {} // My target is almost up to me, no need to search + else //Have to select nearest target + { + Unit *curAtt = GetNearestAttackerOf(m_bot); + if (curAtt && curAtt->GetGUID() != pTarget->GetGUID()) + { + m_bot->SetSelection(curAtt->GetGUID()); + //ai->AddLootGUID(curAtt->GetGUID()); + DoNextCombatManeuver(curAtt); //Restart new update to get variables fixed.. + return; + } + } + //my target is attacking me + //if (m_bot->getRace() == (uint8) RACE_NIGHTELF && CanCast(R_SHADOWMELD,m_bot) && CastSpell(R_SHADOWMELD,m_bot) ) { return; } + if (m_bot->GetShield(true)) { if (ChangeStance(DEFENSIVE_STANCE)) { m_role = BOT_ROLE_OFFTANK; return; } } + else if (ChangeStance(BATTLE_STANCE)) { return; } + } + else if (ai->GetHealthPercent() > 90) + { + if (ChangeStance(BERSERKER_STANCE)) { return; } + } + else if (ai->GetForm() != FORM_BERSERKERSTANCE || ai->GetHealthPercent() < 70 ) { if (ChangeStance(BATTLE_STANCE)) { return; } } + #pragma endregion + } + + // Cast CC breakers if any match found (does not work yet) + // uint32 ccSpells[7] = { HEROIC_FURY, BERSERKER_RAGE, BLADESTORM, R_ESCAPE_ARTIST, R_EVERY_MAN_FOR_HIMSELF, R_WILL_OF_FORSAKEN, R_STONEFORM }; + // if (castSelfCCBreakers(ccSpells)) { } //most of them dont have gcd + + TakePosition(pTarget); + + // If there's a cast stop + if(m_bot->HasUnitState(UNIT_STAT_CASTING)) { return; } + + if (m_pulling) { + if (GetAI()->CastSpell(SHOOT,pTarget)) { + m_pulling = false; + GetAI()->SetCombatOrder(ORDERS_NONE); + GetAI()->Follow(*GetMaster()); + GetAI()->SetIgnoreUpdateTime(2); + } + return; + } + + #pragma region Buff Heal Interrupt + //Buff UP + if (!m_bot->HasAura(BATTLE_SHOUT) && CastSpell(BATTLE_SHOUT,m_bot)) { return; } + if (!m_bot->HasAura(BATTLE_SHOUT,m_bot->GetGUID()) && !m_bot->HasAura(COMMANDING_SHOUT) && CastSpell(COMMANDING_SHOUT,m_bot)) { return; } + + + //HEAL UP && PROTECT UP + if (ai->GetHealthPercent() <= 85 && CastSpell(SHIELD_BLOCK, m_bot)) { } //no GCD + if (ai->GetHealthPercent() <= 45 && CastSpell(SHIELD_WALL, m_bot)) { return; } + if (ai->GetHealthPercent() < 55 && + (m_bot->HasAura(BERSERKER_RAGE) || m_bot->HasAura(BLOODRAGE) || m_bot->HasAura(DEATH_WISH)) //There are other spells that count as enrage + && CastSpell(ENRAGED_REGENERATION,m_bot)) { return; } + if (ai->GetHealthPercent() < 25 && CastSpell(INTIMIDATING_SHOUT, m_bot)) { return; } + if (ai->GetHealthPercent() <= 75 && CastSpell(LAST_STAND, m_bot)) { return; } + if (m_bot->getRace() == (uint8) RACE_DWARF && ai->GetHealthPercent() < 75 && CastSpell(R_STONEFORM,m_bot)) { } //no gcd + if (m_bot->getRace() == (uint8) RACE_DRAENEI && ai->GetHealthPercent() < 55 && CastSpell(R_GIFT_OF_NAARU,m_bot)) { return; } //no Gcd, but has cast + + //Break spells being cast + if (pTarget->IsNonMeleeSpellCasted(true)) + { + if (pVictim && pVictim->GetGUID() == m_bot->GetGUID() && CastSpell(SPELL_REFLECTION,pTarget)) { return; } + if (m_bot->HasAura(SPELL_REFLECTION)) + { + if (CastSpell(SHIELD_BASH,pTarget)) {} // No GCD + else if (CastSpell(PUMMEL,pTarget)) { return; } + else if (m_bot->getRace() == (uint8) RACE_BLOODELF && pDist < 8 && CastSpell(R_ARCANE_TORRENT, pTarget)) { } //no gcd + } + } + #pragma endregion + + #pragma region Taunt / Threat + // if i am main tank, protect master by taunt + if(m_tank->GetGUID() == m_bot->GetGUID()) + { + // Taunt if needed (Only for master) + Unit *curAtt = GetAttackerOf(GetMaster()); + if (curAtt) + { + if (isUnderAttack(GetMaster(),2) && CastSpell(CHALLENGING_SHOUT, curAtt)) { return; } + if (CastSpell(TAUNT, curAtt,true,true)) { return; } + if (CastSpell(VIGILANCE, GetMaster())) { return; } + if (CastSpell(TAUNT, curAtt)) { return; } + if (CastSpell(MOCKING_BLOW, curAtt)) { return; } + } + // My target is not attacking me, taunt.. + if (pVictim && pVictim->GetGUID() != m_bot->GetGUID()) + { + if (CastSpell(VIGILANCE, pVictim)) { return; } + if (CastSpell(TAUNT, pTarget)) { return; } + if (CastSpell(MOCKING_BLOW, pTarget)) { return; } + } + } + + // If not in Defensive Stance slow down due to threat + if (pThreat > threatThreshold && ai->GetForm() != FORM_DEFENSIVESTANCE && m_tank->GetGUID() != m_bot->GetGUID() && !isUnderAttack() ) + { + if (m_tank->getVictim() && m_tank->getVictim()->GetGUID() != pTarget->GetGUID()) // I am attacking wrong target!! + { + m_bot->SetSelection(m_tank->getVictim()->GetGUID()); + return; + } + else { return; } //Warrior has no threat reducing spells, just slow down + } + #pragma endregion + + #pragma region Dps + + //Ranged Stuff (Openers) + if (CastSpell(CHARGE,pTarget)) { } //no GCD + else if (CastSpell(INTERCEPT,pTarget)) { } //no GCD + if (pDist > MELEE_RANGE && ai->GetForm() == FORM_DEFENSIVESTANCE && CastSpell(HEROIC_THROW,pTarget)) { return; } //High threat + if (pDist > MELEE_RANGE && CastSpell(SHATTERING_THROW,pTarget)) { return; } + + //Catch + if (pTarget->HasUnitMovementFlag(UNIT_FLAG_FLEEING)) + { + if (CastSpell(HAMSTRING,pTarget)) return; + if (CastSpell(PIERCING_HOWL,pTarget)) return; + } + + + //Dps up + if (ai->GetHealthPercent() > 90 && ai->GetRageAmount() < 20 && CastSpell(BLOODRAGE,m_bot)) { return; } + if (isUnderAttack() && CastSpell(RETALIATION,m_bot)) { return; } + if (ai->GetHealthPercent() > 90 && CastSpell(DEATH_WISH,m_bot)) { return; } + if (ai->GetHealthPercent() > 80 && CastSpell(RECKLESSNESS,m_bot)) { return; } + if (m_bot->getRace() == (uint8) RACE_TROLL && CastSpell(R_BERSERKING,m_bot)) {} //no GCD + if (m_bot->getRace() == (uint8) RACE_ORC && CastSpell(R_BLOOD_FURY,m_bot)) {} //no GCD + + //Tank only stuff + if ((ai->GetForm() == FORM_DEFENSIVESTANCE || ai->GetRageAmount() > 85) && CastSpell(THUNDER_CLAP)) { return; } //High threat + if ((ai->GetForm() == FORM_DEFENSIVESTANCE || ai->GetRageAmount() > 75) && CastSpell(HEROIC_STRIKE)) {} //nogcd high threat + + //Finishing Move / Timed moves + if (ai->GetHealthPercent(*pTarget) < 20 && CastSpell(EXECUTE,pTarget)) { return; } + if (CastSpell(VICTORY_RUSH,pTarget)) { return; } + + //AOE + if (CastSpell(SHOCKWAVE,pTarget)) { return; } + if ((isUnderAttack(m_tank,3) || m_tank->GetGUID() == m_bot->GetGUID()) && CastSpell(CLEAVE,pTarget)) {} //no GCD + if (isUnderAttack(m_tank,3) && CastSpell(SWEEPING_STRIKES,m_bot)) {} //no GCD + if (isUnderAttack(m_tank,4) && CastSpell(BLADESTORM,m_bot)) { return; } + if (isUnderAttack(m_tank,4) && CastSpell(WHIRLWIND,pTarget)) { return; } + + //Main dps + if (m_bot->HasAura(SLAMM) && CastSpell(SLAM,pTarget)) { return; } //instant slam only + if (CastSpell(REVENGE,pTarget)) { return; } //Def stance only + if (CastSpell(OVERPOWER,pTarget)) { return; } + if (CastSpell(SHIELD_SLAM,pTarget)) { return; } + if (CastSpell(BLOODTHIRST,pTarget)) { return; } + if (CastSpell(MORTAL_STRIKE,pTarget)) { return; } + + + //Support/Debuff + if (CastSpell(DEMORALIZING_SHOUT,pTarget)) { return; } + if (DEVASTATE) { if (CastSpell(DEVASTATE,pTarget,1,1)) { return; } } //High threat + else if (CastSpell(SUNDER_ARMOR)) { return; } //Only 1 - High threat + if (CastSpell(CONCUSSION_BLOW,pTarget)) { return; } + if (CastSpell(REND,pTarget)) { return; } + if (CastSpell(DISARM,pTarget)) { return; } + #pragma endregion + +} //end DoNextCombatManeuver + +void PlayerbotWarriorAI::DoNonCombatActions() +{ + PlayerbotAI *ai = GetAI(); + Player *m_bot = GetPlayerBot(); + if (!m_bot || !ai || m_bot->isDead()) { return; } + + //If Casting or Eating/Drinking return + if (m_bot->HasUnitState(UNIT_STAT_CASTING)) { return; } + if (m_bot->getStandState() == UNIT_STAND_STATE_SIT) { return; } + + //Buff Up + if (!m_bot->HasAura(BATTLE_SHOUT) && CastSpell(BATTLE_SHOUT,m_bot)) { return; } + if (!m_bot->HasAura(BATTLE_SHOUT,m_bot->GetGUID()) && !m_bot->HasAura(COMMANDING_SHOUT) && CastSpell(COMMANDING_SHOUT,m_bot)) { return; } + + if (GetMaster()->isAlive() && CastSpell(VIGILANCE, GetMaster())) { return; } + + //want to start off in battle stance so we can CHARGE + //if(ai->GetRageAmount() < 20 && ai->GetForm() != FORM_BATTLESTANCE && ChangeStance(BATTLE_STANCE)) { return; } + + //mana/hp check + if (m_bot->getRace() == (uint8) RACE_UNDEAD_PLAYER && ai->GetHealthPercent() < 75 && CastSpell(R_CANNIBALIZE,m_bot)) { return; } + if (ai->GetHealthPercent() < 75) { ai->Feast(); } +} //end DoNonCombatActions + +bool PlayerbotWarriorAI::ChangeStance(uint32 stance) +{ + if (stance == 0) return false; + if (CastSpell(stance, GetPlayerBot())) { return true; } + return false; +} + +void PlayerbotWarriorAI::Pull() +{ + if (!SHOOT) return; + + // check ammo + uint32 ammo_id = GetPlayerBot()->GetUInt32Value(PLAYER_AMMO_ID); + if (!ammo_id) { + GetPlayerBot()->Say("I'm out of ammo.", LANG_UNIVERSAL); + return; + } + + Unit* pTarget = ObjectAccessor::GetUnit(*GetMaster(), GetMaster()->GetSelection()); + if (pTarget==NULL || pTarget->IsFriendlyTo(GetMaster())) + { + GetPlayerBot()->Say("Invalid target", LANG_UNIVERSAL); + m_pulling = false; + GetAI()->Follow(*GetMaster()); + return; + } + + m_role = BOT_ROLE_DPS_RANGED; + m_pulling = true; + GetAI()->SetIgnoreUpdateTime(0); +} + +/* +void PlayerbotWarriorAI::BreakCC(const uint32 diff) +{ + if(pvpTrinket_cd < diff && GCD < diff) + { + if(m_creature->HasAuraType(SPELL_AURA_MOD_ROOT) || + m_creature->HasAuraType(SPELL_AURA_MOD_CONFUSE) || //dragons breath/blind/poly + m_creature->HasAura(8983) || //Druid bash rank 3 + m_creature->HasAura(27006) || //Druid pounce rank 4 + m_creature->HasAura(33786) || //Druid cyclone + m_creature->HasAura(22570, 1) || //Druid maim + m_creature->HasAura(10308) || //Paladin hammer of justice rank 4 + m_creature->HasAura(30414, 1) || //Warlock shadowfury rank 3 + m_creature->HasAura(6215) || //Warlock fear rank 3 **REMOVE THIS & IMPLEMENT IN BERSERKER RAGE** + m_creature->HasAura(17928) || //Warlock howlofterror rank 3 **REMOVE THIS & IMPLEMENT IN BERSERKER RAGE** + m_creature->HasAura(10890) || //Priest psychic scream rank 4 **REMOVE THIS & IMPLEMENT IN BERSERKER RAGE** + m_creature->HasAura(14902) || //Rogue Cheap shot + m_creature->HasAura(8643) || //Rogue Kidney shot Rank 2 + m_creature->HasAura(38764, 2) || //Rogue Gouge Rank 6 **REMOVE THIS & IMPLEMENT IN BERSERKER RAGE** + m_creature->HasAura(12809)) //Warrior concussion blow + { + doCast(m_creature, PVPTRINKET); //I think it would be better to instead of applying individual spells that apply the + pvpTrinket_cd = PVPTRINKET_CD; //effect SPELL_AURA_MOD_STUN, just add that type and start removing bad choices e.g. impact. + } + + if(m_creature->HasAura(11297) && m_creature->GetDistance(m_creature->getVictim()) < 10) + { //if warrior sapped and creature is less then 10 yards from war, cast pvp trinket and attempt to demo shout him out of stealth + doCast(m_creature, PVPTRINKET); + pvpTrinket_cd = PVPTRINKET_CD; + castDemoralizingShout = true; + } + } +} //BreakCC +*/ diff --git a/src/server/game/AI/Bots/PlayerbotWarriorAI.h b/src/server/game/AI/Bots/PlayerbotWarriorAI.h new file mode 100644 index 0000000..87ab4a8 --- /dev/null +++ b/src/server/game/AI/Bots/PlayerbotWarriorAI.h @@ -0,0 +1,57 @@ +#ifndef _PLAYERBOTWARRIORAI_H +#define _PLAYERBOTWARRIORAI_H + +#include "PlayerbotClassAI.h" + +class PlayerbotWarriorAI : PlayerbotClassAI +{ + public: + PlayerbotWarriorAI(Player *const master, Player *const bot, PlayerbotAI *const ai); + virtual ~PlayerbotWarriorAI(); + + virtual void LoadSpells(); + + //all combat actions go here + void DoNextCombatManeuver(Unit *); + + //all non combat actions go here, ex buffs, heals, rezzes + void DoNonCombatActions(); + + virtual void Pull(); + + private: + //Defensive Stance + uint32 SHIELD_WALL, REVENGE, SHIELD_BLOCK, DISARM, INTERVENE; + + //Berserker Stance + uint32 RECKLESSNESS, WHIRLWIND, PUMMEL, INTERCEPT; + + //Battle Stance + uint32 RETALIATION, CHARGE, OVERPOWER, SHATTERING_THROW; + + //Mixed Attacks //1 3 + uint32 REND, THUNDER_CLAP, SPELL_REFLECTION, SHIELD_BASH, EXECUTE, HAMSTRING, SWEEPING_STRIKES, VICTORY_RUSH; + + //General attacks + uint32 HEROIC_STRIKE, MORTAL_STRIKE, BLOODTHIRST, SHIELD_SLAM, SHOCKWAVE, SLAM, CLEAVE, BLADESTORM, HEROIC_THROW, CONCUSSION_BLOW, SUNDER_ARMOR, DEMORALIZING_SHOUT, INTIMIDATING_SHOUT, PIERCING_HOWL, DEVASTATE; + + //buffs + uint32 COMMANDING_SHOUT, BATTLE_SHOUT, VIGILANCE, BERSERKER_RAGE, ENRAGED_REGENERATION, BLOODRAGE, LAST_STAND, HEROIC_FURY, DEATH_WISH; + + //Stances + uint32 DEFENSIVE_STANCE, BATTLE_STANCE, BERSERKER_STANCE; + + //Taunts + uint32 TAUNT, CHALLENGING_SHOUT, MOCKING_BLOW; + + //Special + uint32 SLAMM; + + uint32 TALENT_ARMS, TALENT_FURY, TALENT_PROT; + + bool ChangeStance(uint32 stance); + + +}; + +#endif diff --git a/src/server/game/AI/CoreAI/PetAI.cpp b/src/server/game/AI/CoreAI/PetAI.cpp index ac142db..81d9bfe 100755 --- a/src/server/game/AI/CoreAI/PetAI.cpp +++ b/src/server/game/AI/CoreAI/PetAI.cpp @@ -46,6 +46,7 @@ PetAI::PetAI(Creature *c) : CreatureAI(c), i_tracker(TIME_INTERVAL_LOOK) void PetAI::EnterEvadeMode() { + if(me->GetIAmABot() && me->GetBotAI()) me->GetBotAI()->EnterEvadeMode(); } bool PetAI::_needToStop() @@ -83,6 +84,12 @@ void PetAI::UpdateAI(const uint32 diff) if (!me->isAlive()) return; + if(me->GetIAmABot()) + { + //don't do anything if eating or drinking, otherwise call UpdateAI + if(!me->HasAura(10256) && !me->HasAura(1137) && me->GetBotAI()) me->GetBotAI()->UpdateAI(diff); + } + Unit* owner = me->GetCharmerOrOwner(); if (m_updateAlliesTimer <= diff) @@ -295,6 +302,8 @@ void PetAI::KilledUnit(Unit *victim) void PetAI::AttackStart(Unit *target) { + if (me->GetCharmInfo() == NULL) return; + // Overrides Unit::AttackStart to correctly evaluate Pet states // Check all pet states to decide if we can attack this target diff --git a/src/server/game/CMakeLists.txt b/src/server/game/CMakeLists.txt index 323a3ac..be5240c 100644 --- a/src/server/game/CMakeLists.txt +++ b/src/server/game/CMakeLists.txt @@ -107,6 +107,7 @@ include_directories( ${CMAKE_SOURCE_DIR}/dep/zlib ${CMAKE_SOURCE_DIR}/src/server/collision ${CMAKE_SOURCE_DIR}/src/server/collision/Management + ${CMAKE_SOURCE_DIR}/src/server/game/AI/Bots ${CMAKE_SOURCE_DIR}/src/server/shared ${CMAKE_SOURCE_DIR}/src/server/shared/Configuration ${CMAKE_SOURCE_DIR}/src/server/shared/Cryptography @@ -127,6 +128,7 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/Achievements ${CMAKE_CURRENT_SOURCE_DIR}/Addons ${CMAKE_CURRENT_SOURCE_DIR}/AI + ${CMAKE_CURRENT_SOURCE_DIR}/AI/Bots ${CMAKE_CURRENT_SOURCE_DIR}/AI/CoreAI ${CMAKE_CURRENT_SOURCE_DIR}/AI/EventAI ${CMAKE_CURRENT_SOURCE_DIR}/AI/ScriptedAI diff --git a/src/server/game/Chat/Chat.cpp b/src/server/game/Chat/Chat.cpp index 30fe5de..668da5e 100755 --- a/src/server/game/Chat/Chat.cpp +++ b/src/server/game/Chat/Chat.cpp @@ -437,6 +437,9 @@ ChatCommand * ChatHandler::getCommandTable() { "bindsight", SEC_ADMINISTRATOR, false, OldHandler<&ChatHandler::HandleBindSightCommand>, "", NULL }, { "unbindsight", SEC_ADMINISTRATOR, false, OldHandler<&ChatHandler::HandleUnbindSightCommand>, "", NULL }, { "playall", SEC_GAMEMASTER, false, OldHandler<&ChatHandler::HandlePlayAllCommand>, "", NULL }, + // Playerbot mod + { "bot", SEC_ADMINISTRATOR, false, OldHandler<&ChatHandler::HandlePlayerbotCommand>, "", NULL }, + { "maintank", SEC_PLAYER, false, OldHandler<&ChatHandler::HandlePlayerbotMainTankCommand>, "", NULL }, { NULL, 0, false, NULL, "", NULL } }; diff --git a/src/server/game/Chat/Chat.h b/src/server/game/Chat/Chat.h index 486987d..cb2d923 100755 --- a/src/server/game/Chat/Chat.h +++ b/src/server/game/Chat/Chat.h @@ -172,6 +172,8 @@ class ChatHandler bool HandleUnPossessCommand(const char* args); bool HandleBindSightCommand(const char* args); bool HandleUnbindSightCommand(const char* args); + bool HandlePlayerbotCommand(const char *args); + bool HandlePlayerbotMainTankCommand(const char *args); bool HandleGuildCreateCommand(const char* args); bool HandleGuildInviteCommand(const char* args); @@ -346,6 +348,10 @@ class ChatHandler bool HandleQuestRemove(const char * args); bool HandleQuestComplete(const char * args);*/ + bool BotHandleQuestAdd(const char *args); + bool BotHandleQuestRemove(const char *args); + + //bool HandleSet32Bit(const char* args); bool HandleSaveAllCommand(const char* args); diff --git a/src/server/game/Chat/Commands/Level0.cpp b/src/server/game/Chat/Commands/Level0.cpp index 5deed67..f3714b1 100755 --- a/src/server/game/Chat/Commands/Level0.cpp +++ b/src/server/game/Chat/Commands/Level0.cpp @@ -23,11 +23,13 @@ #include "Opcodes.h" #include "Chat.h" #include "ObjectAccessor.h" +#include "ObjectMgr.h" #include "Language.h" #include "AccountMgr.h" #include "SystemConfig.h" #include "revision.h" #include "Util.h" +#include "Group.h" bool ChatHandler::HandleHelpCommand(const char* args) { @@ -144,6 +146,185 @@ bool ChatHandler::HandleSaveCommand(const char* /*args*/) return true; } +//Playerbot mod +bool ChatHandler::HandlePlayerbotCommand(const char *args) +{ + if(!m_session) + { + PSendSysMessage("You may only add bots from an active session"); + SetSentErrorMessage(true); + return false; + } + + if(!*args) + { + PSendSysMessage("usage: add PLAYERNAME or remove PLAYERNAME"); + SetSentErrorMessage(true); + return false; + } + + char *cmd = strtok ((char*)args, " "); + char *charname = strtok (NULL, " "); + if(!cmd || !charname) + { + PSendSysMessage("usage: add PLAYERNAME or remove PLAYERNAME"); + SetSentErrorMessage(true); + return false; + } + + std::string cmdStr = cmd; + std::string charnameStr = charname; + uint64 guid; + + if (charnameStr.compare("all") != 0) + { + if (!normalizePlayerName(charnameStr)) + return false; + + guid = sObjectMgr->GetPlayerGUIDByName(charnameStr.c_str()); + if (guid == 0 || (guid == m_session->GetPlayer()->GetGUID())) + { + SendSysMessage(LANG_PLAYER_NOT_FOUND); + SetSentErrorMessage(true); + return false; + } + + uint32 accountId = sObjectMgr->GetPlayerAccountIdByGUID(guid); + if (accountId != m_session->GetAccountId()) + { + PSendSysMessage("You may only add bots from the same account."); + SetSentErrorMessage(true); + return false; + } + } + + if (cmdStr.compare("add") == 0 || cmdStr.compare("login") == 0) + { + if (charnameStr.compare("all") == 0) + { + std::list<std::string> *names; + names=m_session->GetPlayer()->GetCharacterList(); + std::list<std::string>::iterator iter,next; + for (iter = names->begin(); iter != names->end(); iter++) + { + std::stringstream arg; + arg << "add " << (*iter).c_str(); + HandlePlayerbotCommand(arg.str().c_str()); + } + PSendSysMessage("Bots added successfully."); + return true; + } + else + { + if(m_session->GetPlayerBot(guid) != NULL) + { + PSendSysMessage("Bot already exists in world."); + SetSentErrorMessage(true); + return false; + } + m_session->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 = m_session->GetPlayerBotsBegin(); iter != m_session->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(arg.str().c_str()); + } + return true; + } + else + { + if (m_session->GetPlayerBot(guid) == NULL) + { + PSendSysMessage("Bot can not be removed because bot does not exst in world."); + SetSentErrorMessage(true); + return false; + } + m_session->LogoutPlayerBot(guid, true); + PSendSysMessage("Bot removed successfully."); + return true; + } + } + return true; +} + +bool ChatHandler::HandlePlayerbotMainTankCommand(const char *args) +{ + uint64 guid = 0; + uint64 pGuid = 0; + char *charname ; + Group *group = m_session->GetPlayer()->GetGroup(); + + if (group == NULL) { + PSendSysMessage("Must be in a group to set a main tank."); + SetSentErrorMessage(true); + return false; + } + + QueryResult result = CharacterDatabase.PQuery("SELECT memberGuid FROM group_member WHERE memberFlags='%u' AND guid = '%u'",MEMBER_FLAG_MAINTANK, group->GetGUID()); + if(result) + { + pGuid = MAKE_NEW_GUID(result->Fetch()->GetInt32(),0,HIGHGUID_PLAYER); + } + + // if no arguments are passed in, just say who the current main tank is + if(!*args) { + + if (pGuid>0) { + Player *pPlayer = sObjectMgr->GetPlayer(pGuid); + + if (pPlayer && pPlayer->isAlive()){ + PSendSysMessage("Main tank is %s.", pPlayer->GetName()); + return true; + } + } + + PSendSysMessage("Currently there is no main tank. "); + return true; + } else { + charname = strtok ((char*)args, " "); + std::string charnameStr = charname; + guid = sObjectMgr->GetPlayerGUIDByName(charnameStr.c_str()); + + // clear if same player + if (pGuid==guid) { + group->SetMainTank(guid, false); + PSendSysMessage("Main tank has been cleared. "); + return true; + } + + if (m_session->GetPlayer()->GetGroup()->IsMember(guid)) { + group->SetMainTank(pGuid,false); // clear old one + group->SetMainTank(guid, true); // set new one + Player *pPlayer = sObjectMgr->GetPlayer(guid); + if (pPlayer->IsInWorld()) + PSendSysMessage("Main tank is %s.", pPlayer->GetName()); + else + PSendSysMessage("Player is not online."); + + } else { + PSendSysMessage("Player is not in your group."); + } + + } + + + return true; +} + + /// Display the 'Message of the day' for the realm bool ChatHandler::HandleServerMotdCommand(const char* /*args*/) { diff --git a/src/server/game/Chat/Commands/Level1.cpp b/src/server/game/Chat/Commands/Level1.cpp index d5acf4b..f9b9eb2 100755 --- a/src/server/game/Chat/Commands/Level1.cpp +++ b/src/server/game/Chat/Commands/Level1.cpp @@ -270,9 +270,10 @@ bool ChatHandler::HandleSummonCommand(const char* args) target->UnbindInstance(pMap->GetInstanceId(), target->GetDungeonDifficulty(), true); // we are in instance, and can summon only player in our group with us as lead - if (!m_session->GetPlayer()->GetGroup() || !target->GetGroup() || + if(!target->IsPlayerbot() && + (!m_session->GetPlayer()->GetGroup() || !target->GetGroup() || (target->GetGroup()->GetLeaderGUID() != m_session->GetPlayer()->GetGUID()) || - (m_session->GetPlayer()->GetGroup()->GetLeaderGUID() != m_session->GetPlayer()->GetGUID())) + (m_session->GetPlayer()->GetGroup()->GetLeaderGUID() != m_session->GetPlayer()->GetGUID()))) // the last check is a bit excessive, but let it be, just in case { PSendSysMessage(LANG_CANNOT_SUMMON_TO_INST,nameLink.c_str()); @@ -320,7 +321,6 @@ bool ChatHandler::HandleSummonCommand(const char* args) m_session->GetPlayer()->GetZoneId(), target_guid); } - return true; } diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index 13e3234..5c0e729 100755 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -152,6 +152,9 @@ m_formation(NULL) for (uint8 i = 0; i < CREATURE_MAX_SPELLS; ++i) m_spells[i] = 0; + is_a_bot = false; + bot_AI = NULL; + m_CreatureSpellCooldowns.clear(); m_CreatureCategoryCooldowns.clear(); m_GlobalCooldown = 0; diff --git a/src/server/game/Entities/Creature/Creature.h b/src/server/game/Entities/Creature/Creature.h index 5794167..a607fe1 100755 --- a/src/server/game/Entities/Creature/Creature.h +++ b/src/server/game/Entities/Creature/Creature.h @@ -481,6 +481,18 @@ class Creature : public Unit, public GridObject<Creature> void AI_SendMoveToPacket(float x, float y, float z, uint32 time, uint32 MovementFlags, uint8 type); CreatureAI * AI() const { return (CreatureAI*)i_AI; } + // + //Bot commands + // + void SetBotAI(CreatureAI *newAI) + { + bot_AI = newAI; + } + CreatureAI *GetBotAI(){ return bot_AI; } + void SetIAmABot(bool bot){ is_a_bot = bot; } + bool GetIAmABot(){ return is_a_bot; } + + uint32 GetShieldBlockValue() const //dunno mob block value { return (getLevel()/2 + uint32(GetStat(STAT_STRENGTH)/20)); @@ -723,6 +735,9 @@ class Creature : public Unit, public GridObject<Creature> bool isVisibleForInState(WorldObject const* seer) const; private: + bool is_a_bot; + CreatureAI *bot_AI; + //WaypointMovementGenerator vars uint32 m_waypointID; uint32 m_path_id; diff --git a/src/server/game/Entities/Creature/GossipDef.h b/src/server/game/Entities/Creature/GossipDef.h index 2b6fe7d..e2e8f71 100755 --- a/src/server/game/Entities/Creature/GossipDef.h +++ b/src/server/game/Entities/Creature/GossipDef.h @@ -50,6 +50,7 @@ enum Gossip_Option GOSSIP_OPTION_UNLEARNPETTALENTS = 17, //UNIT_NPC_FLAG_TRAINER (16) (bonus option for GOSSIP_OPTION_TRAINER) GOSSIP_OPTION_LEARNDUALSPEC = 18, //UNIT_NPC_FLAG_TRAINER (16) (bonus option for GOSSIP_OPTION_TRAINER) GOSSIP_OPTION_OUTDOORPVP = 19, //added by code (option for outdoor pvp creatures) + GOSSIP_OPTION_BOT = 20, GOSSIP_OPTION_MAX }; diff --git a/src/server/game/Entities/Creature/TemporarySummon.cpp b/src/server/game/Entities/Creature/TemporarySummon.cpp index 1204a10..dc4a7f1 100755 --- a/src/server/game/Entities/Creature/TemporarySummon.cpp +++ b/src/server/game/Entities/Creature/TemporarySummon.cpp @@ -238,6 +238,14 @@ void TempSummon::UnSummon() if (owner && owner->GetTypeId() == TYPEID_UNIT && owner->ToCreature()->IsAIEnabled) owner->ToCreature()->AI()->SummonedCreatureDespawn(this); + if (owner && + owner->GetTypeId() == TYPEID_PLAYER && + ((Player*)owner)->HaveBot() && + ((Player*)owner)->GetBot()->GetGUID()==this->GetGUID() && + this->isDead()) { // dont unsummon corpse if a bot + return; + } + AddObjectToRemoveList(); } diff --git a/src/server/game/Entities/Object/Object.cpp b/src/server/game/Entities/Object/Object.cpp index ff08de3..4031e42 100755 --- a/src/server/game/Entities/Object/Object.cpp +++ b/src/server/game/Entities/Object/Object.cpp @@ -1568,9 +1568,16 @@ void WorldObject::GetRandomPoint(const Position &pos, float distance, float &ran void WorldObject::UpdateGroundPositionZ(float x, float y, float &z) const { - float new_z = GetBaseMap()->GetHeight(x,y,z,true); - if (new_z > INVALID_HEIGHT) - z = new_z+ 0.05f; // just to be sure that we are not a few pixel under the surface + float map_z = GetBaseMap()->GetHeight(x,y,z,false); + float vmap_z = GetBaseMap()->GetHeight(x,y,z,true); + + if(vmap_z > INVALID_HEIGHT) + z = vmap_z; // add or subtract say 0.05f, to adjust bot hover height + + if((map_z > vmap_z) && (map_z > z)) + z = map_z; + + Trinity::NormalizeMapCoord(z); } bool Position::IsPositionValid() const @@ -2106,7 +2113,19 @@ TempSummon *Map::SummonCreature(uint32 entry, const Position &pos, SummonPropert case UNIT_MASK_SUMMON: summon = new TempSummon (properties, summoner); break; case UNIT_MASK_GUARDIAN: summon = new Guardian (properties, summoner); break; case UNIT_MASK_PUPPET: summon = new Puppet (properties, summoner); break; - case UNIT_MASK_TOTEM: summon = new Totem (properties, summoner); break; + case UNIT_MASK_TOTEM: + { + if(summoner->isCharmed()) + { + //If the caster is charmed, assume it is a Bot. This might not always be + //the case, but oh well. This will allow the affects of the totem + //(ex healing, stoneskin, etc, to affect the bot owner insteadof the + //bot. Thats ok, the bot is expendable :-) + summon = new Totem (properties, summoner->GetCharmer()); break; + } else { + summon = new Totem (properties, summoner); break; + } + } case UNIT_MASK_MINION: summon = new Minion (properties, summoner); break; default: return NULL; } diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 45fcef3..1fe3d90 100755 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -69,6 +69,11 @@ #include "LFGMgr.h" #include "CharacterDatabaseCleaner.h" #include <cmath> +// Playerbot mod +#include "CreatureAIFactory.h" +#include "Config.h" +#include "PlayerbotAI.h" +#include "PlayerbotClassAI.h" #define ZONE_UPDATE_INTERVAL (1*IN_MILLISECONDS) @@ -389,8 +394,11 @@ UpdateMask Player::updateVisualBits; #ifdef _MSC_VER #pragma warning(disable:4355) #endif -Player::Player (WorldSession *session): Unit(), m_achievementMgr(this), m_reputationMgr(this) +Player::Player (WorldSession *session): Unit(), m_achievementMgr(this), m_reputationMgr(this), m_MaxPlayerbots(9), m_bot_died(false) { + // Playerbot mod + m_playerbotAI = NULL; + #ifdef _MSC_VER #pragma warning(default:4355) #endif @@ -608,6 +616,22 @@ Player::Player (WorldSession *session): Unit(), m_achievementMgr(this), m_reputa m_ChampioningFaction = 0; + ///////////////////// Bot System //////////////////////// + m_botTimer = 0; + m_bot = NULL; + m_bot_form = 0; + m_bot_race = 0; + m_bot_class = 0; + m_bot_must_wait_for_spell_1 = 0; + m_bot_must_wait_for_spell_2 = 0; + m_bot_must_wait_for_spell_3 = 0; + m_bot_must_be_created = false; + m_bot_must_die = false; + m_bot_entry_must_be_created = 0; + m_bot_class_must_be_created = 0; + m_bot_race_must_be_created = 0; + m_bot_entry = 0; + for (uint8 i = 0; i < MAX_POWERS; ++i) m_powerFraction[i] = 0; @@ -652,6 +676,13 @@ Player::~Player () delete m_declinedname; delete m_runes; + //Playerbot mod: remove AI if exists + if(m_playerbotAI != NULL) + { + delete m_playerbotAI; + m_playerbotAI = NULL; + } + sWorld->DecreasePlayerCount(); } @@ -665,6 +696,27 @@ void Player::CleanupsBeforeDelete(bool finalCleanup) if (m_transport) m_transport->RemovePassenger(this); + if(GetGroup() && HaveBot()) + { + Creature *m_bot = GetBot(); + Group *m_group = GetGroup(); + + //removing bot from group + if(m_group->IsMember(m_bot->GetGUID())) + { + //deleting bot from group + if(m_group->RemoveMember(m_bot->GetGUID(), GROUP_REMOVEMETHOD_DEFAULT) < 1) // 99 means I'm a bot + { + //no one left in group so deleting group + delete m_group; + sObjectMgr->RemoveGroup(m_group); + } + } + m_bot->SetCharmerGUID(0); + m_bot->RemoveFromWorld(); + RemoveBot(); + } + // clean up player-instance binds, may unload some instance saves for (uint8 i = 0; i < MAX_DIFFICULTY; ++i) for (BoundInstancesMap::iterator itr = m_boundInstances[i].begin(); itr != m_boundInstances[i].end(); ++itr) @@ -1463,6 +1515,22 @@ void Player::Update(uint32 p_time) RegenerateAll(); } + + //want to refresh bot even if we're dead so + //it can rez me + if(HaveBot() || GetBotMustBeCreated() || m_bot_died) + RefreshBot(p_time); + + + if(m_botTimer > 0) + { + if(p_time >= m_botTimer) + m_botTimer = 0; + else + m_botTimer -= p_time; + } + + if (m_deathState == JUST_DIED) KillPlayer(); @@ -1526,6 +1594,9 @@ 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: this was added as part of the Playerbot mod, + if(m_playerbotAI != NULL) m_playerbotAI->UpdateAI(p_time); } void Player::setDeathState(DeathState s) @@ -1782,7 +1853,8 @@ void Player::SendTeleportPacket(Position &oldPos) WorldPacket data2(MSG_MOVE_TELEPORT, 38); data2.append(GetPackGUID()); BuildMovementPacket(&data2); - Relocate(&oldPos); + // Relocate(&oldPos); + if (!this->IsPlayerbot()) Relocate(&oldPos); SendMessageToSet(&data2, false); } @@ -1832,6 +1904,16 @@ bool Player::TeleportTo(uint32 mapid, float x, float y, float z, float orientati return false; } + //Playerbot mod: if this user has bots, tell them to stop following master + //so they don't try to follow the master after the master teleports + for(PlayerBotMap::const_iterator itr = GetSession()->GetPlayerBotsBegin(); itr != GetSession()->GetPlayerBotsEnd(); ++itr) + { + Player *botPlayer = itr->second; + botPlayer->GetMotionMaster()->Clear(); + } + + + // preparing unsummon pet if lost (we must get pet before teleportation or will not find it later) Pet* pet = GetPet(); @@ -1881,6 +1963,33 @@ bool Player::TeleportTo(uint32 mapid, float x, float y, float z, float orientati } } + //HACK ELSE CLIENT CRASH WHEN PLAYER IS TELEPORTED + if(GetGroup() && HaveBot()) + { + //sLog->outError("Player::teleporting.. removing from group"); + + Group *m_group = GetGroup(); + Creature *m_bot = GetBot(); + + //removing bot from group + if(m_group->IsMember(m_bot->GetGUID())) + { + //deleting bot from group + if(m_group->RemoveMember(m_bot->GetGUID(), GROUP_REMOVEMETHOD_DEFAULT) < 1) // 99 means I'm a bot + { + //no one left in group so deleting group + delete m_group; + sObjectMgr->RemoveGroup(m_group); + } + } + m_bot->SetCharmerGUID(0); + //m_bot->RemoveFromWorld(); + RemoveBot(); + SetBotMustBeCreated(m_bot_entry, newbotrace, newbotclass); + } + + + // The player was ported to another map and loses the duel immediately. // We have to perform this check before the teleport, otherwise the // ObjectAccessor won't find the flag. @@ -2057,6 +2166,20 @@ bool Player::TeleportTo(uint32 mapid, float x, float y, float z, float orientati else return false; } + + //if I'm dead than need to remove bot manually + //This means I'm at the graveyard, but the bot or rest of the group + //finished off the mob + if(HaveBot() && m_bot->isAlive() && !isAlive()) + { + m_bot->SetCharmerGUID(0); + m_bot->RemoveFromWorld(); + RemoveBot(); + + //recreate it when you are alive again + SetBotMustBeCreated(m_bot_entry, newbotrace, newbotclass); + } //end if bot is alive and I'm not + return true; } @@ -2171,6 +2294,1094 @@ void Player::RemoveFromWorld() } } +Player *Player::GetObjPlayer(uint64 guid) +{ + return sObjectMgr->GetPlayer(guid); +} + +void Player::GetBotLevelInfo(uint32 race, uint32 class_,uint32 level, PlayerLevelInfo* info) const { + sObjectMgr->GetPlayerLevelInfo (race, class_, level, info); +} + +void Player::RefreshBot(uint32 diff) +{ + if(m_botTimer != 0) + return; + uint32 refreshDelay = 0; + + if(m_bot_died == true && !this->isInCombat() && isAlive()) + { + //recreate bot because it died + CreateBot(m_bot_entry, newbotrace, newbotclass); + m_bot = GetBot(); + m_bot_died = false; + } + + if (isInFlight()) + { + if (HaveBot()) + { + if (GetGroup()) + { + Group* m_group = GetGroup(); + Creature* m_bot = GetBot(); + + // removing bot from group + if (m_group->IsMember(m_bot->GetGUID())) + { + if (m_group->RemoveMember(m_bot->GetGUID(), GROUP_REMOVEMETHOD_DEFAULT) <= 1) + { + // deleting group since no one is left + delete m_group; + sObjectMgr->RemoveGroup(m_group); + } + } + } + m_bot->SetCharmerGUID(0); + m_bot->RemoveFromWorld(); + RemoveBot(); + + SetBotMustBeCreated(m_bot_entry, newbotrace, newbotclass); + } + return; + } + + if(HaveBot()) + { + Creature *m_bot = GetBot(); + + //BOT IS DEAD SUPPORT + if(GetGroup() && !m_bot->isAlive()) + { + Group *m_group = GetGroup(); + + //respawn if master is not in combat and is alive + if(!this->isInCombat() && isAlive()) + { + CreateBot(m_bot_entry, newbotrace, newbotclass); + m_bot = GetBot(); + } else { + m_bot_died = true; + } + } + //BOT MUST DIE SUPPORT + else if(GetBotMustDie()) + { + if(m_bot->isAlive()) { // dont want to delete from group if dead + if(GetGroup()) + { + Group *m_group = GetGroup(); + + //removing bot from group + if(m_group->IsMember(m_bot->GetGUID()) && m_group->GetMembersCount() >= 2) + { + //deleting bot from group + if(m_group->RemoveMember(m_bot->GetGUID(), GROUP_REMOVEMETHOD_DEFAULT) < 1) // 99 means I'm a bot + { + //no one left in group so deleting group + delete m_group; + sObjectMgr->RemoveGroup(m_group); + } + } + } + + m_bot->SetReactState(REACT_PASSIVE); + m_bot->CombatStop(); + + m_bot->DeleteThreatList(); + m_bot->SetCharmerGUID(0); + m_bot->RemoveFromWorld(); + RemoveBot(); + + CharacterDatabase.PExecute("DELETE FROM character_npcbot WHERE owner='%u'", this->GetGUIDLow()); + } + } + } + + if(HaveBot() && GetBot()->isAlive()) + { + +/*------------------------------------------*/ + Creature *m_bot = GetBot(); +/*------------------------------------------*/ + + if(isInCombat()) + { + if(m_bot->getVictim() > 0) + { + if(m_bot->getVictim()->IsPolymorphed()) + { + //m_bot->Say("SHEEP!", LANG_UNIVERSAL, NULL); + m_bot->SetReactState(REACT_PASSIVE); //Don't attack sheep + m_bot->CombatStop(); + return; //for now return because can't do anything, need to continue timer though somehow + } //end if polymorpth + } //end if getVictim > 0 + else if(getVictim() > 0 && !getVictim()->IsPolymorphed()) + { + if(GetBotClass() == CLASS_PRIEST || GetBotClass() == CLASS_MAGE || GetBotClass() == CLASS_WARLOCK) + m_bot->SetReactState(REACT_PASSIVE); //casters shouldn't melee + else + m_bot->SetReactState(REACT_DEFENSIVE); + } + + //if I'm in combat but the bot is not, put bot in combat + //this fixes the case where group member gets initial aggro + //otherwise the bot wont fight unless I get hit. + if(!m_bot->isInCombat() && m_bot->GetReactState() != REACT_PASSIVE) + { + if(getVictim() > 0 && m_bot->GetDistance(getVictim())<30) { + m_bot->AI()->AttackStart(getVictim()); + SetBotCommandState(COMMAND_ATTACK); + //m_bot->AI()->BotAttackStart(getVictim()); + } + } else { +//sLog->outError ("putting priest to follow master"); + // m_bot->GetMotionMaster()->MoveFollow(this, urand(5, 10), PET_FOLLOW_ANGLE); + } + } //end if isInCombat + + + //TELEPORT AND CHANGE ZONE/AREA SUPPORT + if(!isInFlight()) + { + bool tp = false; + if(GetMapId() != m_bot->GetMapId()) + tp = true; + else if(GetZoneId() != m_bot->GetZoneId()) + tp = true; + //If bot and player not in the same area but around 25 + else if((GetAreaId() != m_bot->GetAreaId()) && + ((abs(m_bot->GetPositionX() - GetPositionX()) > 25) || + (abs(m_bot->GetPositionY() - GetPositionY()) > 25)) || + (abs(m_bot->GetPositionZ() - GetPositionZ()) > 25)) + { + tp = true; + } + + //If player change of zone/area + if(tp) + { + //HACK ELSE BOT IS DUPLICATED AND CLIENT CRASH + if(GetGroup() && GetBot()) + { + Group *m_group = GetGroup(); + Creature *m_bot = GetBot(); + + //removing bot from group + if(m_group->IsMember(m_bot->GetGUID())) + { + //deleting bot from group + if(m_group->RemoveMember(m_bot->GetGUID(), GROUP_REMOVEMETHOD_DEFAULT) < 1) // 99 means I'm a bot + { + //no one left in group so deleting group + delete m_group; + sObjectMgr->RemoveGroup(m_group); + } + } + } + + //SAVE INFO + uint32 entry = m_bot->GetEntry(); + + //DESPAWN + m_bot->SetCharmerGUID(0); + m_bot->RemoveFromWorld(); + RemoveBot(); + + //RESPAWN + CreateBot(entry, newbotrace, newbotclass); + m_bot = GetBot(); + return; + } + } + + //FLYING MOUNT SUPPORT + if((IsMounted() && HasAuraType(SPELL_AURA_FLY)) || canFly() || IsFlying() || isInFlight()) + { + if(m_bot->GetMountID() != 17759 + && m_bot->GetMountID() != 17703 + && m_bot->GetMountID() != 17718 + && m_bot->GetMountID() != 17720 + && m_bot->GetMountID() != 17721 + && m_bot->GetMountID() != 17719) + { + int m_mount = 0; + int m_rand = rand()%100; + if(m_rand < 33) m_mount = 1; + else if(m_rand > 64) m_mount = 2; + else m_mount = 3; + + if((GetBotClass() == CLASS_DRUID) && (m_bot->getLevel() >= 70)) m_mount = 0; + if((GetBotClass() == CLASS_DRUID) && (m_bot->getLevel() >= 70)) m_mount = 0; + + if(GetBotRace() == RACE_HUMAN || GetBotRace() == RACE_DWARF || GetBotRace() == RACE_NIGHTELF || GetBotRace() == RACE_GNOME || GetBotRace() == RACE_DRAENEI) + { + switch(m_mount) + { + case 1: m_bot->Mount(17759); break; + case 2: m_bot->Mount(17703); break; + case 3: m_bot->Mount(17718); break; + default: break; + } + } + else if(GetBotRace() == RACE_ORC || GetBotRace() == RACE_UNDEAD_PLAYER || GetBotRace() == RACE_TAUREN || GetBotRace() == RACE_TROLL || GetBotRace() == RACE_BLOODELF) + { + switch(m_mount) + { + case 1: m_bot->Mount(17720); break; + case 2: m_bot->Mount(17721); break; + case 3: m_bot->Mount(17719); break; + default: break; + } + } + //m_bot->SetSpeed(MOVE_RUN, GetSpeed(MOVE_WALK) - 0.1f, true); + } + if(isInFlight()) + { + m_bot->HasUnitMovementFlag(MOVEMENTFLAG_FLYING); + m_bot->SetSpeed(MOVE_RUN, GetSpeed(MOVE_FLIGHT) + 0.1f, true); + } + else + { + m_bot->HasUnitMovementFlag(MOVEMENTFLAG_NONE); + m_bot->SetSpeed(MOVE_RUN, GetSpeed(MOVE_RUN) + 0.1f, true); + } + } + //MOUNT SUPPORT + else if(IsMounted() && !m_bot->IsMounted()) + { + int m_mount = 0; + int m_rand = rand()%100; + if(m_rand < 33) m_mount = 1; + else if(m_rand > 64) m_mount = 2; + else m_mount = 3; + + if((GetBotClass() == CLASS_DRUID) && (m_bot->getLevel() < 60)) m_mount = 0; + if((GetBotClass() == CLASS_DRUID) && (m_bot->getLevel() < 60)) m_mount = 0; + + switch(GetBotRace()) + { + case RACE_HUMAN: + { + if(getLevel() < 60) + { + switch(m_mount) + { + case 1: m_bot->Mount(2409); break; + case 2: m_bot->Mount(2404); break; + case 3: m_bot->Mount(2405); break; + default: break; + } + } + else + { + switch(m_mount) + { + case 1: m_bot->Mount(14338); break; + case 2: m_bot->Mount(14583); break; + case 3: m_bot->Mount(14582); break; + default: break; + } + } + break; + } + case RACE_ORC: + { + if(getLevel() < 60) + { + switch(m_mount) + { + case 1: m_bot->Mount(247); break; + case 2: m_bot->Mount(2327); break; + case 3: m_bot->Mount(2328); break; + default: break; + } + } + else + { + switch(m_mount) + { + case 1: m_bot->Mount(14575); break; + case 2: m_bot->Mount(14574); break; + case 3: m_bot->Mount(14573); break; + default: break; + } + } + break; + } + case RACE_DWARF: + { + if(getLevel() < 60) + { + switch(m_mount) + { + case 1: m_bot->Mount(2785); break; + case 2: m_bot->Mount(2786); break; + case 3: m_bot->Mount(2736); break; + default: break; + } + } + else + { + switch(m_mount) + { + case 1: m_bot->Mount(14347); break; + case 2: m_bot->Mount(14576); break; + case 3: m_bot->Mount(14346); break; + default: break; + } + } + break; + } + case RACE_NIGHTELF: + { + if(getLevel() < 60) + { + switch(m_mount) + { + case 1: m_bot->Mount(6080); break; + case 2: m_bot->Mount(6448); break; + case 3: m_bot->Mount(6444); break; + default: break; + } + } + else + { + switch(m_mount) + { + case 1: m_bot->Mount(14632); break; + case 2: m_bot->Mount(14332); break; + case 3: m_bot->Mount(14331); break; + default: break; + } + } + break; + } + case RACE_UNDEAD_PLAYER: + { + if(getLevel() < 60) + { + switch(m_mount) + { + case 1: m_bot->Mount(10670); break; + case 2: m_bot->Mount(10671); break; + case 3: m_bot->Mount(10672); break; + default: break; + } + } + else + { + switch(m_mount) + { + case 1: m_bot->Mount(10721); break; + case 2: m_bot->Mount(10720); break; + case 3: m_bot->Mount(10719); break; + default: break; + } + } + break; + } + case RACE_TAUREN: + { + if(getLevel() < 60) + { + switch(m_mount) + { + case 1: m_bot->Mount(12246); break; + case 2: m_bot->Mount(11641); break; + case 3: m_bot->Mount(12245); break; + default: break; + } + } + else + { + switch(m_mount) + { + case 1: m_bot->Mount(14579); break; + case 2: m_bot->Mount(14349); break; + case 3: m_bot->Mount(14578); break; + default: break; + } + } + break; + } + case RACE_GNOME: + { + if(getLevel() < 60) + { + switch(m_mount) + { + case 1: m_bot->Mount(9473); break; + case 2: m_bot->Mount(10661); break; + case 3: m_bot->Mount(6569); break; + default: break; + } + } + else + { + switch(m_mount) + { + case 1: m_bot->Mount(14376); break; + case 2: m_bot->Mount(14374); break; + case 3: m_bot->Mount(14377); break; + default: break; + } + } + break; + } + case RACE_TROLL: + { + if(getLevel() < 60) + { + switch(m_mount) + { + case 1: m_bot->Mount(6472); break; + case 2: m_bot->Mount(4806); break; + case 3: m_bot->Mount(6473); break; + default: break; + } + } + else + { + switch(m_mount) + { + case 1: m_bot->Mount(14344); break; + case 2: m_bot->Mount(14339); break; + case 3: m_bot->Mount(14342); break; + default: break; + } + } + break; + } + case RACE_BLOODELF: + { + if(getLevel() < 60) + { + switch(m_mount) + { + case 1: m_bot->Mount(18696); break; + case 2: m_bot->Mount(19480); break; + case 3: m_bot->Mount(19478); break; + default: break; + } + } + else + { + switch(m_mount) + { + case 1: m_bot->Mount(19484); break; + case 2: m_bot->Mount(18697); break; + case 3: m_bot->Mount(19482); break; + default: break; + } + } + break; + } + case RACE_DRAENEI: + { + if(getLevel() < 60) + { + switch(m_mount) + { + case 1: m_bot->Mount(17063); break; + case 2: m_bot->Mount(19870); break; + case 3: m_bot->Mount(19869); break; + default: break; + } + } + else + { + switch(m_mount) + { + case 1: m_bot->Mount(19871); break; + case 2: m_bot->Mount(19872); break; + case 3: m_bot->Mount(19873); break; + default: break; + } + } + break; + } + } + m_bot->SetSpeed(MOVE_RUN, GetSpeed(MOVE_WALK) - 0.1f, true); + } + else if(!IsMounted() + && m_bot->IsMounted()) + { + m_bot->Unmount(); + CreatureInfo const *cinfo = sObjectMgr->GetCreatureTemplate(m_bot->GetEntry()); + m_bot->SetSpeed(MOVE_RUN, cinfo->speed_run, true); + m_bot->HasUnitMovementFlag(MOVEMENTFLAG_NONE); + } + + //if low on mana, take a drink (only check for classes with custom AI) + //because they are the only ones currently using mana + if(GetBotClass() == CLASS_SHAMAN || GetBotClass() == CLASS_DRUID || + GetBotClass() == CLASS_PRIEST || GetBotClass() == CLASS_MAGE || GetBotClass() == CLASS_HUNTER || + GetBotClass() == CLASS_WARLOCK || GetBotClass() == CLASS_PALADIN) + { + if(m_bot->GetPower(POWER_MANA)*100/m_bot->GetMaxPower(POWER_MANA) < 80 && + !m_bot->HasAura(1137) && + GetBotMustWaitForSpell3() <= 0 && + !m_bot->isInCombat()) + { + m_bot->CastSpell(m_bot, 1137, true); + SetBotMustWaitForSpell3(1000); + m_bot->SetStandState(1); + m_botTimer = 5000; //set longer delay so it wont stand up right away + return; + } + } + + //if drinking, have to fake mana regen because charmed NPCs + //do not regen mana + if(m_bot->HasAura(1137)) + { + uint32 addvalue = 0; + uint32 maxValue = m_bot->GetMaxPower(POWER_MANA); + uint32 curValue = m_bot->GetPower(POWER_MANA); + + if(curValue <= maxValue) + { + addvalue = maxValue/20; + m_bot->ModifyPower(POWER_MANA, addvalue); + //return; + } + } + + if(m_bot->HasAura(1137) && m_bot->GetPower(POWER_MANA) >= m_bot->GetMaxPower(POWER_MANA)) + m_bot->RemoveAurasDueToSpell(1137); + + //eat + if(m_bot->GetHealth()*100 / m_bot->GetMaxHealth() < 80 && + !m_bot->HasAura(10256) && + GetBotMustWaitForSpell3() <= 0 && + !m_bot->isInCombat()) + { + SetBotMustWaitForSpell3(1000); + m_bot->CastSpell(m_bot, 10256, true); + m_bot->SetStandState(1); + m_botTimer = 5000; //set longer delay so it wont stand up right away + return; + } + + //if eating, have to fake regen because charmed NPCs + //do not regen + if(m_bot->HasAura(10256)) + { + uint32 addvalue = 0; + uint32 maxValue = m_bot->GetMaxHealth(); + uint32 curValue = m_bot->GetHealth(); + + if(curValue <= maxValue) + { + addvalue = maxValue/20; + m_bot->SetHealth(curValue + addvalue); + //return; + } + } + + if(m_bot->GetHealth() == m_bot->GetMaxHealth() && m_bot->HasAura(10256)) + m_bot->RemoveAurasDueToSpell(10256); + + //if bot stands up for some reason, ie goes into combat, + //remove the food and drink affect + if(m_bot->isInCombat() /*|| !m_bot->IsStopped()*/ || m_bot->IsStandState()) + { + if(m_bot->HasAura(10256)) m_bot->RemoveAurasDueToSpell(10256); + if(m_bot->HasAura(1137)) m_bot->RemoveAurasDueToSpell(1137); + } + + //if done drinking and eating, stand up + if(!isInCombat() && m_bot->IsSitState() && !m_bot->HasAura(1137) && !m_bot->HasAura(10256)) + m_bot->SetStandState(UNIT_STAND_STATE_STAND); + + //SPELL AI CUSTOM SUPPORT + switch(GetBotClass()) + { + case CLASS_DRUID: //DRUID FORM SUPPORT + { + if(!m_bot->isAlive()) break; + + CreatureInfo const *cinfo = sObjectMgr->GetCreatureTemplate(m_bot->GetEntry()); + + uint32 m_old_bot_form = m_bot->GetDisplayId(); + if((IsMounted() && HasAuraType(SPELL_AURA_FLY)) || canFly() || IsFlying() || isInFlight()) + { + //flight form + if((((IsMounted()) && (m_bot->getLevel() >= 70)) || (m_bot->GetShapeshiftForm() == FORM_FLIGHT) || (m_bot->GetShapeshiftForm() == FORM_FLIGHT_EPIC))) + { + if((GetBotRace() == RACE_NIGHTELF) && (m_bot->GetDisplayId() != 21243)) + m_bot->SetDisplayId(21243); + if((GetBotRace() == RACE_TAUREN) && (m_bot->GetDisplayId() != 21244)) + m_bot->SetDisplayId(21244); + m_bot->Unmount(); + SetBotForm(m_bot->GetDisplayId()); + SetBotMustWaitForSpell1(3000); + } + } + else if((GetBotMustWaitForSpell1() == 0) && (m_bot->IsInWater()) && (!isInFlight())) + { + //Removed this because it turns into a seal in Booty Bay + //seal form +/* + if((GetBotRace() == RACE_NIGHTELF) && (m_bot->GetDisplayId() != 2428)) + m_bot->SetDisplayId(2428); + if((GetBotRace() == RACE_TAUREN) && (m_bot->GetDisplayId() != 2428)) + m_bot->SetDisplayId(2428); + SetBotForm(m_bot->GetDisplayId()); + SetBotMustWaitForSpell1(3000); +*/ + } + else if((GetBotMustWaitForSpell1() == 0) && (m_bot->isInCombat()) && (!m_bot->isInFlight())) + { + //combat form is now handled in AI + } + else if((GetBotMustWaitForSpell1() == 0) && (((IsMounted()) && (m_bot->getLevel() < 60)) || (m_bot->GetShapeshiftForm() == FORM_TRAVEL)) && (!m_bot->isInFlight())) + { + //travel form + if((GetBotRace() == RACE_NIGHTELF) && (m_bot->GetDisplayId() != 632)) + m_bot->SetDisplayId(632); + if((GetBotRace() == RACE_TAUREN) && (m_bot->GetDisplayId() != 632)) + m_bot->SetDisplayId(632); + SetBotForm(m_bot->GetDisplayId()); + SetBotMustWaitForSpell1(3000); + } + else if((GetBotMustWaitForSpell1() == 0) && (m_bot->GetShapeshiftForm() == FORM_CAT) && (!m_bot->isInFlight())) + { + //cat form + if((GetBotRace() == RACE_NIGHTELF) && (m_bot->GetDisplayId() != 892)) + m_bot->SetDisplayId(892); + if((GetBotRace() == RACE_TAUREN) && (m_bot->GetDisplayId() != 8571)) + m_bot->SetDisplayId(8571); + SetBotForm(m_bot->GetDisplayId()); + //SetBotMustWaitForSpell1(3000); + } + else if((GetBotMustWaitForSpell1() == 0) && (m_bot->GetShapeshiftForm() == FORM_BEAR) && (!m_bot->isInFlight())) + { + //bear form + if((GetBotRace() == RACE_NIGHTELF) && (m_bot->GetDisplayId() != 2281)) m_bot->SetDisplayId(2281); + if((GetBotRace() == RACE_TAUREN) && (m_bot->GetDisplayId() != 2289)) m_bot->SetDisplayId(2289); + SetBotForm(m_bot->GetDisplayId()); + //SetBotMustWaitForSpell1(3000); + } + + if(m_old_bot_form != GetBotForm()) + { + //change stats based on forms + m_bot->SetSpeed(MOVE_SWIM, cinfo->speed_run, true); m_bot->SetSpeed(MOVE_RUN, cinfo->speed_run, true); + if(GetBotForm() == 2428) + m_bot->SetSpeed(MOVE_SWIM, cinfo->speed_run * 1.50f, true); + else if(GetBotForm() == 632) //travel form + { + //m_bot->SetSpeed(MOVE_RUN, GetSpeed(MOVE_WALK) , true); + m_bot->SetSpeed(MOVE_RUN, GetSpeed(MOVE_RUN) - 0.1f, true); + } + + } + + //RESET FORMS + if((!m_bot->isInFlight()) && (!m_bot->IsInWater()) && (!m_bot->isInCombat()) && (!IsMounted()) && (m_bot->GetShapeshiftForm() != FORM_TRAVEL) && (m_bot->GetShapeshiftForm() != FORM_CAT)) + { + //don't reset if bear or cat because it costs too much mana + if(m_bot->HasAura(9634) || + m_bot->HasAura(768) || + m_bot->HasAura(16591)) + return; + + if((GetBotRace() == 4) && (m_bot->GetDisplayId() != cinfo->Modelid1)) + m_bot->SetDisplayId(cinfo->Modelid1); + if((GetBotRace() == 6) && (m_bot->GetDisplayId() != cinfo->Modelid3)) + m_bot->SetDisplayId(cinfo->Modelid3); + m_bot->SetSpeed(MOVE_SWIM, cinfo->speed_run, true); + m_bot->SetSpeed(MOVE_RUN, cinfo->speed_run, true); + SetBotForm(m_bot->GetDisplayId()); + } + + //SPECIAL SPELL FOR DRUID + SetBotForm(m_bot->GetDisplayId()); + break; + } + + }//END SWITCH + + if (!m_bot->isInCombat() && + m_bot->GetCharmInfo()->GetCommandState() != COMMAND_STAY && + GetDistance(m_bot) > 60 && + !IsBeingTeleported()) + { + m_bot->Relocate(this); + SetBotCommandState(COMMAND_FOLLOW); + } + + if (getStandState() == UNIT_STAND_STATE_SIT) + { + m_bot->SetStandState(UNIT_STAND_STATE_SIT); + } + else + { + m_bot->SetStandState(UNIT_STAND_STATE_STAND); + } + } + else if(GetBotMustBeCreated() && isAlive()) + { + CreateBot(m_bot_entry_must_be_created, m_bot_race_must_be_created, m_bot_class_must_be_created); + } + + if(GetBotMustWaitForSpell1() > 0) + SetBotMustWaitForSpell1(GetBotMustWaitForSpell1() - refreshDelay > 0 ? refreshDelay : 10); + + if(GetBotMustWaitForSpell2() > 0) + SetBotMustWaitForSpell2(GetBotMustWaitForSpell2() - refreshDelay > 0 ? refreshDelay : 10); + + if(GetBotMustWaitForSpell3() > 0) + SetBotMustWaitForSpell3(GetBotMustWaitForSpell3() - refreshDelay > 0 ? refreshDelay : 10); + + m_botTimer = refreshDelay; + +} //end Player::RefreshBot + +void Player::RemoveBot() +{ + if(m_botHasPet && m_botsPet != NULL) + { + m_botsPet->SetCharmerGUID(0); + m_botsPet->CombatStop(); + m_botsPet->DeleteFromDB(); + m_botsPet->CleanupsBeforeDelete(); + m_botsPet->AddObjectToRemoveList(); + } + m_botsPet = NULL; + m_botHasPet = false; + + m_bot->CombatStop(); + m_bot->DeleteFromDB(); + m_bot->CleanupsBeforeDelete(); + m_bot->SetIAmABot(false); //this HAS to come after CleanupsBeforeDelete + m_bot->AddObjectToRemoveList(); + + m_bot = NULL; m_bot_class = 0; m_bot_race = 0; m_bot_form = 0; + m_bot_must_wait_for_spell_1 = 0; m_bot_must_wait_for_spell_2 = 0; + m_bot_must_wait_for_spell_3 = 0; m_bot_must_be_created = false; + + if(m_bot_ai) + { + //delete m_bot_ai; + m_bot_ai = NULL; + } + m_bot_must_die = false; + + +} //end RemoveBot + + +void Player::CreateBot(uint32 botentry, uint8 botrace, uint8 botclass) +{ + if(IsBeingTeleported()) return; //being teleported so don't create bot yet + + if (this->isInCombat() || this->isDead()) return; // don't create while fighting or dead + + if (m_bot != NULL) { + m_bot->SetHealth(m_bot->GetMaxHealth()); + m_bot->setDeathState(ALIVE); + return; + } + + Creature *newbot = SummonCreature(botentry, GetPositionX()-2, GetPositionY()-2, GetPositionZ(), GetOrientation(), TEMPSUMMON_MANUAL_DESPAWN, 0); + + SetBot(newbot); + SetBotClass(botclass); + SetBotRace(botrace); + + newbot->SetIAmABot(true); + newbot->SetCharmerGUID(GetGUID()); + newbot->CombatStop(); + newbot->DeleteThreatList(); + newbot->AIM_Initialize(); + + CharmInfo *charmInfonewbot = newbot->InitCharmInfo(); + newbot->setFaction(getFaction()); + if(botclass == CLASS_PRIEST || botclass == CLASS_MAGE || botclass == CLASS_WARLOCK) + newbot->SetReactState(REACT_PASSIVE); //casters shouldn't melee + else + newbot->SetReactState(REACT_DEFENSIVE); + //charmInfonewbot->SetCommandState(COMMAND_FOLLOW); + SetBotCommandState(COMMAND_FOLLOW);; + + //sLog->outError("Player::CreateBot: %s", newbot->GetName()); + //sLog->outError("\thp = %d", newbot->GetMaxHealth()); + //sLog->outError("\tmana = %d", newbot->GetMaxPower(POWER_MANA)); + //sLog->outError("\tlevel = %d", newbot->getLevel()); + //sLog->outError("\tentry = %d", botentry); + //sLog->outError("\trace = %d", botrace); + //sLog->outError("\tclass = %d", botclass); + + //If the player have a group, it's possible to add the bot. + if(GetGroup()) + { + Group *m_group = GetGroup(); + if(!m_group->IsFull()) m_group->AddMember(newbot->GetGUID(), newbot->GetName()); + else { + //group is full so can't add bot + //(m_bot->AI())->MonsterSay("Group is full.", LANG_UNIVERSAL, NULL); + SetBotMustDie(); + } + } + else + { + Group *m_group = new Group; + if(!m_group->Create(GetGUID(), GetName())) + { + delete m_group; + return; + } + sObjectMgr->AddGroup(m_group); + + if(!m_group->IsFull()) m_group->AddMember(newbot->GetGUID(), newbot->GetName()); + } + m_bot_must_die = false; + + //if not in group, go away + Group::MemberSlotList const &a =GetGroup()->GetMemberSlots(); + if(GetGroup() == NULL || a.empty()) + { + //m_creature->MonsterSay("Not in group.", LANG_UNIVERSAL, NULL); + SetBotMustDie(); + return; + } + + m_bot_entry = m_bot->GetEntry(); + newbotclass = GetBotClass(); + newbotrace = GetBotRace(); + + //Set the race. This is so when the druid shapeshifts + //it wont get an error about not finding correct race + GetBot()->SetByteValue(UNIT_FIELD_BYTES_0, 0, newbotrace); + + if(GetBotAI()) GetBotAI()->JustRespawned(); + + SetBotMustWaitForSpell1(0); + SetBotMustWaitForSpell2(0); + SetBotMustWaitForSpell3(0); //eating and drinking + + // m_bot->SelectLevel(sObjectMgr->GetCreatureTemplate(m_bot->GetEntry())); + // m_bot->SelectLevel(sObjectMgr->GetCreatureTemplate(m_bot->GetEntry())); + CreatureBaseStats const* stats = sObjectMgr->GetCreatureBaseStats(getLevel(), newbot->GetCreatureInfo()->unit_class); + newbot->SetArmor(stats->BaseArmor); + newbot->SetMaxHealth((getLevel() * newbot->GetMaxHealth()) / newbot->getLevel()); //set health based of level + newbot->SetMaxHealth(newbot->GetMaxHealth() - newbot->GetMaxHealth() * urand(1, 25) / 100); // insert some randomness + newbot->SetHealth(newbot->GetMaxHealth()); + + if(botclass != CLASS_WARRIOR && botclass != CLASS_ROGUE && botclass != CLASS_DEATH_KNIGHT) + { + newbot->SetPower(POWER_MANA, m_bot->GetMaxPower(POWER_MANA)); + newbot->SetMaxPower(POWER_MANA, (getLevel() * m_bot->GetMaxPower(POWER_MANA)) / m_bot->getLevel()); //set mana based of level + newbot->SetMaxPower(POWER_MANA, m_bot->GetMaxPower(POWER_MANA) - newbot->GetMaxPower(POWER_MANA) * urand(1, 20) / 100); // insert some randomness + } + + //newbot->SetFaction(getFaction()); + newbot->setFaction(getFaction()); + + //remove any entries, just in case it didnt get cleaned up earlier + CharacterDatabase.PExecute("DELETE FROM character_npcbot WHERE owner = '%u'", this->GetGUIDLow()); + + //add the new bot + CharacterDatabase.PExecute("INSERT INTO character_npcbot (owner,entry,race,class) VALUES ('%u','%u','%u','%u')", this->GetGUIDLow(), botentry, botrace, botclass); + + m_botsPet = NULL; + m_botHasPet = false; + + newbot->SetLevel(getLevel()); + newbot->AI()->Reset(); + + m_SaveOrgLocation = sConfig->GetIntDefault("Bot.SaveOrgLocation", 0); + +} //end Player::CreateBot + +Creature *Player::GetBotsPet(uint32 entry) +{ + Creature *pet = this->SummonCreature(entry, GetPositionX() + 10, GetPositionY() + 10, GetPositionZ(), 0, TEMPSUMMON_DEAD_DESPAWN, 0); + + QueryResult result; + + result = WorldDatabase.PQuery("SELECT hp, mana, armor, str, agi FROM pet_levelstats WHERE creature_entry = 1860 AND level=%u", this->getLevel()); + + if(result) + { + Field *fields = result->Fetch(); + uint32 hp = fields[0].GetUInt32(); + uint32 mana = fields[1].GetUInt32(); + uint32 armor = fields[2].GetUInt32(); + uint32 str = fields[3].GetUInt32(); + uint32 agi = fields[4].GetUInt32(); + //sLog->outError("hp = %u", hp); + //sLog->outError("mana = %u", mana); + //sLog->outError("str = %u", str); + //sLog->outError("agi = %u", agi); + + pet->SetMaxHealth(hp); + pet->SetMaxPower(POWER_MANA, mana); + pet->SetArmor(armor); + pet->SetStat(STAT_STRENGTH, str); + pet->SetStat(STAT_AGILITY, agi); + + //delete result; + } + pet->SetLevel(getLevel()); + + m_botHasPet = true; + m_botsPet = pet; + + return pet; +} //end GetBotsPet + +void Player::SetBotsPetDied() +{ + if(m_botHasPet && m_botsPet != NULL) + { + m_botsPet->SetCharmerGUID(0); + m_botsPet->CombatStop(); + m_botsPet->DeleteFromDB(); + m_botsPet->CleanupsBeforeDelete(); + m_botsPet->AddObjectToRemoveList(); + } + + m_botsPet = NULL; + m_botHasPet = false; +} + +// +//This is called from script_bot_giver.cpp +// +uint8 Player::GetMaxPlayerBot() +{ + //load config variables + if(m_MaxPlayerbots > 9) m_MaxPlayerbots = sConfig->GetIntDefault("Bot.MaxPlayerbots", 9); + + return m_MaxPlayerbots; + +} + +// +//This is called from script_bot_giver.cpp +// +void Player::CreatePlayerBot(std::string name) +{ + uint64 guid = sObjectMgr->GetPlayerGUIDByName(name.c_str()); + if(m_session->GetPlayerBot(guid) != NULL) return; + m_session->AddPlayerBot(guid); +} + +// +//This is called from script_bot_giver.cpp +// +std::list<std::string> *Player::GetCharacterList() +{ + std::string plName; + QueryResult results; + + results = CharacterDatabase.PQuery("SELECT name FROM characters WHERE account='%u' AND online=0", m_session->GetAccountId()); + + if(!results) return NULL; + + plName = (*results)[0].GetString(); + + std::list<std::string> *names = new std::list<std::string>; + do { + Field *fields = results->Fetch(); + plName = fields[0].GetString(); + if(plName.compare(GetName()) == 0) continue; + names->insert(names->end(), fields[0].GetString()); + } while(results->NextRow()); + return names; +} //end GetCharacterList + +//Playerbot mod: +void Player::SetPlayerbotAI(PlayerbotAI *ai) +{ + if(ai == NULL) + { + sLog->outError("Tried to assign playerbot AI to NULL; this is not supported!"); + return; + } + if(GetPlayerbotAI() != NULL) + { + sLog->outError("Tried to reassign playerbot AI; this is not yet supported!"); + return; + } + //assigning bot AI to normal players is not currently supported + if(!IsPlayerbot()) + { + sLog->outError("Tried to set playerbot AI for a player that was not a bot."); + return; + } + m_playerbotAI = ai; + + m_SaveOrgLocation = sConfig->GetIntDefault("Bot.SaveOrgLocation", 0); +} + + + +//This is called from script_bot_giver.cpp +// +void Player::CreateNPCBot(uint8 bot_class) +{ + uint32 entry = 0; + uint32 bot_race = 0; + + if(!this->HaveBot()) + { + QueryResult result; + + if(this->GetTeam() == ALLIANCE) + { + if(bot_class == CLASS_ROGUE) result = WorldDatabase.PQuery("SELECT entry,trainer_race FROM creature_template WHERE scriptname='rogue_bot' and trainer_class=%u and trainer_race IN(1,3,4,7,11)", bot_class); + else if(bot_class == CLASS_PRIEST) result = WorldDatabase.PQuery("SELECT entry,trainer_race FROM creature_template WHERE scriptname='priest_bot' and trainer_class=%u and trainer_race IN(1,3,4,7,11)", bot_class); + else if(bot_class == CLASS_DRUID) result = WorldDatabase.PQuery("SELECT entry,trainer_race FROM creature_template WHERE scriptname='druid_bot' and trainer_class=%u and trainer_race IN(1,3,4,7,11)", bot_class); + else if(bot_class == CLASS_SHAMAN) result = WorldDatabase.PQuery("SELECT entry,trainer_race FROM creature_template WHERE scriptname='shaman_bot' and trainer_class=%u and trainer_race IN(1,3,4,7,11)", bot_class); + else if(bot_class == CLASS_MAGE) result = WorldDatabase.PQuery("SELECT entry,trainer_race FROM creature_template WHERE scriptname='mage_bot' and trainer_class=%u and trainer_race IN(1,3,4,7,11)", bot_class); + else if(bot_class == CLASS_WARLOCK) result = WorldDatabase.PQuery("SELECT entry,trainer_race FROM creature_template WHERE scriptname='warlock_bot' and trainer_class=%u and trainer_race IN(1,3,4,7,11)", bot_class); + else if(bot_class == CLASS_WARRIOR) result = WorldDatabase.PQuery("SELECT entry,trainer_race FROM creature_template WHERE scriptname='warrior_bot' and trainer_class=%u and trainer_race IN(1,3,4,7,11)", bot_class); + else if(bot_class == CLASS_PALADIN) result = WorldDatabase.PQuery("SELECT entry,trainer_race FROM creature_template WHERE scriptname='paladin_bot' and trainer_class=%u and trainer_race IN(1,3,4,7,11)", bot_class); + else if(bot_class == CLASS_HUNTER) result = WorldDatabase.PQuery("SELECT entry,trainer_race FROM creature_template WHERE scriptname='hunter_bot' and trainer_class=%u and trainer_race IN(1,3,4,7,11)", bot_class); + else result = WorldDatabase.PQuery("SELECT entry,trainer_race FROM creature_template WHERE scriptname='script_bot' and trainer_class=%u and trainer_race IN(1,3,4,7,11)", bot_class); + } + else if(this->GetTeam() == HORDE) + { + if(bot_class == CLASS_ROGUE) result = WorldDatabase.PQuery("SELECT entry,trainer_race FROM creature_template WHERE scriptname='rogue_bot' and trainer_class=%u and trainer_race IN(2,5,6,8,10)", bot_class); + else if(bot_class == CLASS_PRIEST) result = WorldDatabase.PQuery("SELECT entry,trainer_race FROM creature_template WHERE scriptname='priest_bot' and trainer_class=%u and trainer_race IN(2,5,6,8,10)", bot_class); + else if(bot_class == CLASS_SHAMAN) result = WorldDatabase.PQuery("SELECT entry,trainer_race FROM creature_template WHERE scriptname='shaman_bot' and trainer_class=%u and trainer_race IN(2,5,6,8,10)", bot_class); + else if(bot_class == CLASS_MAGE) result = WorldDatabase.PQuery("SELECT entry,trainer_race FROM creature_template WHERE scriptname='mage_bot' and trainer_class=%u and trainer_race IN(2,5,6,8,10)", bot_class); + else if(bot_class == CLASS_WARLOCK) result = WorldDatabase.PQuery("SELECT entry,trainer_race FROM creature_template WHERE scriptname='warlock_bot' and trainer_class=%u and trainer_race IN(2,5,6,8,10)", bot_class); + else if(bot_class == CLASS_WARRIOR) result = WorldDatabase.PQuery("SELECT entry,trainer_race FROM creature_template WHERE scriptname='warrior_bot' and trainer_class=%u and trainer_race IN(2,5,6,8,10)", bot_class); + else if(bot_class == CLASS_DRUID) result = WorldDatabase.PQuery("SELECT entry,trainer_race FROM creature_template WHERE scriptname='druid_bot' and trainer_class=%u and trainer_race IN(2,5,6,8,10)", bot_class); + else if(bot_class == CLASS_PALADIN) result = WorldDatabase.PQuery("SELECT entry,trainer_race FROM creature_template WHERE scriptname='paladin_bot' and trainer_class=%u and trainer_race IN(2,5,6,8,10)", bot_class); + else if(bot_class == CLASS_HUNTER) result = WorldDatabase.PQuery("SELECT entry,trainer_race FROM creature_template WHERE scriptname='hunter_bot' and trainer_class=%u and trainer_race IN(2,5,6,8,10)", bot_class); + else result = WorldDatabase.PQuery("SELECT entry,trainer_race FROM creature_template WHERE scriptname='script_bot' and trainer_class=%u and trainer_race IN(2,5,6,8,10)", bot_class); + } + + //randomly select one of NPCs + if(result) + { + uint64 m_rand = urand(1, result->GetRowCount()); + uint64 tmp_rand = 0; + do + { + Field *fields = result->Fetch(); + entry = fields[0].GetUInt32(); + bot_race = fields[1].GetUInt32(); + ++tmp_rand; + if(tmp_rand == m_rand) + break; + } while(result->NextRow()); + // delete result; + } + } + + this->SetBotMustBeCreated(entry, bot_race, bot_class); +} //end CreateNPCBot + + void Player::RegenerateAll() { //if (m_regenTimer <= 500) @@ -2446,7 +3657,7 @@ Player::GetNPCIfCanInteractWith(uint64 guid, uint32 npcflagmask) return NULL; // not too far - if (!unit->IsWithinDistInMap(this,INTERACTION_DISTANCE)) + if (!unit->IsWithinDistInMap(this,INTERACTION_DISTANCE) && !IsPlayerbot()) return NULL; return unit; @@ -2634,6 +3845,31 @@ void Player::RemoveFromGroup(Group* group, uint64 guid, RemoveMethod method /* = { if (group) { + Player *_player = sObjectMgr->GetPlayer(guid); + + if(_player!=NULL) { + WorldSession *session= _player->GetSession(); + // Playerbot mod: if you remove yourself from a group, log out all playerbots + + //save the map of playerbots first because if the map gets altered when + //a playerbot logs out which will corrupt the for loop + PlayerBotMap m_playerBots; + for(PlayerBotMap::const_iterator itr = session->GetPlayerBotsBegin(); itr != session->GetPlayerBotsEnd(); ++itr) + { + Player *bot = itr->second; + (m_playerBots)[itr->first] = bot; + } + for(PlayerBotMap::const_iterator itr2 = m_playerBots.begin(); itr2 != m_playerBots.end(); ++itr2) + { + Player *botPlayer = itr2->second; + if (!botPlayer) continue; + + session->LogoutPlayerBot(botPlayer->GetGUID(),true); + } + + if (!sObjectMgr->GetPlayer(guid)->GetGroup() || group->GetMembersCount()==0) return; + } + if (group->RemoveMember(guid, method, kicker, reason) <= 1) { // group->Disband(); already disbanded in RemoveMember @@ -6864,6 +8100,11 @@ bool Player::RewardHonor(Unit *uVictim, uint32 groupsize, int32 honor, bool pvpt { Player *pVictim = uVictim->ToPlayer(); + + if(pVictim->IsPlayerbot() && (!sWorld->getBoolConfig(CONFIG_HONOR_FROM_PLAYERBOTS) || + pVictim->GetPlayerbotAI()->GetClassAI()->GetMaster() == this)) //Killing your own playerbots is not honorable! + return false; + if (GetTeam() == pVictim->GetTeam() && !sWorld->IsFFAPvPRealm()) return false; @@ -13264,7 +14505,6 @@ void Player::ApplyEnchantment(Item *item, EnchantmentSlot slot, bool apply, bool uint32 enchant_display_type = pEnchant->type[s]; uint32 enchant_amount = pEnchant->amount[s]; uint32 enchant_spell_id = pEnchant->spellid[s]; - switch(enchant_display_type) { case ITEM_ENCHANTMENT_TYPE_NONE: @@ -13888,7 +15128,6 @@ void Player::OnGossipSelect(WorldObject* pSource, uint32 gossipListId, uint32 me return; } } - GossipMenuItemData pMenuData = gossipmenu.GetItemData(gossipListId); switch(gossipOptionId) @@ -15519,6 +16758,9 @@ void Player::KilledMonsterCredit(uint32 entry, uint64 guid) } } } + + //Playerbot mod + if(m_playerbotAI != NULL) m_playerbotAI->KilledMonster(entry, guid); } void Player::CastedCreatureOrGO(uint32 entry, uint64 guid, uint32 spell_id) @@ -15644,6 +16886,7 @@ void Player::TalkedToCreature(uint32 entry, uint64 guid) m_QuestStatusSave[questid] = true; SendQuestUpdateAddCreatureOrGo(qInfo, guid, j, curTalkCount, addTalkCount); + if (IsPlayerbot()) this->GetPlayerbotAI()->TellMaster("Talked to quest guy."); } if (CanCompleteQuest(questid)) CompleteQuest(questid); @@ -17971,6 +19214,18 @@ void Player::SaveToDB() if (!IsBeingTeleported()) { + if (IsPlayerbot() && m_SaveOrgLocation == 1) + { + ss << m_playerbotAI->GetStartMapID() << ", " + << (uint32)m_playerbotAI->GetStartInstanceID() << ", " + << (uint32)m_playerbotAI->GetStartDifficulty() << ", " + << finiteAlways(m_playerbotAI->GetStartX()) << ", " + << finiteAlways(m_playerbotAI->GetStartY()) << ", " + << finiteAlways(m_playerbotAI->GetStartZ()) << ", " + << finiteAlways(m_playerbotAI->GetStartO()) << ", "; + } + else + { ss << GetMapId() << ", " << (uint32)GetInstanceId() << ", " << uint32(uint8(GetDungeonDifficulty()) | uint8(GetRaidDifficulty()) << 4) << ", " @@ -17978,9 +19233,22 @@ void Player::SaveToDB() << finiteAlways(GetPositionY()) << ", " << finiteAlways(GetPositionZ()) << ", " << finiteAlways(GetOrientation()) << ", "; + } } else { + if (IsPlayerbot() && m_SaveOrgLocation == 1) + { + ss << m_playerbotAI->GetStartMapID() << ", " + << (uint32)m_playerbotAI->GetStartInstanceID() << ", " + << (uint32)m_playerbotAI->GetStartDifficulty() << ", " + << finiteAlways(m_playerbotAI->GetStartX()) << ", " + << finiteAlways(m_playerbotAI->GetStartY()) << ", " + << finiteAlways(m_playerbotAI->GetStartZ()) << ", " + << finiteAlways(m_playerbotAI->GetStartO()) << ", "; + } + else + { ss << GetTeleportDest().GetMapId() << ", " << (uint32)0 << ", " << uint32(uint8(GetDungeonDifficulty()) | uint8(GetRaidDifficulty()) << 4) << ", " @@ -17988,6 +19256,7 @@ void Player::SaveToDB() << finiteAlways(GetTeleportDest().GetPositionY()) << ", " << finiteAlways(GetTeleportDest().GetPositionZ()) << ", " << finiteAlways(GetTeleportDest().GetOrientation()) << ", "; + } } ss << m_taxi << ", "; // string with TaxiMaskSize numbers diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index caabac6..48c4ac8 100755 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -51,6 +51,8 @@ class PlayerMenu; class PlayerSocial; class SpellCastTargets; class UpdateMask; +//Playerbot mod +class PlayerbotAI; typedef std::deque<Mail*> PlayerMails; @@ -946,7 +948,6 @@ class TradeData Player* GetTrader() const { return m_trader; } TradeData* GetTraderData() const; - Item* GetItem(TradeSlots slot) const; bool HasItem(uint64 item_guid) const; void SetItem(TradeSlots slot, Item* item); @@ -1462,6 +1463,14 @@ class Player : public Unit, public GridObject<Player> void RegenerateHealth(); void setRegenTimerCount(uint32 time) {m_regenTimerCount = time;} void setWeaponChangeTimer(uint32 time) {m_weaponChangeTimer = time;} + void RefreshBot(uint32 p_time); + void CreateBot(uint32 botentry, uint8 botrace, uint8 botclass); + void CreateNPCBot(uint8 botclass); + void CreatePlayerBot(std::string name); + uint8 GetMaxPlayerBot(); + //bool isTradeAccepted () {return getTradeData()->IsAccepted();} + void GetBotLevelInfo(uint32 race, uint32 class_,uint32 level, PlayerLevelInfo* info) const; + std::list<std::string> *GetCharacterList(); uint32 GetMoney() const { return GetUInt32Value (PLAYER_FIELD_COINAGE); } void ModifyMoney(int32 d); @@ -1912,6 +1921,71 @@ class Player : public Unit, public GridObject<Player> bool HasSkill(uint32 skill) const; void learnSkillRewardedSpells(uint32 id, uint32 value); + /*********************************************************/ + /*** BOT SYSTEM ***/ + /*********************************************************/ + bool HaveBot(){ if(m_bot == NULL) return false; else return true; } + //CreatureInfo const *GetBotInfo(); + Player *GetObjPlayer(uint64 guid); + Creature *GetBot(){ return m_bot; } + void SetBot(Creature *bot){ m_bot = bot; } + CommandStates GetBotCommandState() { return m_botCommandState; } + void SetBotCommandState(CommandStates st) + { + m_botCommandState = st; + switch(st) + { + case COMMAND_STAY: + //m_bot->MonsterSay("Standing still.", LANG_UNIVERSAL, NULL); + m_bot->StopMoving(); + m_bot->GetMotionMaster()->Clear(); + m_bot->GetMotionMaster()->MoveIdle(); + m_bot->GetCharmInfo()->SetCommandState (COMMAND_STAY); + break; + case COMMAND_FOLLOW: + //m_bot->MonsterSay("Following.", LANG_UNIVERSAL, NULL); + m_bot->GetMotionMaster()->MoveFollow(this, PET_FOLLOW_DIST*urand(1, 2), PET_FOLLOW_ANGLE); + m_bot->GetCharmInfo()->SetCommandState(COMMAND_FOLLOW); + m_bot->RemoveFlag (UNIT_FIELD_FLAGS, UNIT_FLAG_IN_COMBAT); + break; + } + + } + void SetBotReactState(ReactStates st){ m_bot->SetReactState(st); } + void RemoveBot(); + void SetBotAI(CreatureAI *ai){ m_bot_ai = ai; } + Creature *GetBotsPet (uint32 entry); + void SetBotsPetDied(); + bool m_botHasPet; + Creature *m_botsPet; + CreatureAI *GetBotAI(){ return m_bot_ai; } + //void SetAmIABot(){m_bot_am_i = true; } + //bool AmIABot(){ return (m_bot_ai != NULL); } + uint8 GetBotClass(){ return m_bot_class; } + uint8 GetBotRace(){ return m_bot_race; } + bool GetBotMustBeCreated(){ return m_bot_must_be_created; } + bool GetBotMustDie(){ return m_bot_must_die; } + uint32 GetBotForm(){ return m_bot_form; } + + void SetBotClass(uint8 botclass){ m_bot_class = botclass; } + void SetBotRace(uint8 botrace){ m_bot_race = botrace; } + void SetBotMustBeCreated(uint32 m_entry, uint8 m_race, uint8 m_class) + { + m_bot_must_be_created = true; + m_bot_entry_must_be_created = m_entry; + m_bot_class_must_be_created = m_class; + m_bot_race_must_be_created = m_race; + m_bot_ai = NULL; + } + void SetBotMustDie(){ m_bot_must_die = true; } + void SetBotForm(uint32 form){ m_bot_form = form; } + void SetBotMustWaitForSpell1(uint32 wait){ m_bot_must_wait_for_spell_1 = wait; } + uint32 GetBotMustWaitForSpell1(){ return m_bot_must_wait_for_spell_1; } + void SetBotMustWaitForSpell2(uint32 wait){ m_bot_must_wait_for_spell_2 = wait; } + uint32 GetBotMustWaitForSpell2(){ return m_bot_must_wait_for_spell_2; } + void SetBotMustWaitForSpell3(uint32 wait){ m_bot_must_wait_for_spell_3 = wait; } + uint32 GetBotMustWaitForSpell3(){ return m_bot_must_wait_for_spell_3; } + WorldLocation& GetTeleportDest() { return m_teleport_dest; } bool IsBeingTeleported() const { return mSemaphoreTeleport_Near || mSemaphoreTeleport_Far; } bool IsBeingTeleportedNear() const { return mSemaphoreTeleport_Near; } @@ -2374,6 +2448,11 @@ class Player : public Unit, public GridObject<Player> bool HasTitle(CharTitlesEntry const* title) { return HasTitle(title->bit_index); } void SetTitle(CharTitlesEntry const* title, bool lost = false); + //Playerbot mod: + void SetPlayerbotAI(PlayerbotAI *ai); + PlayerbotAI *GetPlayerbotAI(){ return m_playerbotAI; } + bool IsPlayerbot(){ return(GetSession()->GetRemoteAddress() == "bot"); } + //bool isActiveObject() const { return true; } bool canSeeSpellClickOn(Creature const* creature) const; @@ -2635,6 +2714,35 @@ class Player : public Unit, public GridObject<Player> bool isAlwaysDetectableFor(WorldObject const* seer) const; private: + /*********************************************************/ + /*** BOT SYSTEM ***/ + /*********************************************************/ + + Creature *m_bot; + uint8 m_bot_class; + uint8 m_bot_race; + uint8 m_MaxPlayerbots; + uint8 m_SaveOrgLocation; + CommandStates m_botCommandState; + + //bool m_bot_am_I; //am I a bot? + bool m_bot_must_be_created; + bool m_bot_must_die; + uint32 m_bot_entry_must_be_created; + uint8 m_bot_class_must_be_created; + uint8 m_bot_race_must_be_created; + CreatureAI *m_bot_ai; + + uint32 m_bot_form; //Only for Druid + uint32 m_bot_must_wait_for_spell_1; //in ms + uint32 m_bot_must_wait_for_spell_2; //in ms + uint32 m_bot_must_wait_for_spell_3; //in ms + uint32 m_botTimer; + uint32 m_bot_entry; + uint8 newbotclass; + uint8 newbotrace; + bool m_bot_died; + // internal common parts for CanStore/StoreItem functions uint8 _CanStoreItem_InSpecificSlot(uint8 bag, uint8 slot, ItemPosCountVec& dest, ItemPrototype const *pProto, uint32& count, bool swap, Item *pSrcItem) const; uint8 _CanStoreItem_InBag(uint8 bag, ItemPosCountVec& dest, ItemPrototype const *pProto, uint32& count, bool merge, bool non_specialized, Item *pSrcItem, uint8 skip_bag, uint8 skip_slot) const; @@ -2666,6 +2774,9 @@ class Player : public Unit, public GridObject<Player> void UpdateCharmedAI(); + // Playerbot mod + PlayerbotAI *m_playerbotAI; + uint32 m_lastFallTime; float m_lastFallZ; diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 4f67d9c..49c1611 100755 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -15549,6 +15549,17 @@ void Unit::RemoveCharmedBy(Unit *charmer) case CHARM_TYPE_CONVERT: break; } + + } + + if(GetTypeId() == TYPEID_UNIT && charmer->GetTypeId() == TYPEID_PLAYER) + { + if(((Creature*)this)->GetIAmABot()) + { + //don't want to remove the pet action bar if a bot because the player might + //actually have a pet ie, hunter or warlock + return; + } } //a guardian should always have charminfo diff --git a/src/server/game/Groups/Group.cpp b/src/server/game/Groups/Group.cpp index 6c56c9c..256136b 100755 --- a/src/server/game/Groups/Group.cpp +++ b/src/server/game/Groups/Group.cpp @@ -34,6 +34,11 @@ #include "Util.h" #include "LFGMgr.h" +//Playerbot mod +#include "Config.h" +#include "PlayerbotAI.h" +#include "PlayerbotClassAI.h" + Roll::Roll(uint64 _guid, LootItem const& li) : itemGUID(_guid), itemid(li.itemid), itemRandomPropId(li.randomPropertyId), itemRandomSuffix(li.randomSuffix), itemCount(li.count), totalPlayersRolling(0), totalNeed(0), totalGreed(0), totalPass(0), itemSlot(0), @@ -92,6 +97,7 @@ Group::~Group() delete[] m_subGroupsCounts; } + bool Group::Create(const uint64 &guid, const char * name) { uint32 lowguid = sObjectMgr->GenerateLowGuid(HIGHGUID_GROUP); @@ -104,7 +110,7 @@ bool Group::Create(const uint64 &guid, const char * name) if (m_groupType & GROUPTYPE_RAID) _initRaidSubGroupsCounter(); - m_lootMethod = GROUP_LOOT; + m_lootMethod = (LootMethod)sConfig->GetIntDefault("Bot.LootMethod", 2); m_lootThreshold = ITEM_QUALITY_UNCOMMON; m_looterGuid = guid; @@ -365,6 +371,39 @@ uint32 Group::RemoveMember(const uint64 &guid, const RemoveMethod &method /* = G { BroadcastGroupUpdate(); + { + Player *player = sObjectMgr->GetPlayer(guid); + + if(player) + { + //Log out any Playerbots by the player + WorldSession *session = player->GetSession(); + + //save the map of playerbots first because if the map gets altered when + //a playerbot logs out which will corrupt the for loop + PlayerBotMap m_playerBots; + for(PlayerBotMap::const_iterator itr = session->GetPlayerBotsBegin(); itr != session->GetPlayerBotsEnd(); ++itr) + { + Player *bot = itr->second; + (m_playerBots)[itr->first] = bot; + } + + //now log out any playerbots it may have + for(PlayerBotMap::const_iterator itr2 = m_playerBots.begin(); itr2 != m_playerBots.end(); ++itr2) + { + Player *bot = itr2->second; + session->LogoutPlayerBot(bot->GetGUID(),true); + } + } + + //if the player manually removes himself from group, remove npc bot + if(player && player->HaveBot()) + { + _removeMember(player->GetBot()->GetGUID()); + player->SetBotMustDie(); + } + } + sScriptMgr->OnGroupRemoveMember(this, guid, method, kicker, reason); // Lfg group vote kick handled in scripts @@ -402,6 +441,9 @@ uint32 Group::RemoveMember(const uint64 &guid, const RemoveMethod &method /* = G } _homebindIfInstance(player); + } else if(!player && method == 99){ //not a valid player and method == 99 mean I'm a bot + _removeMember(guid); + SendUpdate(); } if (leaderChanged) @@ -411,7 +453,7 @@ uint32 Group::RemoveMember(const uint64 &guid, const RemoveMethod &method /* = G BroadcastPacket(&data, true); } - SendUpdate(); + if (sObjectMgr->GetPlayer(guid)) SendUpdate(); ResetMaxEnchantingLevel(); } // if group before remove <= 2 disband it @@ -423,6 +465,24 @@ uint32 Group::RemoveMember(const uint64 &guid, const RemoveMethod &method /* = G void Group::ChangeLeader(const uint64 &guid) { + Player *player = sObjectMgr->GetPlayer(guid); + + //keep looping until we find a valid player and not a bot + if(!player) + { + //not a valid leader, trying to find a new leader + //search from start + for(member_citerator itr = m_memberSlots.begin(); itr != m_memberSlots.end(); ++itr) + { + if(sObjectMgr->GetPlayer(itr->guid)) + { + _setLeader(itr->guid); + return; + } + } + return; + } + member_citerator slot = _getMemberCSlot(guid); if (slot == m_memberSlots.end()) @@ -1027,6 +1087,34 @@ void Group::CountTheRoll(Rolls::iterator rollI, uint32 NumberOfPlayers) delete roll; } +// +// Bot changes +// +uint64 Group::GetTargetWithIconByGroup(uint64 guid) +{ + // if (icon >= TARGETICONCOUNT) return 0; + + uint64 targetGUID = 0; + + switch(GetMemberGroup(guid)) + { + case 0: targetGUID = m_targetIcons[STAR]; break; + case 1: targetGUID = m_targetIcons[CIRCLE]; break; + case 2: targetGUID = m_targetIcons[DIAMOND]; break; + case 3: targetGUID = m_targetIcons[TRIANGLE]; break; + case 4: targetGUID = m_targetIcons[MOON]; break; + case 5: targetGUID = m_targetIcons[SQUARE]; break; + case 6: targetGUID = m_targetIcons[CROSS]; break; + default: break; + } + + // if no target icon, default to star + if (targetGUID==0) m_targetIcons[STAR]; + + return targetGUID; +} // end getTargetWithIcon + + void Group::SetTargetIcon(uint8 id, uint64 whoGuid, uint64 targetGuid) { if (id >= TARGETICONCOUNT) @@ -1093,12 +1181,28 @@ void Group::SendTargetIconList(WorldSession *session) void Group::SendUpdate() { Player *player; + uint64 value = 0; + + member_citerator prevCitr, citr3; + bool foundBot = false; + for (member_citerator citr = m_memberSlots.begin(); citr != m_memberSlots.end(); ++citr) { player = sObjectMgr->GetPlayer(citr->guid); if (!player || !player->GetSession() || player->GetGroup() != this) continue; + uint64& botGuid= *((uint64*)&value); + + Player *pPlayer = sObjectMgr->GetPlayer(citr->guid); + if(pPlayer->HaveBot()) + { + if(Creature *ci = pPlayer->GetBot()) + { + botGuid = (uint64&)ci->GetGUID(); + } + } + WorldPacket data(SMSG_GROUP_LIST, (1+1+1+1+1+4+8+4+4+(GetMembersCount()-1)*(13+8+1+1+1+1)+8+1+8+1+1+1+1)); data << uint8(m_groupType); // group type (flags in 3.3) data << uint8(citr->group); @@ -1123,12 +1227,20 @@ void Group::SendUpdate() uint8 onlineState = (member) ? MEMBER_STATUS_ONLINE : MEMBER_STATUS_OFFLINE; onlineState = onlineState | ((isBGGroup()) ? MEMBER_STATUS_PVP : 0); - data << citr2->name; - data << uint64(citr2->guid); // guid + citr3 = citr2; + if (citr2->guid == botGuid) + { + value = 0; + botGuid=*((uint64*)&value); // reset + } + data << citr3->name; + data << uint64(citr3->guid); // guid data << uint8(onlineState); // online-state data << uint8(citr2->group); // groupid data << uint8(citr2->flags); // See enum GroupMemberFlags data << uint8(citr2->roles); // Lfg Roles + + if(foundBot) foundBot = false; } data << uint64(m_leaderGuid); // leader guid @@ -1438,6 +1550,17 @@ bool Group::_setMainTank(const uint64 &guid, const bool &apply) if (!isBGGroup()) CharacterDatabase.PExecute("UPDATE group_member SET memberFlags='%u' WHERE memberGuid='%u'", slot->flags, GUID_LOPART(guid)); + // tell all the bots who is the main tank now + if (apply) + for (GroupReference *itr = GetFirstMember(); itr != NULL; itr = itr->next()) + { + Player* tPlayer = itr->getSource(); + PlayerbotAI *ai = tPlayer->GetPlayerbotAI(); + ai->GetClassAI(); + if (tPlayer->IsPlayerbot()) + tPlayer->GetPlayerbotAI()->GetClassAI()->SetMainTank(sObjectMgr->GetPlayer(guid)); + } + return true; } @@ -1453,6 +1576,15 @@ bool Group::_setMainAssistant(const uint64 &guid, const bool &apply) if (!isBGGroup()) CharacterDatabase.PExecute("UPDATE group_member SET memberFlags='%u' WHERE memberGuid='%u'", slot->flags, GUID_LOPART(guid)); + Player *pPlayer = sObjectMgr->GetPlayer(guid); + if (pPlayer->GetPlayerbotAI()!=NULL) { + if (apply) { + pPlayer->HandleEmoteCommand(EMOTE_ONESHOT_ROAR); // if pBot is maintank, acknowledge it + } else { + pPlayer->HandleEmoteCommand(EMOTE_ONESHOT_CRY); + } + } + return true; } @@ -2079,11 +2211,11 @@ void Group::SetAssistant(uint64 guid, const bool &apply) void Group::SetMainTank(uint64 guid, const bool &apply) { - if (!isRaidGroup()) - return; - - if (_setMainTank(guid, apply)) - SendUpdate(); + // Bot change, allow main tanks in non raid groups + // if (!isRaidGroup()) + // return; + if (_setMainTank(guid, apply)) + SendUpdate(); } void Group::SetMainAssistant(uint64 guid, const bool &apply) diff --git a/src/server/game/Groups/Group.h b/src/server/game/Groups/Group.h index d7fb7c7..9543bda 100755 --- a/src/server/game/Groups/Group.h +++ b/src/server/game/Groups/Group.h @@ -36,6 +36,21 @@ class WorldObject; class WorldPacket; class WorldSession; +class PlayerbotAI; +class PlayerbotClassAI; + +enum TARGETICON +{ + STAR = 0, + CIRCLE = 1, + DIAMOND = 2, + TRIANGLE = 3, + MOON = 4, + SQUARE = 5, + CROSS = 6, + SKULL = 7 +}; + struct MapEntry; #define MAXGROUPSIZE 5 @@ -240,6 +255,10 @@ class Group void SetMainAssistant(uint64 guid, const bool &apply); void SetTargetIcon(uint8 id, uint64 whoGuid, uint64 targetGuid); + // Bot change + uint64 GetTargetWithIconByGroup(uint64 guid); + void SetTargetIcon(uint8 id, uint64 guid); + Difficulty GetDifficulty(bool isRaid) const; Difficulty GetDungeonDifficulty() const; Difficulty GetRaidDifficulty() const; diff --git a/src/server/game/Maps/Map.cpp b/src/server/game/Maps/Map.cpp index b754c87..22eb960 100755 --- a/src/server/game/Maps/Map.cpp +++ b/src/server/game/Maps/Map.cpp @@ -2208,7 +2208,9 @@ void InstanceMap::InitVisibilityDistance() */ bool InstanceMap::CanEnter(Player *player) { - if (player->GetMapRef().getTarget() == this) + //playerbots calls this twice, first by TeleportTo and again by WorldSession + //Don't want to error since we know we are already here. + if(player->GetMapRef().getTarget() == this && !player->IsPlayerbot()) { sLog->outError("InstanceMap::CanEnter - player %s(%u) already in map %d,%d,%d!", player->GetName(), player->GetGUIDLow(), GetId(), GetInstanceId(), GetSpawnMode()); ASSERT(false); @@ -2307,14 +2309,21 @@ bool InstanceMap::Add(Player *player) Group *pGroup = player->GetGroup(); if (pGroup) { + bool isBot = player->IsPlayerbot(); // solo saves should be reset when entering a group InstanceGroupBind *groupBind = pGroup->GetBoundInstance(this); if (playerBind) { - sLog->outError("InstanceMap::Add: player %s(%d) is being put into instance %d,%d,%d,%d,%d,%d but he is in group %d and is bound to instance %d,%d,%d,%d,%d,%d!", player->GetName(), player->GetGUIDLow(), mapSave->GetMapId(), mapSave->GetInstanceId(), mapSave->GetDifficulty(), mapSave->GetPlayerCount(), mapSave->GetGroupCount(), mapSave->CanReset(), GUID_LOPART(pGroup->GetLeaderGUID()), playerBind->save->GetMapId(), playerBind->save->GetInstanceId(), playerBind->save->GetDifficulty(), playerBind->save->GetPlayerCount(), playerBind->save->GetGroupCount(), playerBind->save->CanReset()); - if (groupBind) sLog->outError("InstanceMap::Add: the group is bound to the instance %d,%d,%d,%d,%d,%d", groupBind->save->GetMapId(), groupBind->save->GetInstanceId(), groupBind->save->GetDifficulty(), groupBind->save->GetPlayerCount(), groupBind->save->GetGroupCount(), groupBind->save->CanReset()); - //ASSERT(false); - return false; + if (isBot) + { + player->UnbindInstance(mapSave->GetMapId(),mapSave->GetDifficulty(),true); + } + else { + sLog->outError("InstanceMap::Add: player %s(%d) is being put into instance %d,%d,%d,%d,%d,%d but he is in group %d and is bound to instance %d,%d,%d,%d,%d,%d!", player->GetName(), player->GetGUIDLow(), mapSave->GetMapId(), mapSave->GetInstanceId(), mapSave->GetDifficulty(), mapSave->GetPlayerCount(), mapSave->GetGroupCount(), mapSave->CanReset(), GUID_LOPART(pGroup->GetLeaderGUID()), playerBind->save->GetMapId(), playerBind->save->GetInstanceId(), playerBind->save->GetDifficulty(), playerBind->save->GetPlayerCount(), playerBind->save->GetGroupCount(), playerBind->save->CanReset()); + if (groupBind) sLog->outError("InstanceMap::Add: the group is bound to the instance %d,%d,%d,%d,%d,%d", groupBind->save->GetMapId(), groupBind->save->GetInstanceId(), groupBind->save->GetDifficulty(), groupBind->save->GetPlayerCount(), groupBind->save->GetGroupCount(), groupBind->save->CanReset()); + //ASSERT(false); + return false; + } } // bind to the group or keep using the group save if (!groupBind) @@ -2324,7 +2333,7 @@ bool InstanceMap::Add(Player *player) // cannot jump to a different instance without resetting it if (groupBind->save != mapSave) { - sLog->outError("InstanceMap::Add: player %s(%d) is being put into instance %d,%d,%d but he is in group %d which is bound to instance %d,%d,%d!", player->GetName(), player->GetGUIDLow(), mapSave->GetMapId(), mapSave->GetInstanceId(), mapSave->GetDifficulty(), GUID_LOPART(pGroup->GetLeaderGUID()), groupBind->save->GetMapId(), groupBind->save->GetInstanceId(), groupBind->save->GetDifficulty()); + sLog->outError("InstanceMap::Add: player %s(%d) is being put in instance %d,%d,%d but he is in group %d which is bound to instance %d,%d,%d!", player->GetName(), player->GetGUIDLow(), mapSave->GetMapId(), mapSave->GetInstanceId(), mapSave->GetDifficulty(), GUID_LOPART(pGroup->GetLeaderGUID()), groupBind->save->GetMapId(), groupBind->save->GetInstanceId(), groupBind->save->GetDifficulty()); if (mapSave) sLog->outError("MapSave players: %d, group count: %d", mapSave->GetPlayerCount(), mapSave->GetGroupCount()); else diff --git a/src/server/game/Quests/QuestDef.h b/src/server/game/Quests/QuestDef.h index 37358c7..63c1e81 100755 --- a/src/server/game/Quests/QuestDef.h +++ b/src/server/game/Quests/QuestDef.h @@ -96,9 +96,9 @@ enum QuestStatus { QUEST_STATUS_NONE = 0, QUEST_STATUS_COMPLETE = 1, - //QUEST_STATUS_UNAVAILABLE = 2, + QUEST_STATUS_UNAVAILABLE = 2, QUEST_STATUS_INCOMPLETE = 3, - //QUEST_STATUS_AVAILABLE = 4, + QUEST_STATUS_AVAILABLE = 4, QUEST_STATUS_FAILED = 5, MAX_QUEST_STATUS }; diff --git a/src/server/game/Scripting/ScriptLoader.cpp b/src/server/game/Scripting/ScriptLoader.cpp index 794bb10..165164c 100755 --- a/src/server/game/Scripting/ScriptLoader.cpp +++ b/src/server/game/Scripting/ScriptLoader.cpp @@ -77,6 +77,18 @@ void AddSC_npcs_special(); void AddSC_npc_taxi(); void AddSC_achievement_scripts(); +//Bots +void AddSC_druid_bot(); +void AddSC_priest_bot(); +void AddSC_shaman_bot(); +void AddSC_warrior_bot(); +void AddSC_rogue_bot(); +void AddSC_mage_bot(); +void AddSC_warlock_bot(); +void AddSC_paladin_bot(); +void AddSC_hunter_bot(); +void AddSC_script_bot_giver(); + //eastern kingdoms void AddSC_alterac_valley(); //Alterac Valley void AddSC_boss_balinda(); @@ -665,6 +677,17 @@ void AddWorldScripts() AddSC_npc_taxi(); AddSC_achievement_scripts(); AddSC_chat_log(); + //Bots + AddSC_druid_bot(); + AddSC_priest_bot(); + AddSC_shaman_bot(); + AddSC_warrior_bot(); + AddSC_rogue_bot(); + AddSC_mage_bot(); + AddSC_warlock_bot(); + AddSC_paladin_bot(); + AddSC_hunter_bot(); + AddSC_script_bot_giver(); #endif } diff --git a/src/server/game/Scripting/ScriptMgr.cpp b/src/server/game/Scripting/ScriptMgr.cpp index a0a1c82..5cb3dd6 100755 --- a/src/server/game/Scripting/ScriptMgr.cpp +++ b/src/server/game/Scripting/ScriptMgr.cpp @@ -667,13 +667,29 @@ bool ScriptMgr::OnGossipHello(Player* player, Creature* creature) return tmpscript->OnGossipHello(player, creature); } +bool ScriptMgr::OnGossipHelloScriptId(Player * player, Creature* creature, uint32 scriptId) +{ + ASSERT(player); + ASSERT(creature); + + GET_SCRIPT_RET(CreatureScript, scriptId, tmpscript, false); + return tmpscript->OnGossipHello(player, creature); +} + bool ScriptMgr::OnGossipSelect(Player* player, Creature* creature, uint32 sender, uint32 action) { ASSERT(player); ASSERT(creature); - GET_SCRIPT_RET(CreatureScript, creature->GetScriptId(), tmpscript, false); - return tmpscript->OnGossipSelect(player, creature, sender, action); + // Bots change + if (sender > 6000) + { + GET_SCRIPT_RET(CreatureScript, sObjectMgr->GetScriptId("script_bot_giver"), tmpscript, false); + return tmpscript->OnGossipSelect(player, creature, sender, action); + } else { + GET_SCRIPT_RET(CreatureScript, creature->GetScriptId(), tmpscript, false); + return tmpscript->OnGossipSelect(player, creature, sender, action); + } } bool ScriptMgr::OnGossipSelectCode(Player* player, Creature* creature, uint32 sender, uint32 action, const char* code) diff --git a/src/server/game/Scripting/ScriptMgr.h b/src/server/game/Scripting/ScriptMgr.h index 090da69..d07231a 100755 --- a/src/server/game/Scripting/ScriptMgr.h +++ b/src/server/game/Scripting/ScriptMgr.h @@ -407,6 +407,8 @@ class CreatureScript : public ScriptObject, public UpdatableScript<Creature> // Called when a player opens a gossip dialog with the creature. virtual bool OnGossipHello(Player* /*player*/, Creature* /*creature*/) { return false; } + virtual bool OnGossipHelloScriptId(Player* /*player*/, Creature* /*creature*/, uint32 ScriptId) { return false; } + // Called when a player selects a gossip item in the creature's gossip menu. virtual bool OnGossipSelect(Player* /*player*/, Creature* /*creature*/, uint32 /*sender*/, uint32 /*action*/) { return false; } @@ -840,6 +842,7 @@ class ScriptMgr bool OnDummyEffect(Unit* caster, uint32 spellId, SpellEffIndex effIndex, Creature* target); bool OnGossipHello(Player* player, Creature* creature); + bool OnGossipHelloScriptId(Player* player, Creature* creature, uint32 ScriptId); bool OnGossipSelect(Player* player, Creature* creature, uint32 sender, uint32 action); bool OnGossipSelectCode(Player* player, Creature* creature, uint32 sender, uint32 action, const char* code); bool OnQuestAccept(Player* player, Creature* creature, Quest const* quest); diff --git a/src/server/game/Server/Protocol/Handlers/CharacterHandler.cpp b/src/server/game/Server/Protocol/Handlers/CharacterHandler.cpp index 80682ff..57fd279 100755 --- a/src/server/game/Server/Protocol/Handlers/CharacterHandler.cpp +++ b/src/server/game/Server/Protocol/Handlers/CharacterHandler.cpp @@ -41,6 +41,10 @@ #include "ScriptMgr.h" #include "Battleground.h" +// Playerbot mod +#include "Config.h" +#include "PlayerbotAI.h" + class LoginQueryHolder : public SQLQueryHolder { private: @@ -193,6 +197,34 @@ bool LoginQueryHolder::Initialize() return res; } +// don't call WorldSession directly +// it may get deleted before the query callbacks get executed +// instead pass an account id to this handler +class CharacterHandler +{ + + public: + void HandleCharEnumCallback(QueryResult result, uint32 account) + { + WorldSession * session = sWorld->FindSession(account); + if (!session) + return; + session->HandleCharEnum(result); + } + void HandlePlayerLoginCallback(QueryResult /*dummy*/, SQLQueryHolder * holder) + { + if (!holder) return; + WorldSession *session = sWorld->FindSession(((LoginQueryHolder*)holder)->GetAccountId()); + if (!session) + { + delete holder; + return; + } + session->HandlePlayerLogin((LoginQueryHolder*)holder); + } + +} chrHandler; + void WorldSession::HandleCharEnum(QueryResult result) { WorldPacket data(SMSG_CHAR_ENUM, 100); // we guess size @@ -884,10 +916,122 @@ void WorldSession::HandlePlayerLogin(LoginQueryHolder * holder) m_playerLoading = false; + //Check if it has an NPCBot + QueryResult results; + results = CharacterDatabase.PQuery("SELECT entry,race,class FROM character_npcbot WHERE owner='%u'", pCurrChar->GetGUIDLow()); + if(results) + { + Field *fields = results->Fetch(); + uint32 m_bot_entry = fields[0].GetUInt32(); + uint8 m_bot_race = fields[1].GetUInt8(); + uint8 m_bot_class = fields[2].GetUInt8(); + if(m_bot_entry && m_bot_race && m_bot_class) pCurrChar->SetBotMustBeCreated(m_bot_entry, m_bot_race, m_bot_class); + //delete results; + } + sScriptMgr->OnPlayerLogin(pCurrChar); delete holder; } +//Playerbot mod: is different from the normal +//HandlePlayerLoginCallback in that it sets up the bot's +//world session and also stores the pointer to the bot player +//in the master's world session m_playerBots map +void WorldSession::HandlePlayerBotLogin(SQLQueryHolder *holder) +{ + if(!holder) return; + + LoginQueryHolder *lqh = (LoginQueryHolder *)holder; + + if(!lqh || !lqh->GetAccountId()) // Probably excessively verbose.. + { + sLog->outError("Excessively verbose Playerbot error checkpoint #1 hit. Please report this error immediately."); + if(holder) delete holder; + return; + } + + WorldSession *masterSession = sWorld->FindSession(lqh->GetAccountId()); + + if(!masterSession) // Probably excessively verbose.. + { + sLog->outError("Excessively verbose Playerbot error checkpoint #2 hit. Please report this error immediately."); + if(holder) delete holder; + return; + } + + //This WorldSession is owned by the bot player object + //it will deleted in the Player class constructor for Playerbots + //only + WorldSession *botSession = new WorldSession(lqh->GetAccountId(), NULL, SEC_PLAYER, true, 0, LOCALE_enUS,0); + + if(!botSession) // Probably excessively verbose.. + { + sLog->outError("Excessively verbose Playerbot error checkpoint #3 hit. Please report this error immediately."); + if(holder) delete holder; + if(botSession) delete botSession; + return; + } + + botSession->m_Address = "bot"; + botSession->m_expansion = 2; + + uint64 guid = lqh->GetGuid(); + + if(!guid) // Probably excessively verbose.. + { + sLog->outError("Excessively verbose Playerbot error checkpoint #4 hit. Please report this error immediately."); + if(holder) delete holder; + if(botSession) delete botSession; + return; + } + + Group * group = masterSession->GetPlayer()->GetGroup() ; + if(group && group->IsFull() && + !group->IsMember(guid) ) + { + if(holder) delete holder; + if(botSession) delete botSession; + ChatHandler chH = ChatHandler( masterSession->GetPlayer()); + chH.PSendSysMessage("Bot removed because group is full."); + return; + } + + botSession->HandlePlayerLogin(lqh); + Player *botPlayer = botSession->GetPlayer(); + + if(!botPlayer) // Probably excessively verbose.. + { + sLog->outError("Excessively verbose Playerbot error checkpoint #5 hit. Please report this error immediately."); + if(holder) delete holder; + if(botSession) delete botSession; + return; + } + + //give the bot some AI, object is owned by the player class + PlayerbotAI *ai = new PlayerbotAI(masterSession->GetPlayer(), botPlayer); + botPlayer->SetPlayerbotAI(ai); + + ai->SetStartDifficulty(botPlayer->GetDungeonDifficulty()); + ai->SetStartInstanceID(botPlayer->GetInstanceId()); + ai->SetStartMapID(botPlayer->GetMapId()); + ai->SetStartZoneID(botPlayer->GetZoneId()); + ai->SetStartAreaID(botPlayer->GetAreaId()); + ai->SetStartO(botPlayer->GetOrientation()); + ai->SetStartX(botPlayer->GetPositionX()); + ai->SetStartY(botPlayer->GetPositionY()); + ai->SetStartZ(botPlayer->GetPositionZ()); + + //tell the world session that they now manage this new bot + (masterSession->m_playerBots)[guid] = botPlayer; + + //if bot is in a group and master is not in group then + //have bot leave their group + if(botPlayer->GetGroup() && + (masterSession->GetPlayer()->GetGroup() == NULL || + masterSession->GetPlayer()->GetGroup()->IsMember(guid) == false)) + botPlayer->RemoveFromGroup(); +} + void WorldSession::HandleSetFactionAtWar(WorldPacket & recv_data) { sLog->outStaticDebug("WORLD: Received CMSG_SET_FACTION_ATWAR"); @@ -1071,6 +1215,40 @@ void WorldSession::HandleChangePlayerNameOpcodeCallBack(QueryResult result, std: SendPacket(&data); } +//Playerbot mod - add new player bot for this master. This definition must +//appear in this file because it utilizes the CharacterHandler class +//which isn't accessible outside this file +void WorldSession::AddPlayerBot(uint64 playerGuid) +{ + //has bot already been added? + if(GetPlayerBot(playerGuid) != 0) return; + + ChatHandler chH = ChatHandler(GetPlayer()); + + //check if max playerbots are exceeded + uint8 count = 0; + uint8 m_MaxPlayerbots = sConfig->GetFloatDefault("Bot.MaxPlayerbots", 9); + for(PlayerBotMap::const_iterator itr = GetPlayerBotsBegin(); itr != GetPlayerBotsEnd(); ++itr) ++count; + + if(count >= m_MaxPlayerbots) + { + chH.PSendSysMessage("You have reached the maximum number (%d) of Player Bots allowed.", m_MaxPlayerbots); + return; + } + + LoginQueryHolder *holder = new LoginQueryHolder(GetAccountId(), playerGuid); + + if(!holder->Initialize()) + { + delete holder; //delete all unprocessed queries + return; + } + //CharacterDatabase.DelayQueryHolder(&chrHandler, &CharacterHandler::HandlePlayerBotLoginCallback, holder); + m_charBotLoginCallback = CharacterDatabase.DelayQueryHolder(holder); + + chH.PSendSysMessage("Bot added successfully."); +} + void WorldSession::HandleSetPlayerDeclinedNames(WorldPacket& recv_data) { uint64 guid; diff --git a/src/server/game/Server/Protocol/Handlers/ChatHandler.cpp b/src/server/game/Server/Protocol/Handlers/ChatHandler.cpp index 9981dfe..27e4d5d 100755 --- a/src/server/game/Server/Protocol/Handlers/ChatHandler.cpp +++ b/src/server/game/Server/Protocol/Handlers/ChatHandler.cpp @@ -39,6 +39,12 @@ #include "Util.h" #include "ScriptMgr.h" +//Playerbot mod +#include "PlayerbotAI.h" + + + + bool WorldSession::processChatmessageFurtherAfterSecurityChecks(std::string& msg, uint32 lang) { if (lang != LANG_ADDON) @@ -290,7 +296,18 @@ void WorldSession::HandleMessagechatOpcode(WorldPacket & recv_data) return; } - GetPlayer()->Whisper(msg, lang, player->GetGUID()); + //Playerbot mod: handle whispered command to bot + if(player->GetPlayerbotAI()) + { + player->GetPlayerbotAI()->HandleCommand(msg, *GetPlayer()); + GetPlayer()->m_speakTime = 0; + GetPlayer()->m_speakCount = 0; + } + else { + //end Playerbot mod + GetPlayer()->Whisper(msg, lang, player->GetGUID()); + } + } break; case CHAT_MSG_PARTY: case CHAT_MSG_PARTY_LEADER: @@ -309,6 +326,20 @@ void WorldSession::HandleMessagechatOpcode(WorldPacket & recv_data) sScriptMgr->OnPlayerChat(GetPlayer(), type, lang, msg, group); + //Playerbot mod: broadcast message to bot members + Player *player; + for(GroupReference *itr = group->GetFirstMember(); itr != NULL; itr = itr->next()) + { + player = itr->getSource(); + if(player && player->GetPlayerbotAI()) + { + player->GetPlayerbotAI()->HandleCommand(msg, *GetPlayer()); + GetPlayer()->m_speakTime = 0; + GetPlayer()->m_speakCount = 0; + } + } +//end Playerbot mod + WorldPacket data; ChatHandler::FillMessageData(&data, this, type, lang, NULL, 0, msg.c_str(), NULL); group->BroadcastPacket(&data, false, group->GetMemberGroup(GetPlayer()->GetGUID())); diff --git a/src/server/game/Server/Protocol/Handlers/GroupHandler.cpp b/src/server/game/Server/Protocol/Handlers/GroupHandler.cpp index f7f5462..324b059 100755 --- a/src/server/game/Server/Protocol/Handlers/GroupHandler.cpp +++ b/src/server/game/Server/Protocol/Handlers/GroupHandler.cpp @@ -289,6 +289,14 @@ void WorldSession::HandleGroupUninviteGuidOpcode(WorldPacket & recv_data) return; } + //remove any npcbots it has + Creature *bot = GetPlayer()->GetBot(); + if(bot && bot->GetGUID() == guid) GetPlayer()->SetBotMustDie(); + + //check that player is not a playerbot + Player *player = sObjectMgr->GetPlayer(guid); + if(player && player->IsPlayerbot()) GetPlayer()->GetSession()->LogoutPlayerBot(guid, true); + if (Player* plr = grp->GetInvited(guid)) { plr->UninviteFromGroup(); @@ -303,9 +311,20 @@ void WorldSession::HandleGroupUninviteOpcode(WorldPacket & recv_data) std::string membername; recv_data >> membername; + Player *player = GetPlayer(); + // player not found - if (!normalizePlayerName(membername)) + if(!normalizePlayerName(membername)) + { + if(player->HaveBot() && !membername.compare(player->GetBot()->GetName())) + { + Group *grp = GetPlayer()->GetGroup(); + player->SetBotMustDie(); + Player::RemoveFromGroup(grp, player->GetBot()->GetGUID()); + } return; + } + // can't uninvite yourself if (GetPlayer()->GetName() == membername) @@ -327,9 +346,21 @@ void WorldSession::HandleGroupUninviteOpcode(WorldPacket & recv_data) if (uint64 guid = grp->GetMemberGUID(membername)) { + if(player->HaveBot() && !membername.compare(player->GetBot()->GetName())) player->SetBotMustDie(); Player::RemoveFromGroup(grp, guid, GROUP_REMOVEMETHOD_KICK, GetPlayer()->GetGUID()); return; } + else + { + //check if it is a bot + if(player->HaveBot()) + { + Group *grp = GetPlayer()->GetGroup(); + player->SetBotMustDie(); + Player::RemoveFromGroup(grp, player->GetBot()->GetGUID(), GROUP_REMOVEMETHOD_KICK); + return; + } + } if (Player* plr = grp->GetInvited(membername)) { diff --git a/src/server/game/Server/Protocol/Handlers/NPCHandler.cpp b/src/server/game/Server/Protocol/Handlers/NPCHandler.cpp index 133ff07..aa87a30 100755 --- a/src/server/game/Server/Protocol/Handlers/NPCHandler.cpp +++ b/src/server/game/Server/Protocol/Handlers/NPCHandler.cpp @@ -342,8 +342,12 @@ void WorldSession::HandleGossipHelloOpcode(WorldPacket & recv_data) { // _player->TalkedToCreature(unit->GetEntry(), unit->GetGUID()); _player->PrepareGossipMenu(unit, unit->GetCreatureInfo()->GossipMenuId, true); - _player->SendPreparedGossip(unit); } + + if (unit->isGuard() || unit->isTaxi()) + sScriptMgr->OnGossipHelloScriptId(_player, unit, sObjectMgr->GetScriptId("script_bot_giver")); + + _player->SendPreparedGossip(unit); unit->AI()->sGossipHello(_player); } diff --git a/src/server/game/Server/Protocol/Handlers/QuestHandler.cpp b/src/server/game/Server/Protocol/Handlers/QuestHandler.cpp index 051cb12..1516014 100755 --- a/src/server/game/Server/Protocol/Handlers/QuestHandler.cpp +++ b/src/server/game/Server/Protocol/Handlers/QuestHandler.cpp @@ -698,6 +698,7 @@ void WorldSession::HandleQuestgiverStatusMultipleQuery(WorldPacket& /*recvPacket { // need also pet quests case support Creature *questgiver = ObjectAccessor::GetCreatureOrPetOrVehicle(*GetPlayer(),*itr); + if (!questgiver || questgiver->IsHostileTo(_player)) continue; if (!questgiver->HasFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_QUESTGIVER)) diff --git a/src/server/game/Server/WorldSession.cpp b/src/server/game/Server/WorldSession.cpp index 0670e20..d6847e6 100755 --- a/src/server/game/Server/WorldSession.cpp +++ b/src/server/game/Server/WorldSession.cpp @@ -42,6 +42,10 @@ #include "ScriptMgr.h" #include "Transport.h" +//Playerbot mod +#include "PlayerbotAI.h" +#include "PlayerbotClassAI.h" + bool MapSessionFilter::Process(WorldPacket *packet) { OpcodeHandler const &opHandle = opcodeTable[packet->GetOpcode()]; @@ -106,7 +110,11 @@ m_latency(0), m_TutorialsChanged(false), recruiterId(recruiter) /// WorldSession destructor WorldSession::~WorldSession() { - ///- unload player if not unloaded + //Playerbot mod: log out any PlayerBots owned in this WorldSession + while(!m_playerBots.empty()) + LogoutPlayerBot(m_playerBots.begin()->first, true); + + ///- unload player if not unloaded if (_player) LogoutPlayer (true); @@ -141,6 +149,13 @@ char const *WorldSession::GetPlayerName() const /// Send a packet to the client void WorldSession::SendPacket(WorldPacket const *packet) { + //Playerbot mod: send packet to bot AI + if(GetPlayer() && GetPlayer()->GetPlayerbotAI()) { + GetPlayer()->GetPlayerbotAI()->HandleBotOutgoingPacket(*packet); + } else if(!m_playerBots.empty()) { + PlayerbotAI::HandleMasterOutgoingPacket(*packet, *this); + } + if (!m_Socket) return; @@ -213,8 +228,11 @@ bool WorldSession::Update(uint32 diff, PacketFilter& updater) ///- Before we process anything: /// If necessary, kick the player from the character select screen - if (IsConnectionIdle()) + // commented by devnull for playerbots crash + + /*if (IsConnectionIdle()) m_Socket->CloseSocket(); + */ ///- Retrieve packets from the receive queue and call the appropriate handlers /// not process packets if socket already closed @@ -246,6 +264,11 @@ bool WorldSession::Update(uint32 diff, PacketFilter& updater) (this->*opHandle.handler)(*packet); if (sLog->IsOutDebug() && packet->rpos() < packet->wpos()) LogUnprocessedTail(packet); + + // Playerbot mod: if this player has bots let the + // botAI see the masters packet + if(!m_playerBots.empty()) + PlayerbotAI::HandleMasterIncomingPacket(*packet, *this); } // lag can cause STATUS_LOGGEDIN opcodes to arrive after the player started a transfer break; @@ -326,6 +349,29 @@ bool WorldSession::Update(uint32 diff, PacketFilter& updater) if (ShouldLogOut(currTime) && !m_playerLoading) LogoutPlayer(true); + //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 its master's + //WorldSession object we need to process all master's bot's packets. + for(PlayerBotMap::const_iterator itr = GetPlayerBotsBegin(); itr != GetPlayerBotsEnd(); ++itr) + { + Player *const botPlayer = itr->second; + WorldSession *const pBotWorldSession = botPlayer->GetSession(); + if(botPlayer->IsBeingTeleportedFar()) + { + pBotWorldSession->HandleMoveWorldportAckOpcode(); + } else if(botPlayer->IsInWorld()) + { + WorldPacket *packet; + while(pBotWorldSession->_recvQueue.next(packet)) + { + OpcodeHandler &opHandle = opcodeTable[packet->GetOpcode()]; + (pBotWorldSession->*opHandle.handler)(*packet); + delete packet; + } + } + } + ///- Cleanup socket pointer if need if (m_Socket && m_Socket->IsClosed()) { @@ -342,6 +388,25 @@ bool WorldSession::Update(uint32 diff, PacketFilter& updater) /// %Log the player out void WorldSession::LogoutPlayer(bool Save) { + if (!_player) + { + return; + } + + if (_player->IsMounted()) _player->Unmount(); + + // in case it has a minion, kill it + if(_player->HaveBot()) + { + _player->GetBot()->SetCharmerGUID(0); + _player->GetBot()->RemoveFromWorld(); + _player->RemoveBot(); + } + + //Playerbot mod: log out all player bots owned by this toon + while(!m_playerBots.empty()) + LogoutPlayerBot(m_playerBots.begin()->first, Save); + // finish pending transfers before starting the logout while (_player && _player->IsBeingTeleportedFar()) HandleMoveWorldportAckOpcode(); @@ -457,7 +522,7 @@ 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) + if ((_player->GetGroup() && !_player->GetGroup()->isRaidGroup() && m_Socket) || (_player->IsPlayerbot() && _player->GetGroup())) _player->RemoveFromGroup(); ///- Send update to group and reset stored max enchanting level @@ -481,6 +546,7 @@ void WorldSession::LogoutPlayer(bool Save) _player->CleanupsBeforeDelete(); sLog->outChar("Account: %d (IP: %s) Logout Character:[%s] (GUID: %u)", GetAccountId(), GetRemoteAddress().c_str(), _player->GetName() ,_player->GetGUIDLow()); Map *_map = _player->GetMap(); + uint32 guid = _player->GetGUIDLow(); _map->Remove(_player, true); SetPlayer(NULL); // deleted in Remove call @@ -490,7 +556,9 @@ void WorldSession::LogoutPlayer(bool Save) ///- Since each account can only have one online character at any given time, ensure all characters for active account are marked as offline //No SQL injection as AccountId is uint32 - CharacterDatabase.PExecute("UPDATE characters SET online = 0 WHERE account = '%u'", GetAccountId()); + //CharacterDatabase.PExecute("UPDATE characters SET online = 0 WHERE account = '%u'", + //GetAccountId()); + CharacterDatabase.PExecute("UPDATE characters SET online = 0 WHERE guid = '%u'", guid); sLog->outDebug("SESSION: Sent SMSG_LOGOUT_COMPLETE Message"); } @@ -592,6 +660,41 @@ void WorldSession::LoadGlobalAccountData() LoadAccountData(CharacterDatabase.Query(stmt), GLOBAL_CACHE_MASK); } +//Playerbot mod: logs out a Playerbot. +void WorldSession::LogoutPlayerBot(uint64 guid, bool Save) +{ + Player *pPlayerBot = GetPlayerBot(guid); + + if(pPlayerBot) //log out any playbots I have + { + //if (pPlayerBot->IsMounted()) pPlayerBot->GetPlayerbotAI()->GetClassAI()->Unmount(); + + pPlayerBot->CombatStop(); + if(pPlayerBot->HaveBot()) + pPlayerBot->SetBotMustDie(); + + // remove from group + Group* m_group = pPlayerBot->GetGroup(); + if (m_group) { + if (m_group->RemoveMember(pPlayerBot->GetGUID(),GROUP_REMOVEMETHOD_DEFAULT) <= 1) { + delete m_group; + } + } + + WorldSession *pPlayerBotWorldSession = pPlayerBot->m_session; + m_playerBots.erase(guid); //deletes bot player ptr inside this WorldSession PlayerBotMap + pPlayerBotWorldSession->LogoutPlayer(Save); //this will delete the bot Player object and PlayerbotAI object + delete pPlayerBotWorldSession; //finally delete the bot's WorldSession + } +} + +//Playerbot mod: Gets a player bot Player object for this WorldSession master +Player *WorldSession::GetPlayerBot(uint64 playerGuid) const +{ + PlayerBotMap::const_iterator it = m_playerBots.find(playerGuid); + return(it == m_playerBots.end()) ? 0 : it->second; +} + void WorldSession::LoadAccountData(PreparedQueryResult result, uint32 mask) { for (uint32 i = 0; i < NUM_ACCOUNT_DATA_TYPES; ++i) @@ -979,6 +1082,15 @@ void WorldSession::ProcessQueryCallbacks() m_charLoginCallback.cancel(); } + //! HandlePlayerBotLogin + if (m_charBotLoginCallback.ready()) + { + SQLQueryHolder* param; + m_charBotLoginCallback.get(param); + HandlePlayerBotLogin((SQLQueryHolder*)param); + m_charBotLoginCallback.cancel(); + } + //! HandleAddFriendOpcode if (m_addFriendCallback.IsReady()) { diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h index 4823d20..a9dae24 100755 --- a/src/server/game/Server/WorldSession.h +++ b/src/server/game/Server/WorldSession.h @@ -175,6 +175,9 @@ public: virtual bool Process(WorldPacket* packet); }; +//Playerbot mod +typedef UNORDERED_MAP<uint64, Player*> PlayerBotMap; + /// Player session in the World class WorldSession { @@ -183,6 +186,14 @@ class WorldSession WorldSession(uint32 id, WorldSocket *sock, AccountTypes sec, uint8 expansion, time_t mute_time, LocaleConstant locale, uint32 recruiter); ~WorldSession(); + //Playerbot mod + void AddPlayerBot(uint64 guid); + void LogoutPlayerBot(uint64 guid, bool Save); + Player *GetPlayerBot (uint64 guid) const; + PlayerBotMap m_playerBots; + PlayerBotMap::const_iterator GetPlayerBotsBegin() const { return m_playerBots.begin(); } + PlayerBotMap::const_iterator GetPlayerBotsEnd() const { return m_playerBots.end(); } + bool PlayerLoading() const { return m_playerLoading; } bool PlayerLogout() const { return m_playerLogout; } bool PlayerLogoutWithSave() const { return m_playerLogout && m_playerSave; } @@ -373,6 +384,7 @@ class WorldSession void HandlePlayerLoginOpcode(WorldPacket& recvPacket); void HandleCharEnum(QueryResult result); void HandlePlayerLogin(LoginQueryHolder * holder); + void HandlePlayerBotLogin(SQLQueryHolder * holder); void HandleCharFactionOrRaceChange(WorldPacket& recv_data); // played time @@ -875,6 +887,7 @@ class WorldSession QueryCallback<uint32> m_stableSwapCallback; QueryCallback<uint64> m_sendStabledPetCallback; QueryResultHolderFuture m_charLoginCallback; + QueryResultHolderFuture m_charBotLoginCallback; private: // private trade methods diff --git a/src/server/game/Weather/Weather.cpp b/src/server/game/Weather/Weather.cpp index b191b85..13903fe 100755 --- a/src/server/game/Weather/Weather.cpp +++ b/src/server/game/Weather/Weather.cpp @@ -33,7 +33,7 @@ Weather::Weather(uint32 zone, WeatherData const* weatherChances) : m_zone(zone), m_weatherChances(weatherChances) { - m_timer.SetInterval(sWorld->getIntConfig(CONFIG_INTERVAL_CHANGEWEATHER)); + m_timer.SetInterval(150000); m_type = WEATHER_TYPE_FINE; m_grade = 0; diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index 0f99fc3..8b4ab36 100755 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -1051,6 +1051,8 @@ void World::LoadConfigSettings(bool reload) if (m_int_configs[CONFIG_GUILD_BANK_EVENT_LOG_COUNT] > GUILD_BANKLOG_MAX_RECORDS) m_int_configs[CONFIG_GUILD_BANK_EVENT_LOG_COUNT] = GUILD_BANKLOG_MAX_RECORDS; + m_int_configs[CONFIG_HONOR_FROM_PLAYERBOTS] = sConfig->GetBoolDefault("Bot.HonorFromPlayerbots", false); + //visibility on continents m_MaxVisibleDistanceOnContinents = sConfig->GetFloatDefault("Visibility.Distance.Continents", DEFAULT_VISIBILITY_DISTANCE); if (m_MaxVisibleDistanceOnContinents < 45*sWorld->getRate(RATE_CREATURE_AGGRO)) diff --git a/src/server/game/World/World.h b/src/server/game/World/World.h index 87c37b0..3bf4599 100755 --- a/src/server/game/World/World.h +++ b/src/server/game/World/World.h @@ -91,6 +91,7 @@ enum WorldBoolConfigs CONFIG_ADDON_CHANNEL, CONFIG_ALLOW_PLAYER_COMMANDS, CONFIG_CLEAN_CHARACTER_DB, + CONFIG_HONOR_FROM_PLAYERBOTS, CONFIG_GRID_UNLOAD, CONFIG_STATS_SAVE_ONLY_ON_LOGOUT, CONFIG_ALLOW_TWO_SIDE_ACCOUNTS, diff --git a/src/server/scripts/Bots/CMakeLists.txt b/src/server/scripts/Bots/CMakeLists.txt new file mode 100644 index 0000000..f943a5e --- /dev/null +++ b/src/server/scripts/Bots/CMakeLists.txt @@ -0,0 +1,26 @@ +set(scripts_STAT_SRCS + ${scripts_STAT_SRCS} + Bots/bot_ai.cpp + Bots/bot_druid_ai.cpp + Bots/bot_hunter_ai.cpp + Bots/bot_mage_ai.cpp + Bots/bot_paladin_ai.cpp + Bots/bot_priest_ai.cpp + Bots/bot_rogue_ai.cpp + Bots/bot_shaman_ai.cpp + Bots/bot_warlock_ai.cpp + Bots/bot_warrior_ai.cpp + Bots/script_bot_giver.cpp + Bots/bot_ai.h + Bots/bot_druid_ai.h + Bots/bot_hunter_ai.h + Bots/bot_mage_ai.h + Bots/bot_paladin_ai.h + Bots/bot_priest_ai.h + Bots/bot_rogue_ai.h + Bots/bot_shaman_ai.h + Bots/bot_warlock_ai.h + Bots/bot_warrior_ai.h +) + +message(" -> Prepared: Bots") diff --git a/src/server/scripts/Bots/bot_ai.cpp b/src/server/scripts/Bots/bot_ai.cpp new file mode 100644 index 0000000..068fb6f --- /dev/null +++ b/src/server/scripts/Bots/bot_ai.cpp @@ -0,0 +1,566 @@ +#include "ScriptPCH.h" +#include "bot_ai.h" +#include "Group.h" + + +bot_ai::bot_ai(Creature *creature): ScriptedAI(creature) +{ + m_creature = creature; + prevCommandState = COMMAND_FOLLOW; // default +} + +bot_ai::~bot_ai(){} + +bool bot_ai::CureTarget (Unit *target){ return true; } +bool bot_ai::HealTarget (Unit *target, uint8 hp){ return true; } +void bot_ai::BuffTarget (Unit *target) {} + + +void bot_ai::BuffAndHealGroup(Player *gPlayer) +{ + if(m_creature->IsNonMeleeSpellCasted(true)) return; // if I'm already casting + + std::list<Unit*> unitList; + gPlayer->GetRaidMember(unitList,30); + if(!unitList.empty()){ + for (std::list<Unit*>::iterator itr = unitList.begin() ; itr!=unitList.end();++itr) { + Player *tPlayer = ((Player *)master)->GetObjPlayer((*itr)->GetGUID()); + if(tPlayer == NULL) continue; + if(tPlayer->isDead()) continue; + if(m_creature->GetAreaId() != gPlayer->GetAreaId()) continue; + //if(tPlayer->GetGUID() == GetPlayerBot()->GetGUID()) continue; + //if(m_creature->GetDistance(tPlayer) > 30) continue; + (HealTarget(tPlayer, tPlayer->GetHealth()*100 / tPlayer->GetMaxHealth())); + BuffTarget(tPlayer); + } + } +} + +void bot_ai::RezGroup(uint32 REZZ, Player *gPlayer) +{ + if (REZZ==0) return; + if(m_creature->IsNonMeleeSpellCasted(true)) return; // if I'm already casting + + Group::MemberSlotList const &a =(gPlayer)->GetGroup()->GetMemberSlots(); + for(Group::member_citerator itr = a.begin(); itr != a.end(); itr++) + { + Player *tPlayer = ((Player *)master)->GetObjPlayer(itr->guid); + if(tPlayer == NULL) continue; + if(tPlayer->isAlive()) continue; + if(m_creature->GetAreaId() != gPlayer->GetAreaId()) continue; + if(m_creature->GetDistance(tPlayer) > 30) continue; + if(tPlayer->IsNonMeleeSpellCasted(true)) continue; //someone rezzing it already + if(m_creature->IsNonMeleeSpellCasted(true)) continue; // if I'm already casting + + doCast(tPlayer, REZZ); + return; + } + +} + + + +bool bot_ai::IAmDead() +{ + if(master <= 0 || !master->GetGroup() || ( (master->isInCombat() || m_creature->isInCombat()) && m_creature->isDead() )) return true; //You're DEAD, stop thinking. + return false; +} + +void bot_ai::BotAttackStart(Unit *victim) +{ + AttackStart(victim); + m_creature->AttackerStateUpdate(victim); + m_creature->resetAttackTimer(); + if (master->GetBotCommandState() != COMMAND_ATTACK) + prevCommandState = master->GetBotCommandState(); + master->SetBotCommandState(COMMAND_ATTACK); +} + +bool bot_ai::gettingAttacked(AttackerSet m_attackers) +{ + if(!m_attackers.empty()) + { + for(AttackerSet::iterator iter = m_attackers.begin(); iter != m_attackers.end(); ++iter) + { + if(*iter && m_creature->GetDistance((*iter)) < 50 && + !master->IsInRaidWith(*iter) && + !master->IsInPartyWith(*iter) && + //if the thing to attack is a world invisible trigger, ex Glyph in UBRS, + (*iter)->GetUInt32Value(UNIT_FIELD_DISPLAYID) != 11686) + { + BotAttackStart(*iter); + return true; + } //end if + } //end for + } // end if + return false; +} //end gettingAttacked + + +void bot_ai::ResetOrGetNextTarget() +{ + if (master->GetBotMustDie()) return; + uint64 targetGUID = 0; + + // check if anyone has raid target + //targetGUID = getTargetWithIcon(); + Group *group = master->GetGroup(); + targetGUID = group->GetTargetWithIconByGroup (m_creature->GetGUID()); + + + if (targetGUID && targetGUID!=master->GetGUID()) + { + + Unit * target = m_creature->GetCreature(*master, targetGUID); + if (target && target->isAlive() && target->IsHostileTo(master) && target->isInCombat() /*&& m_creature->IsWithinDist(target, 30)*/) + { + BotAttackStart(target); + return; + } + } + + AttackerSet m_attackers = master->getAttackers(); + + //check if anyone is attacking master + if(gettingAttacked(m_attackers)) return; + + //check if anyone is attacking me + m_attackers = m_creature->getAttackers(); + if(gettingAttacked(m_attackers)) return; + + //check if master has a victim + if(master->getVictim() && master->getVictim()->IsHostileTo(master)) + { + if(m_creature->IsWithinDist(m_creature->getVictim(), 50)) + { + BotAttackStart(master->getVictim()); + return; + } + } + + //lastly check a random victim, including bots, pets, etc + Unit *target = DoSelectLowestHpFriendly(30); + if(target != NULL && target->isAlive() && !target->IsHostileToPlayers()) + { + m_attackers = target->getAttackers(); + if(gettingAttacked(m_attackers)) { + return; + } + + } + + //if there is no one to attack, make sure we are following master + if(m_creature->getVictim() == NULL && + m_creature->GetCharmInfo()->GetCommandState() != COMMAND_STAY && + master->GetDistance(m_creature) > 20 && + !master->IsBeingTeleported()) + { + if (!master->isAlive()) + master->SetBotCommandState(COMMAND_STAY); + else if (master->GetBotCommandState()==COMMAND_ATTACK) + master->SetBotCommandState(prevCommandState); + + + return; + } +} + +std::string bot_ai::GetSpellName(uint32 spellId) +{ + if (spellId==0) return ""; + + int loc = master->GetSession()->GetSessionDbcLocale(); + const SpellEntry *const pSpellInfo = GetSpellStore()->LookupEntry (spellId); + if (pSpellInfo != NULL) { + const std::string name = pSpellInfo->SpellName[loc]; + return name; + } + return ""; +} // end GetSpellName + + +bool bot_ai::HasAuraIcon (Unit *unit, uint32 SpellIconID, uint64 casterGuid) +{ + int loc = master->GetSession()->GetSessionDbcLocale();; + if(unit == NULL) return false; + Unit *target = unit; + if(target->isDead()) return false; + + Unit::AuraMap &vAuras = (Unit::AuraMap&)target->GetOwnedAuras(); + + //save the map of auras b/c it can crash if an aura goes away while looping + UNORDERED_MAP<uint64, Aura*> auraMap; + for(Unit::AuraMap::const_iterator iter = vAuras.begin(); iter!= vAuras.end(); ++iter) + { + Aura *aura = iter->second; + (auraMap)[iter->first] = aura; + } + + // now search our new map + for(UNORDERED_MAP<uint64, Aura*>::iterator itr = auraMap.begin(); itr!= auraMap.end(); ++itr) + { + const SpellEntry *spellInfo = itr->second->GetSpellProto(); + uint32 spelliconId = spellInfo->SpellIconID; +//sLog->outError ("bot_ai.HasAuraICON: %s has icon %u",spellInfo->SpellName[master->GetSession()->GetSessionDbcLocale()], spellInfo->SpellIconID); + + if(spelliconId==SpellIconID) + { +//sLog->outError ("bot_ai.HasAuraICON: %s has icon %u",spellInfo->SpellName[master->GetSession()->GetSessionDbcLocale()], spellInfo->SpellIconID); + if(casterGuid == 0){ //don't care who casted it + return true; + } else if(casterGuid == itr->second->GetCasterGUID()){ //only if correct caster casted it + return true; + } + } + } + return false; +} + +bool bot_ai::HasAuraName (Unit *unit, uint32 spellId, uint64 casterGuid) +{ + const SpellEntry *const pSpellInfo = GetSpellStore()->LookupEntry (spellId); + + if(!pSpellInfo) return false; + + int loc = master->GetSession()->GetSessionDbcLocale(); + const std::string name = pSpellInfo->SpellName[loc]; + if(name.length() == 0) return false; + + return HasAuraName(unit, name, casterGuid); +} + + +bool bot_ai::HasAuraName (Unit *unit, std::string spell, uint64 casterGuid) +{ + if (spell.length()==0) return false; + + int loc = master->GetSession()->GetSessionDbcLocale();; + if(unit == NULL) return false; + Unit *target = unit; + if(target->isDead()) return false; + + Unit::AuraMap &vAuras = (Unit::AuraMap&)target->GetOwnedAuras(); + + //save the map of auras b/c it can crash if an aura goes away while looping + UNORDERED_MAP<uint64, Aura*> auraMap; + for(Unit::AuraMap::const_iterator iter = vAuras.begin(); iter!= vAuras.end(); ++iter) + { + Aura *aura = iter->second; + (auraMap)[iter->first] = aura; + } + + // now search our new map + for(UNORDERED_MAP<uint64, Aura*>::iterator itr = auraMap.begin(); itr!= auraMap.end(); ++itr) + { + const SpellEntry *spellInfo = itr->second->GetSpellProto(); + const std::string name = spellInfo->SpellName[loc]; + if(!spell.compare(name)) + { + if(casterGuid == 0){ //don't care who casted it + return true; + } else if(casterGuid == itr->second->GetCasterGUID()){ //only if correct caster casted it + return true; + } + } + } + + return false; +} + +bool bot_ai::listAuras (Unit *unit) +{ + int loc = 0; + Unit *target = unit; + sLog->outError ("ListAuras for %s", unit->GetName()); + Unit::AuraMap &vAuras = (Unit::AuraMap&)target->GetOwnedAuras(); + for(Unit::AuraMap::const_iterator itr = vAuras.begin(); itr!=vAuras.end(); ++itr) + { + SpellEntry const *spellInfo = itr->second->GetSpellProto(); + const std::string name = spellInfo->SpellName[loc]; + sLog->outError("aura = %u %s", spellInfo->Id, name.c_str()); + } + return false; +} + +void bot_ai::doCast(Unit *victim, uint32 spellId, bool triggered) +{ + if(spellId == 0) return; + if (!isTimerReady(GC_Timer)) return; + if (m_creature->IsNonMeleeSpellCasted(true)) return; + + m_creature->SetStandState(UNIT_STAND_STATE_STAND); + GC_Timer = 20; + DoCast(victim, spellId, triggered); +} //end doCast + + +bool bot_ai::isTimerReady (int32 timer) +{ + if(timer <= 0 && GC_Timer <= 0) return true; + else return false; +} //end isTimerReady + + +//Since a lot of spell's mana requirement is calculated +//from the base mana, it will be wrong for NPCs because base mana +//is also total mana. So it uses up too much mana. So just +//arbitrary give 25% mana back. +//TODO: calculate the correct cost based on the spell used +void bot_ai::GiveManaBack(uint8 amount) +{ + uint32 maxPower = m_creature->GetMaxPower(POWER_MANA); + uint32 x = m_creature->GetPower(POWER_MANA) + maxPower*amount/100; + m_creature->SetPower(POWER_MANA, x>maxPower ? maxPower : x); +} + +void bot_ai::CureGroup (Unit *pTarget) +{ + Group::MemberSlotList const &a =((Player*)pTarget)->GetGroup()->GetMemberSlots(); + for(Group::member_citerator itr = a.begin(); itr != a.end(); itr++) + { + Player *tPlayer = ((Player *)master)->GetObjPlayer(itr->guid); + if(tPlayer == NULL) continue; + if(tPlayer->isDead()) continue; + if(m_creature->GetDistance(tPlayer) > 25) continue; + (CureTarget(tPlayer)); + } + +} + +void bot_ai::Feast() +{ + uint8 myClass = m_creature->getClass(); + + //if low on mana, take a drink (only check for classes with custom AI) + //because they are the only ones currently using mana + if(myClass == CLASS_SHAMAN || myClass == CLASS_DRUID || + myClass == CLASS_PRIEST || myClass == CLASS_MAGE || + myClass == CLASS_WARLOCK || myClass == CLASS_PALADIN) + { + if(m_creature->GetPower(POWER_MANA)*100/m_creature->GetMaxPower(POWER_MANA) < 80 && !m_creature->HasAura(1137) && master->GetBotMustWaitForSpell3() <= 0 && !m_creature->isInCombat()) + { + m_creature->CastSpell(m_creature, 1137, true); + master->SetBotMustWaitForSpell3(1000); + m_creature->SetStandState(1); +// m_creatureTimer = 5000; //set longer delay so it wont stand up right away + return; + } + } + //if drinking, have to fake mana regen because charmed NPCs + //do not regen mana + if(m_creature->HasAura(1137)) + { + uint32 addvalue = 0; + uint32 maxValue = m_creature->GetMaxPower(POWER_MANA); + uint32 curValue = m_creature->GetPower(POWER_MANA); + + if(curValue <= maxValue) + { + addvalue = maxValue/20; + m_creature->ModifyPower(POWER_MANA, addvalue); + //return; + } + } + if(m_creature->HasAura(1137) && m_creature->GetPower(POWER_MANA) >= m_creature->GetMaxPower(POWER_MANA)) + m_creature->RemoveAurasDueToSpell(1137); + + //eat + if(m_creature->GetHealth()*100 / m_creature->GetMaxHealth() < 80 && !m_creature->HasAura(10256) && master->GetBotMustWaitForSpell3() <= 0 && !m_creature->isInCombat()) + { + master->SetBotMustWaitForSpell3(1000); + m_creature->CastSpell(m_creature, 10256, true); + m_creature->SetStandState(1); +// m_creatureTimer = 5000; //set longer delay so it wont stand up right away + return; + } + + //if eating, have to fake regen because charmed NPCs + //do not regen + if(m_creature->HasAura(10256)) + { + uint32 addvalue = 0; + uint32 maxValue = m_creature->GetMaxHealth(); + uint32 curValue = m_creature->GetHealth(); + + if(curValue <= maxValue) + { + addvalue = maxValue/20; + m_creature->SetHealth(curValue + addvalue); + //return; + } + } + + if(m_creature->GetHealth() == m_creature->GetMaxHealth() && m_creature->HasAura(10256)) + m_creature->RemoveAurasDueToSpell(10256); +} // end Feast + +void bot_ai::setStats(uint32 myclass, uint32 myrace, uint32 mylevel) { + if (myrace==0) return; + + //sLog->outError ("bot_ai::setStats"); + //DATABASE STATS + PlayerLevelInfo info; + master->GetBotLevelInfo(myrace,myclass,mylevel,&info); + + PlayerClassLevelInfo classInfo; + // master->GetBotClassLevelInfo(m_creature->getClass(),m_creature->getLevel(),&classInfo); + + m_creature->SetLevel(mylevel); + UnitMods unitMod = UNIT_MOD_ATTACK_POWER; + + + //player -> m_creature->UpdateAllStats + for (int i = STAT_STRENGTH; i < MAX_STATS; i++) + { + //float value = m_creature->GetTotalStatValue(Stats(i)); + //m_creature->SetStat(Stats(i), (int32)value); + m_creature->SetStat(Stats(i), info.stats[i]); + } + + m_creature->UpdateAttackPowerAndDamage(); + float val2 = 0.0f; + float level = float(mylevel); + + + uint16 index = UNIT_FIELD_ATTACK_POWER; + uint16 index_mod = UNIT_FIELD_ATTACK_POWER_MODS; + uint16 index_mult = UNIT_FIELD_ATTACK_POWER_MULTIPLIER; + + float mLevelMult = 0.0; + if (level>=40) mLevelMult = 1.5; + if (level>=50) mLevelMult = 2.0; + if (level>=60) mLevelMult = 3.0; + if (level>=70) mLevelMult = 4.0; + if (level>=80) mLevelMult = 5.0; + + switch(myclass) + { + case CLASS_WARRIOR: val2 = level * mLevelMult*4.0f + m_creature->GetStat(STAT_STRENGTH)*2.0f ; break; + case CLASS_DEATH_KNIGHT: val2 = level * mLevelMult*4.0f + m_creature->GetStat(STAT_STRENGTH)*2.0f ; break; + case CLASS_PALADIN: val2 = level * mLevelMult*3.0f + m_creature->GetStat(STAT_STRENGTH)*2.0f - 20.0f; break; + case CLASS_ROGUE: val2 = level * mLevelMult*6.0f + m_creature->GetStat(STAT_STRENGTH) + m_creature->GetStat(STAT_AGILITY); break; + case CLASS_HUNTER: val2 = level * mLevelMult*2.0f + m_creature->GetStat(STAT_STRENGTH) + m_creature->GetStat(STAT_AGILITY) - 20.0f; break; + case CLASS_SHAMAN: val2 = level * mLevelMult*2.0f + m_creature->GetStat(STAT_STRENGTH)*2.0f - 20.0f; break; + case CLASS_DRUID: val2 = m_creature->GetStat(STAT_STRENGTH)*2.0f - 20.0f; break; + case CLASS_MAGE: val2 = m_creature->GetStat(STAT_STRENGTH) - 10.0f; break; + case CLASS_PRIEST: val2 = m_creature->GetStat(STAT_STRENGTH) - 10.0f; break; + case CLASS_WARLOCK: val2 = m_creature->GetStat(STAT_STRENGTH) - 10.0f; break; + } + +//sLog->outError ("val2 = %f", val2); +//sLog->outError ("str = %f", m_creature->GetStat(STAT_STRENGTH)); + //CreatureInfo const *cinfo = sObjectMgr->GetCreatureTemplate(m_creature->GetEntry()); + //m_creature->SetBaseWeaponDamage(BASE_ATTACK, MAXDAMAGE, cinfo->maxdmg/mylevel); + + //Custom weapon system + val2 = abs(val2 + (val2 * (level/80))); + // sLog->outError ("custom val2 = %f", val2); + m_creature->SetModifierValue(unitMod, BASE_VALUE, val2); + float base_attPower = m_creature->GetModifierValue(unitMod, BASE_VALUE) * m_creature->GetModifierValue(unitMod, BASE_PCT) * mLevelMult; + + float attPowerMod = m_creature->GetModifierValue(unitMod, TOTAL_VALUE); + + float attPowerMultiplier = m_creature->GetModifierValue(unitMod, TOTAL_PCT) - 1.0f; + attPowerMod = attPowerMod ? attPowerMod : 1; + attPowerMultiplier = attPowerMultiplier ? attPowerMultiplier : 0.1f; + + m_creature->SetUInt32Value(UNIT_FIELD_ATTACK_POWER, (uint32)base_attPower); //UNIT_FIELD_(RANGED)_ATTACK_POWER field + m_creature->SetUInt32Value(UNIT_FIELD_ATTACK_POWER_MODS, (uint32)attPowerMod); //UNIT_FIELD_(RANGED)_ATTACK_POWER_MODS field + m_creature->SetFloatValue(UNIT_FIELD_ATTACK_POWER_MULTIPLIER, attPowerMultiplier); //UNIT_FIELD_(RANGED)_ATTACK_POWER_MULTIPLIER field + + m_creature->UpdateDamagePhysical(BASE_ATTACK); + + //sLog->outError ("udpating BASE ATTACK"); + //sLog->outError ("mLevelMult = %f", mLevelMult); + //sLog->outError ("bot_ai - base_attPower = %f", base_attPower); + //sLog->outError ("actpower multiplier = %f", attPowerMultiplier); + //sLog->outError ("actpower val2 = %f", val2); + //sLog->outError ("\tmin damage = %f", uint32(val2 * attPowerMultiplier)+level); + //sLog->outError ("\tmax damange = %f",uint32(val2 * attPowerMultiplier)*2+level); + //sLog->outError ("\tmax damange 2 = %f",uint32(val2 * attPowerMultiplier)*mLevelMult); + m_creature->SetBaseWeaponDamage(BASE_ATTACK, MINDAMAGE, uint32(val2 * attPowerMultiplier)+level); + m_creature->SetBaseWeaponDamage(BASE_ATTACK, MAXDAMAGE, uint32(val2 * attPowerMultiplier)*2+level); + + m_creature->UpdateDamagePhysical(BASE_ATTACK); + m_creature->SetFloatValue(UNIT_FIELD_MINRANGEDDAMAGE,uint32(val2 * attPowerMultiplier)+level); + m_creature->SetFloatValue(UNIT_FIELD_MAXRANGEDDAMAGE,uint32(val2 * attPowerMultiplier)*2+level); +//sLog->outError ("\t%s - min damage after setting it = %f", m_creature->GetName(), m_creature->GetFloatValue(UNIT_FIELD_MINDAMAGE)); +//sLog->outError ("\t%s - MINDAMAGE after setting it = %f", m_creature->GetName(), m_creature->GetWeaponDamageRange(BASE_ATTACK, MINDAMAGE)); + + + m_creature->SetModifierValue(UNIT_MOD_ATTACK_POWER, BASE_VALUE, uint32(val2 * attPowerMultiplier)); + //player -> m_creature->UpdateAttackPowerAndDamage(true); // ---> STUPID CREATURE CAN NOT BE RANGED + + + float value = 0.0f; + unitMod = UNIT_MOD_ARMOR; + + value = m_creature->GetModifierValue(unitMod, BASE_VALUE); // base armor (from items) + value *= m_creature->GetModifierValue(unitMod, BASE_PCT); // armor percent from items + value += m_creature->GetStat(STAT_AGILITY) * 2.0f; // armor bonus from stats + value += m_creature->GetModifierValue(unitMod, TOTAL_VALUE); + + value *= m_creature->GetModifierValue(unitMod, TOTAL_PCT); + m_creature->UpdateAttackPowerAndDamage(); + + //Custom armor system + value = value + (value * (level/100)); + + // m_creature->SetArmor(int32(value)); + m_creature->SetModifierValue(UNIT_MOD_ARMOR, BASE_VALUE, value); + //m_creature->UpdateArmor(); + +} + +void bot_ai::ReceiveEmote(Player *player, uint32 emote) +{ + if (emote == TEXTEMOTE_DANCE) + DoSay("I love to dance!",LANG_UNIVERSAL,m_creature->GetGUID(), player); + + // debug to see what auras are on bot + if (emote == TEXTEMOTE_BONK) + listAuras(m_creature); + + // debug to see what auras are on me + if (emote == TEXTEMOTE_SALUTE) + listAuras(player); + + if (emote == TEXTEMOTE_STAND) + { + if (m_creature->GetCharmerGUID() != player->GetGUID()) + { + m_creature->HandleEmoteCommand(TEXTEMOTE_RUDE); + return; + } + player->SetBotCommandState (COMMAND_STAY); + DoSay("Standing Still.",LANG_UNIVERSAL,m_creature->GetGUID(), player); + } + + if (emote == TEXTEMOTE_WAVE) + { + if (m_creature->GetCharmerGUID() != player->GetGUID()) + { + m_creature->HandleEmoteCommand(TEXTEMOTE_RUDE); + return; + } + player->SetBotCommandState (COMMAND_FOLLOW); + DoSay("Following!",LANG_UNIVERSAL,m_creature->GetGUID(), player); + } + + // buff the requester + if (emote == TEXTEMOTE_BOW) + { + ReceiveBowEmote(player); + } + +} // end ReceiveEmote + +void bot_ai::ReceiveBowEmote(Player *player) +{ + // DoSay("I have no buffs for you!",LANG_UNIVERSAL,m_creature->GetGUID(), player); +} + +void bot_ai::DoSay(const std::string& text, const uint32 language,uint64 receiver,Player *player) +{ + +} diff --git a/src/server/scripts/Bots/bot_ai.h b/src/server/scripts/Bots/bot_ai.h new file mode 100644 index 0000000..35a7e07 --- /dev/null +++ b/src/server/scripts/Bots/bot_ai.h @@ -0,0 +1,88 @@ +#ifndef _BOT_AI_H +#define _BOT_AI_H + +#define SPELL_LEVEL ((int)m_creature->getLevel()/10) + +#define BANDAGE 27031 +#define MANAPOTION 28499 +#define REJUVEPOTION 28517 +#define HEALINGPOTION 28495 +#define POTIONCD 60000 +#define FIRSTAID 60000 +#define DRINK 1137 + +#define master ((Player*)m_creature->GetCharmer()) + +typedef std::set<Unit *> AttackerSet; + +class bot_ai : public ScriptedAI +{ + public: + bot_ai(Creature *c); + + virtual ~bot_ai(); + + //Cure the target + virtual bool CureTarget (Unit *target); + + //Heal the target + virtual bool HealTarget (Unit *target, uint8 hp); + + // Buff target + virtual void BuffTarget(Unit *target); + + virtual void ReceiveBowEmote(Player *player); + + + // Cycles through the group to heal/buff/rezz + void BuffAndHealGroup(Player *gPlayer); + void RezGroup(uint32 REZZ, Player *gPlayer); + + //Debug method to list the auras currently active. + //Use to find what spells were casted + bool listAuras(Unit *unit); + + //More generalized method than HasAura(). It looks for + //any rank of the spell and it doesn't care which + //spell effect you want. If it has the spell aura than + //it returns true + bool HasAuraName(Unit *unit, std::string spell, uint64 casterGuid=0); + bool HasAuraName(Unit *unit, uint32 spellId, uint64 casterGuid=0); + bool HasAuraIcon (Unit *unit, uint32 SpellIconID, uint64 casterGuid=0); + + std::string GetSpellName(uint32 spell_id); + + void doCast(Unit *victim, uint32 spellId, bool triggered = false); + bool isTimerReady(int32 timer); + + void ResetOrGetNextTarget(); + + void ReceiveEmote(Player *player, uint32 emote); + + void DoSay(const std::string& text, const uint32 language,uint64 receiver,Player *player); + + bool IAmDead(); + + void GiveManaBack(uint8 amount=25); + + void CureGroup(Unit *pTarget); + + void Feast(); + + void BotAttackStart(Unit *victim); + + Creature *m_creature; + + int32 GC_Timer; //global cooldown + uint32 FirstAid_cd; + uint32 Potion_cd; + + private: + bool gettingAttacked(AttackerSet m_attackers); + + CommandStates prevCommandState; + + protected: + void setStats(uint32 myclass, uint32 myrace, uint32 mylevel); +}; +#endif diff --git a/src/server/scripts/Bots/bot_druid_ai.cpp b/src/server/scripts/Bots/bot_druid_ai.cpp new file mode 100644 index 0000000..0f65931 --- /dev/null +++ b/src/server/scripts/Bots/bot_druid_ai.cpp @@ -0,0 +1,563 @@ +#include "ScriptPCH.h" +#include "bot_druid_ai.h" +#include "Group.h" + +class druid_bot : public CreatureScript +{ +public: + druid_bot() : CreatureScript("druid_bot") { } + + CreatureAI *GetAI(Creature *pCreature) const + { + return new bot_druid_ai(pCreature); + } + + struct bot_druid_ai : public bot_ai + { + bot_druid_ai(Creature *c) : bot_ai(c) + { + Reset(); + } + + int32 GC_Timer; //global cooldown + int32 Heal_Timer; + int32 Regrowth_Timer; + int32 Self_Regrowth_Timer; + int32 Self_Rejuvenation_Timer; + int32 Self_Heal_Timer; + int32 Rejuvenation_Timer; + int32 Others_Heal_Timer; + int32 Oom_timer; + int32 Fade_Timer; + int32 Potion_Timer; + int32 Warstomp_Timer; + int32 Rez_Timer; + + int32 Noggenfogger_Timer; + + //Bear Timers + int32 Demoralizing_Roar_Timer; + int32 Swipe_Timer; + + //Cat Timers + int32 Claw_Timer; + int32 Rake_Timer; + int32 Shred_Timer; + int32 Rip_Timer; + int32 Mangle_Cat_Timer; + + //Balance Timers + int32 Moonfire_Timer; + int32 Starfire_Timer; + int32 Wrath_Timer; + int32 Fairie_Fire_Timer; + + Unit *mobsTarget; + Unit *opponent; + + void Reset() + { + GC_Timer = 0; + Potion_Timer = 0; + Heal_Timer = 0; + Regrowth_Timer = 0; + Self_Regrowth_Timer = 0; + Self_Rejuvenation_Timer = 0; + Self_Heal_Timer = 0; + Rejuvenation_Timer = 0; + Rez_Timer = 0; + + Others_Heal_Timer = 0; + Oom_timer = 0; + + Warstomp_Timer = 0; + Demoralizing_Roar_Timer = 10; + Swipe_Timer = 20; + Noggenfogger_Timer = 0; + + Claw_Timer = 0; + Rake_Timer = 0; + Shred_Timer = 0; + Rip_Timer = 0; + Mangle_Cat_Timer = 0; + + Moonfire_Timer = 30; + Starfire_Timer = 90; + Wrath_Timer = 150; + Fairie_Fire_Timer = 10; + + opponent = NULL; + + if (master) { + setStats(CLASS_DRUID, m_creature->getRace(), master->getLevel()); + } + } + + void Aggro(Unit *who){} + + void EnterEvadeMode(){} + + void KilledUnit(Unit *){ master->SetBotCommandState(COMMAND_FOLLOW); } + + //try to do a warstomp every time I get out of animal form + void warstomp(const uint32 diff) + { + if(master->GetBotRace() != RACE_TAUREN) return; + + if(Warstomp_Timer <= 0) + { + if(opponent != NULL) + { + Warstomp_Timer = 1200; //2 minutes + doCast(opponent, SPELL_WARSTOMP, true); + } + } + else if(Warstomp_Timer >= 0) + --Warstomp_Timer; + } //end warstomp + + void removeFeralForm(Player *m=0) + { + if(!m) m=master; //if m is set than use it, else use master + + if(m_creature->HasAura(SPELL_BEAR_FORM, 0)) + { + m_creature->RemoveAurasDueToSpell(SPELL_BEAR_FORM); + m_creature->RemoveAurasDueToSpell(SPELL_BEAR_FORM_MOD); + m->SetBotMustWaitForSpell1(3000); + } + + if(m_creature->HasAura(SPELL_CAT_FORM, 0)) + { + m_creature->RemoveAurasDueToSpell(SPELL_CAT_FORM); + m_creature->RemoveAurasDueToSpell(SPELL_CAT_FORM_MOD); + m->SetBotMustWaitForSpell1(3000); + } + + m_creature->setPowerType(POWER_MANA); + + } //end removeFeralForm + + bool isTimerReady(int32 timer) + { + if(timer <= 0 && GC_Timer <= 0) return true; + else return false; + } + + void decrementTimers() + { + if(GC_Timer >= 0) --GC_Timer; + if(Demoralizing_Roar_Timer >= 0) --Demoralizing_Roar_Timer; + if(Swipe_Timer >= 0) --Swipe_Timer; + if(Claw_Timer >= 0) --Claw_Timer; + if(Rake_Timer >= 0) --Rake_Timer; + if(Shred_Timer >= 0) --Shred_Timer; + if(Rip_Timer >= 0) --Rip_Timer; + if(Mangle_Cat_Timer >= 0) --Mangle_Cat_Timer; + if(Moonfire_Timer >= 0) --Moonfire_Timer; + if(Starfire_Timer >= 0) --Starfire_Timer; + if(Wrath_Timer >= 0) --Wrath_Timer; + if(Rip_Timer >= 0) --Fairie_Fire_Timer; + if(Potion_Timer >= 0) --Potion_Timer; + if(Rejuvenation_Timer >= 0) --Rejuvenation_Timer; + if(Regrowth_Timer >= 0) --Regrowth_Timer; + if(Heal_Timer > 0) --Heal_Timer; + if(Self_Regrowth_Timer >= 0) --Self_Regrowth_Timer; + if(Self_Rejuvenation_Timer >= 0) --Self_Rejuvenation_Timer; + if(Self_Heal_Timer >= 0) --Self_Heal_Timer; + if(Others_Heal_Timer >= 0) --Others_Heal_Timer; + if(Rez_Timer >= 0) --Rez_Timer; + if(Noggenfogger_Timer >= 0) --Noggenfogger_Timer; + + } + + void doCast(Unit *victim, uint32 spellId, bool triggered = false) + { + if(spellId == 0) return; + + GC_Timer = 20; + m_creature->SetStandState(UNIT_STAND_STATE_STAND); + DoCast(victim, spellId, triggered); + } //end doCast + + void doBearActions(const uint32 diff) + { + m_creature->setPowerType(POWER_RAGE); + + if(isTimerReady(Demoralizing_Roar_Timer) && opponent != NULL) + { + Demoralizing_Roar_Timer = 150; + doCast(opponent, SPELL_DEMORALIZING_ROAR, true); + } + + if(isTimerReady(Swipe_Timer) && opponent != NULL) + { + Swipe_Timer = 50; + doCast(opponent, SPELL_SWIPE, true); + } + }//end doBearActions + + + + void doCatActions(/*Player *master, Creature *m_creature,*/ const uint32 diff) + { + + m_creature->SetPower(POWER_ENERGY, 100); + + if(isTimerReady(Claw_Timer) && opponent != NULL) + { + Claw_Timer = 70; + doCast(opponent, SPELL_CLAW, true); + } + + if(isTimerReady(Mangle_Cat_Timer) && opponent != NULL) + { + Mangle_Cat_Timer = 70; + doCast(opponent, SPELL_MANGLE_CAT, true); + } + + if(isTimerReady(Rake_Timer) && opponent != NULL) + { + Rake_Timer = 100; + doCast(opponent, SPELL_RAKE, true); + } + + if(isTimerReady(Shred_Timer) && opponent != NULL) + { + Shred_Timer = 120; + doCast(opponent, SPELL_SHRED, true); + } + + if(isTimerReady(Rip_Timer) && opponent != NULL) + { + Rip_Timer = 150; + doCast(opponent, SPELL_RIP, true); + } + } //end doCatActions + + + void doBalanceActions(const uint32 diff) + { + removeFeralForm(); + m_creature->setPowerType (POWER_MANA); + + if(isTimerReady(Moonfire_Timer) && opponent != NULL) + { + Moonfire_Timer = 150; + doCast(opponent, SPELL_MOONFIRE, true); + } + + if(isTimerReady(Starfire_Timer) && opponent != NULL) + { + Starfire_Timer = 200; + doCast(opponent, SPELL_STARFIRE, true); + } + + if(isTimerReady(Wrath_Timer) && opponent != NULL) + { + Wrath_Timer = 180; + doCast(opponent, SPELL_WRATH, true); + } + + if(isTimerReady(Fairie_Fire_Timer) && opponent != NULL) + { + Fairie_Fire_Timer = 200; + doCast(opponent, SPELL_FAIRIE_FIRE, true); + } + } //end doBalanceActions + + + bool CureTarget(Unit *target) + { + if (HasAuraIcon(target, 68 /*"Venom Spit"*/) + //|| HasAuraName(target, "Venom Spit") + //|| HasAuraName(target, "Poison")) + && !HasAuraName(target, SPELL_CURE_POISON)) + { + removeFeralForm(); + doCast(target, SPELL_CURE_POISON); + } + return true; + } + + void UpdateAI(const uint32 diff) + { + decrementTimers(); + + if(IAmDead()) return; + + if(m_creature->HasUnitState(UNIT_STAT_CASTING)) + return; + + //self buff + if(!m_creature->isInCombat()) + { + if(!m_creature->HasAura(SPELL_THORNS)) + { + removeFeralForm(); + doCast(m_creature, SPELL_THORNS, true); + } + } + + if(m_creature->GetPower(POWER_MANA) < 400 && isTimerReady(Potion_Timer)) + { + doCast(m_creature, REJUVEPOTION, true); + //m_creature->MonsterSay("MANA POTION", LANG_UNIVERSAL, NULL); + Potion_Timer = 150; + Oom_timer = 0; + } + if(m_creature->GetPower(POWER_MANA)/m_creature->GetMaxPower(POWER_MANA) < 10) + { + if(Oom_timer == 0) + { + //m_creature->MonsterSay("OOM", LANG_UNIVERSAL, NULL); + Oom_timer = 1; + } + } + + //Heal master + if((master->GetHealth()*100 / master->GetMaxHealth() < 90) && isTimerReady(Rejuvenation_Timer) && master->isAlive()) + { + removeFeralForm(); + warstomp(diff); + doCast(master, SPELL_REJUVENATION, true); + Rejuvenation_Timer = 300; + Heal_Timer = Heal_Timer + 30; //wait 3 seconds before casting a real heal + Regrowth_Timer = Regrowth_Timer + 20; //wait 2 seconds before casting a regrowth + //if(master->isInCombat() && master->getVictim() == NULL) return; + return; + } + + if((master->GetHealth()*100 / master->GetMaxHealth() < 90) && isTimerReady(Regrowth_Timer) && master->isAlive()) + { + removeFeralForm(); + warstomp(diff); + doCast(master, SPELL_REGROWTH, true); + Regrowth_Timer = 200; + Heal_Timer = Heal_Timer + 20; //wait 2 seconds before casting a real heal + //if(master->isInCombat() && master->getVictim() == NULL) return; + return; + } + + if((master->GetHealth()*100 / master->GetMaxHealth() < 75) && isTimerReady(Heal_Timer) && master->isAlive() + /* && CanCast(master, GetSpellStore()->LookupEntry(SPELL_HEALING_TOUCH))*/) + { + removeFeralForm(); + warstomp(diff); + doCast(master, SPELL_HEALING_TOUCH, false); + Heal_Timer = 100; + return; + } + + // + //Heal myself + // + if(m_creature->GetHealth()*100 / m_creature->GetMaxHealth() < 80 && !m_creature->HasAura(SPELL_REGROWTH, 1) && isTimerReady(Self_Regrowth_Timer)) + { + removeFeralForm(); + warstomp(diff); + doCast(m_creature, SPELL_REGROWTH, false); + + Self_Regrowth_Timer = 120; + return; + } + if(m_creature->GetHealth()*100 / m_creature->GetMaxHealth() < 80 && !m_creature->HasAura(SPELL_REJUVENATION, 0) && isTimerReady(Self_Rejuvenation_Timer) + /* && CanCast(m_creature, GetSpellStore()->LookupEntry(SPELL_REGROWTH))*/) + { + removeFeralForm(); + warstomp(diff); + doCast(m_creature, SPELL_REJUVENATION, false); + + Self_Rejuvenation_Timer = 120; + return; + } + if((m_creature->GetHealth()*100 / m_creature->GetMaxHealth() < 75) && isTimerReady(Self_Heal_Timer) + /* && CanCast(master, GetSpellStore()->LookupEntry(SPELL_HEALING_TOUCH))*/) + { + removeFeralForm(); + warstomp(diff); + + doCast(m_creature, SPELL_HEALING_TOUCH, false); + Self_Heal_Timer = 100; + return; + } + + + // + //Heal others + // + //check group members, this doesn't check bots/pets. They will be done later. Preference + //goes to real players first. + // + Group::MemberSlotList const &a =((Player*)master)->GetGroup()->GetMemberSlots(); + for(Group::member_citerator itr = a.begin(); itr != a.end(); itr++) + { + Player *tPlayer = ((Player *)master)->GetObjPlayer(itr->guid); + if(tPlayer == NULL) continue; + + //healing others + if(tPlayer->isAlive() && isTimerReady(Others_Heal_Timer) && tPlayer->GetHealth()*100 / tPlayer->GetMaxHealth() < 75) + { + //sLog->outError("Druid_Bot: healing someoneelse: %s it has %d HP", tPlayer->GetName(), tPlayer->GetHealth()); + doCast(tPlayer, SPELL_HEALING_TOUCH, false); + Others_Heal_Timer = 250; + } + + //rezzes + if(tPlayer->isDead() && m_creature->getLevel() >= 20 && + //CanCast(tPlayer, GetSpellStore()->LookupEntry(SPELL_REBIRTH)) && + m_creature->GetDistance(tPlayer) < 40 && (isTimerReady(Rez_Timer))) + { + char *str = (char *)malloc(32); + sprintf(str, "Rezzing %s", tPlayer->GetName()); + m_creature->MonsterSay(str, LANG_UNIVERSAL, NULL); + free(str); + doCast(tPlayer, SPELL_REBIRTH, false); + Rez_Timer = 1600; + } + + //buff group + if(tPlayer->isAlive()) + { + if(!HasAuraName(tPlayer, GetSpellName(SPELL_MARK_OF_THE_WILD)) && !HasAuraName(tPlayer, "Gift of the Wild") && isTimerReady(GC_Timer)) + { + removeFeralForm(); + doCast(tPlayer, SPELL_MARK_OF_THE_WILD); + } + + if(!HasAuraName(tPlayer, SPELL_THORNS) && isTimerReady(GC_Timer)) + { + removeFeralForm(); + doCast(tPlayer, SPELL_THORNS); + } + } + } + + if(isTimerReady(Noggenfogger_Timer)) + { + uint64 m_rand = urand(1, 2); + switch(m_rand) + { + case 1: + doCast(m_creature, SPELL_NOGGENFOGGER_SKELETON, true); + break; + case 2: + // Don't change forms b/c it crashes in GetModelForForm(). It checks + // PLAYER_BYTES and since its an npc it does not have this value. + //doCast(m_creature, SPELL_NOGGENFOGGER_SMALL, true); + break; + } + Noggenfogger_Timer = 6000; //10 minutes + } + + //The rest is combat stuff, so if not in combat just return + opponent = SelectUnit(SELECT_TARGET_TOPAGGRO, 0); + if(!opponent && !m_creature->getVictim()) + { + CureGroup(master); + ResetOrGetNextTarget(); + return; + } + + //default value + float val2 = m_creature->GetStat(STAT_STRENGTH)*2.0f; + + //only go Feral if we are "small" from the noggenfogger spell + if(m_creature->HasAura(SPELL_NOGGENFOGGER_SMALL)) + { + //if the target is attacking us, we want to go bear + if(opponent && opponent->getVictim() && + opponent->getVictim()->GetGUID() == m_creature->GetGUID()) + { + //if we don't have bear yet + if(!m_creature->HasAura(SPELL_BEAR_FORM)) + { + m_creature->RemoveAurasDueToSpell(SPELL_CAT_FORM); //remove cat + m_creature->RemoveAurasDueToSpell(SPELL_CAT_FORM_MOD); + doCast(m_creature, SPELL_BEAR_FORM, true); + doCast(m_creature, SPELL_BEAR_FORM_MOD, true); + master->SetBotMustWaitForSpell1(3000); + m_creature->setPowerType(POWER_RAGE); + m_creature->SetMaxPower(POWER_RAGE, 1000); + m_creature->SetPower(POWER_RAGE, 1000); + val2 = m_creature->getLevel()*3.0f + m_creature->GetStat(STAT_STRENGTH)*2.0f; + + //update attack power based on form + val2 = val2 + (val2 * (m_creature->getLevel()/50)); + m_creature->SetModifierValue(UNIT_MOD_ATTACK_POWER, BASE_VALUE, val2); + m_creature->UpdateDamagePhysical(BASE_ATTACK); + } + doBearActions(diff); + } else { + //if we don't have cat yet + if(!m_creature->HasAura(SPELL_CAT_FORM)) + { + m_creature->RemoveAurasDueToSpell(SPELL_BEAR_FORM); //remove bear + m_creature->RemoveAurasDueToSpell(SPELL_BEAR_FORM_MOD); + doCast(m_creature, SPELL_CAT_FORM, true); + doCast(m_creature, SPELL_CAT_FORM_MOD, true); + master->SetBotMustWaitForSpell1(3000); + m_creature->setPowerType(POWER_ENERGY); + m_creature->SetMaxPower(POWER_ENERGY, 1000); + m_creature->SetPower(POWER_ENERGY, 1000); + m_creature->SetSpeed(MOVE_RUN, m_creature->GetSpeed(MOVE_RUN) - 0.1f, true); + val2 = m_creature->getLevel()*5.0f + m_creature->GetStat(STAT_STRENGTH)*2.0f + m_creature->GetStat(STAT_AGILITY) - 20.0f; + + //update attack power based on form + val2 = val2 + (val2 * (m_creature->getLevel()/50)); + m_creature->SetModifierValue(UNIT_MOD_ATTACK_POWER, BASE_VALUE, val2); + m_creature->UpdateDamagePhysical(BASE_ATTACK); + } + doCatActions(diff); + } + //else go Balance if we are a skeleton from noggenfogger + } else { + val2 = m_creature->GetStat(STAT_STRENGTH)*2.0f; + val2 = val2 + (val2 * (m_creature->getLevel()/50)); + m_creature->SetModifierValue(UNIT_MOD_ATTACK_POWER, BASE_VALUE, val2); + m_creature->UpdateDamagePhysical(BASE_ATTACK); + doBalanceActions(diff); + } + + + + //now try to heal bots and pets. DoSelectLowestHpFriendly will get + //everyone in group including bots and pets. Unfortunately it can + //not be triggered for % of lost HP, so we just set it to -1000. + //This means low level players wont be healed because they wont have + //enough HP. + Unit *target = DoSelectLowestHpFriendly(40, 1000); + if(target && isTimerReady(Others_Heal_Timer)) + { + doCast(target, SPELL_HEALING_TOUCH, false); + Others_Heal_Timer = 50; + } else { + target = DoSelectLowestHpFriendly(40, 500); //now try someone with less HP lost + if(target && isTimerReady(Others_Heal_Timer)) + { + if(!target->HasAura(SPELL_REGROWTH, 1)) + { + doCast(target, SPELL_REGROWTH, false); + Others_Heal_Timer = 100; + } + } + } + + ScriptedAI::UpdateAI(diff); + } + void ReceiveBowEmote(Player *player) + { + ((bot_druid_ai*)m_creature->AI())->removeFeralForm((Player*) m_creature->GetCharmer()); + ((bot_druid_ai*)m_creature->AI())->doCast(player, SPELL_THORNS, true ); + ((bot_druid_ai*)m_creature->AI())->doCast(player, SPELL_MARK_OF_THE_WILD, false ); + } + + }; //end druid_bot + +}; + + +void AddSC_druid_bot() +{ + new druid_bot(); +} diff --git a/src/server/scripts/Bots/bot_druid_ai.h b/src/server/scripts/Bots/bot_druid_ai.h new file mode 100644 index 0000000..d63ea7b --- /dev/null +++ b/src/server/scripts/Bots/bot_druid_ai.h @@ -0,0 +1,63 @@ + +#include "bot_ai.h" + + +#define SPELL_MARK_OF_THE_WILD SPELL_MARK_OF_THE_WILD_A[SPELL_LEVEL] +#define SPELL_THORNS SPELL_THORNS_A[SPELL_LEVEL] +#define SPELL_HEALING_TOUCH SPELL_HEALING_TOUCH_A[SPELL_LEVEL] +#define SPELL_REGROWTH SPELL_REGROWTH_A[SPELL_LEVEL] +#define SPELL_REJUVENATION SPELL_REJUVENATION_A[SPELL_LEVEL] +#define SPELL_REBIRTH SPELL_REBIRTH_A[SPELL_LEVEL] +#define SPELL_CURE_POISON SPELL_CURE_POISON_A[SPELL_LEVEL] + + +//0#define SPELL_RESURRECTION 10881 //rank 4 +#define SPELL_WARSTOMP 20549 //racial ability + +//FERAL SPELLS +#define SPELL_CAT_FORM 768 +#define SPELL_CAT_FORM_MOD 3025 +#define SPELL_BEAR_FORM 9634 +#define SPELL_BEAR_FORM_MOD 1178 + +#define SPELL_DEMORALIZING_ROAR SPELL_DEMORALIZING_ROAR_A[SPELL_LEVEL] +#define SPELL_SWIPE SPELL_SWIPE_A[SPELL_LEVEL] + +#define SPELL_CLAW SPELL_CLAW_A[SPELL_LEVEL] +#define SPELL_RAKE SPELL_RAKE_A[SPELL_LEVEL] +#define SPELL_SHRED SPELL_SHRED_A[SPELL_LEVEL] +#define SPELL_RIP SPELL_RIP_A[SPELL_LEVEL] +#define SPELL_MANGLE_CAT SPELL_MANGLE_CAT_A[SPELL_LEVEL] + +//BALANCE SPELLS +#define SPELL_MOONFIRE SPELL_MOONFIRE_A[SPELL_LEVEL] +#define SPELL_STARFIRE SPELL_STARFIRE_A[SPELL_LEVEL] +#define SPELL_WRATH SPELL_WRATH_A[SPELL_LEVEL] +#define SPELL_FAIRIE_FIRE SPELL_FAIRIE_FIRE_A[SPELL_LEVEL] + +#define SPELL_NOGGENFOGGER_SMALL 16595 +#define SPELL_NOGGENFOGGER_SKELETON 16591 + + +uint32 SPELL_MARK_OF_THE_WILD_A[] = { 1126, 5232, 6756, 5234, 8907, 21849, 21850, 26991, 48470 }; +uint32 SPELL_THORNS_A[] = { 467, 782, 1075, 8914, 9756, 9910, 26992, 26992, 53307, 53307 }; +uint32 SPELL_HEALING_TOUCH_A[] = { 5185, 5187, 5189, 6778, 9758, 9889, 26978, 26979, 26979 }; +uint32 SPELL_REGROWTH_A[] = { 0, 8936, 8939, 8941, 9750, 9857, 9858, 26980, 96980, 48442, 48442 }; +uint32 SPELL_REJUVENATION_A[] = { 774, 1058, 2090, 2091, 3627, 9840, 26981, 26982, 48440, 48440 }; +uint32 SPELL_REBIRTH_A[] = { 0, 0, 2006, 2010, 10880, 10881, 20770, 25435, 25435 }; +uint32 SPELL_CURE_POISON_A[] = { 0, 8946, 8946, 14253, 14253, 14253, 14253, 14253, 14253 }; + + +uint32 SPELL_DEMORALIZING_ROAR_A[] = { 0, 99, 1735, 9490, 9747, 9898, 26998, 26998, 48559, 48560, 48560 }; +uint32 SPELL_SWIPE_A[] = { 0, 779, 780, 769, 9754, 9908, 26997, 26997, 26997 }; + +uint32 SPELL_CLAW_A[] = { 0, 0, 1082, 3029, 5201, 9849, 9850, 27000, 48569, 48569 }; +uint32 SPELL_RAKE_A[] = { 0, 0, 1822, 1823, 1824, 9904, 27003, 27003, 48574, 48574 }; +uint32 SPELL_SHRED_A[] = { 0, 0, 5221, 6800, 8992, 9829, 9830, 27001, 27002, 27002 }; +uint32 SPELL_RIP_A[] = { 0, 0, 1079, 9492, 9493, 9752, 9894, 9896, 27008, 27008 }; +uint32 SPELL_MANGLE_CAT_A[] = { 0, 0, 0, 0, 0, 33982, 33983, 48565, 48566, 48566 }; + +uint32 SPELL_MOONFIRE_A[] = { 8921, 8924, 8925, 8928, 8929, 9834, 26987, 26988, 26988 }; +uint32 SPELL_STARFIRE_A[] = { 0, 0, 2912, 8950, 8951, 9875, 25298, 26986, 26986 }; +uint32 SPELL_WRATH_A[] = { 5176, 5178, 5179, 5180, 8905, 9912, 26984, 26985, 48461, 48461 }; +uint32 SPELL_FAIRIE_FIRE_A[] = { 0, 16857, 16857, 17390, 17391, 17392, 27011, 27011, 48475, 48475 }; diff --git a/src/server/scripts/Bots/bot_hunter_ai.cpp b/src/server/scripts/Bots/bot_hunter_ai.cpp new file mode 100644 index 0000000..6f98d9e --- /dev/null +++ b/src/server/scripts/Bots/bot_hunter_ai.cpp @@ -0,0 +1,303 @@ +#include "ScriptPCH.h" +#include "bot_hunter_ai.h" + +class hunter_bot : public CreatureScript +{ +public: + hunter_bot() : CreatureScript("hunter_bot") { } + + CreatureAI *GetAI(Creature *pCreature) const + { + return new hunter_botAI(pCreature); + } + + struct hunter_botAI : public bot_ai + { + hunter_botAI(Creature *c) :bot_ai(c) + { + Reset(); + pet = NULL; + } + + bool oom_spam; + + Unit *opponent; + Creature *pet; + + int32 ArcaneShot_cd; + int32 ChimeraShot_Timer; + int32 SilencingShot_Timer; + int32 AimedShot_Timer; + + void Reset() + { + oom_spam = false; + + opponent = NULL; + + GC_Timer = 0; + ArcaneShot_cd = 0; + ChimeraShot_Timer = 0; + SilencingShot_Timer = 0; + AimedShot_Timer = 0; + + if (master) { + setStats(CLASS_HUNTER, m_creature->getRace(), master->getLevel()); + } + } + + void CreatePet() + { + pet = master->GetBotsPet(60238); + + if(pet == NULL) + return; + + pet->UpdateCharmAI(); + pet->setFaction(m_creature->getFaction()); + pet->SetReactState(REACT_DEFENSIVE); + pet->GetMotionMaster()->MoveFollow(m_creature, 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.0f + pet->GetStat(STAT_STRENGTH)*2.0f; + + 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)*2+master->getLevel()); + pet->UpdateDamagePhysical(BASE_ATTACK); + + } + + void UpdateAI(const uint32 diff) + { + + ReduceCD(diff); + + if(IAmDead()) return; + + if(!m_creature->isInCombat()) + { + DoNonCombatActions(); + } + + if(pet && pet != NULL && pet->isDead()) + { + master->SetBotsPetDied(); + pet = NULL; + } + + //if we think we have a pet, but master doesn't, it means we teleported + if(pet && master->m_botHasPet == false) + { + master->SetBotsPetDied(); + pet = NULL; + } + + DoNormalAttack(diff); + ScriptedAI::UpdateAI(diff); + + //if low on health, drink a potion + if(m_creature->GetHealth() < m_creature->GetMaxHealth()*0.6 && isTimerReady(Potion_cd)) + { + doCast(m_creature, HEALINGPOTION); + Potion_cd = 1500; + } + + //if low on mana, drink a potion + if(m_creature->GetPower(POWER_MANA) < m_creature->GetMaxPower(POWER_MANA)*0.2) + { + if(isTimerReady(Potion_cd)) + { + doCast(m_creature, MANAPOTION); + //MonsterSay("MANA POTION", LANG_UNIVERSAL, NULL); + Potion_cd = 1500; + } + } + + opponent = SelectUnit(SELECT_TARGET_TOPAGGRO, 0); + if(!opponent && !m_creature->getVictim()) + { + ResetOrGetNextTarget(); + + //to reduce the number of crashes, remove pet whenever we are not in combat + if(pet != NULL && pet->isAlive()) + { + master->SetBotsPetDied(); + pet = NULL; + } + return; + } + + + if(pet == NULL) + CreatePet(); + + if (pet && pet->isAlive() && + !pet->isInCombat() && + m_creature->getVictim()) { + pet->Attack (m_creature->getVictim(), true); + pet->GetMotionMaster()->MoveChase(m_creature->getVictim(), 1, 0); + + } + } + + void Aggro(Unit *who){} + + void JustDied(Unit *Killer) + { + master->SetBotCommandState(COMMAND_FOLLOW); + } + + void KilledUnit(Unit *) + { + ((Player*)master)->SetBotCommandState(COMMAND_FOLLOW); + } + + + void AttackStart(Unit *u) + { + Aggro(u); + ScriptedAI::AttackStartCaster(u, 33); + m_creature->AddThreat(u, 0.001f); + u->AddThreat(m_creature, 0.001f); + } + + void DoNormalAttack(const uint32 diff) + { + AttackerSet m_attackers = master->getAttackers(); + if(opponent == NULL) return; + if(opponent->isDead()) return; + + + // try to get rid of enrage + if (TRANQ_SHOT && HasAuraName(opponent, "Enrage")) { + m_creature->InterruptNonMeleeSpells(true, AUTO_SHOT); + // m_creature->MonsterSay("Tranquility shot!", LANG_UNIVERSAL, NULL); + doCast(opponent, TRANQ_SHOT); + GiveManaBack(); + // doCast(opponent, AUTO_SHOT); + // return; + } + + // silence it + if(SILENCING_SHOT && opponent->IsNonMeleeSpellCasted(true) && isTimerReady(SilencingShot_Timer)) + { + doCast(opponent, SILENCING_SHOT); + SilencingShot_Timer = 200; + GiveManaBack(); + // doCast(opponent, AUTO_SHOT); + // return; + } + + // mark it + if (!HasAuraName(opponent, "Hunter's Mark")) { + doCast(opponent, HUNTERS_MARK); + GiveManaBack(); + // doCast(opponent, AUTO_SHOT); + // return; + } + + // sting it + if (SCORPID_STING && !opponent->HasAura(SCORPID_STING, m_creature->GetGUID())) { + m_creature->InterruptNonMeleeSpells(true, AUTO_SHOT); + doCast(opponent, SCORPID_STING); + // m_creature->MonsterSay("Scorpid Sting!", LANG_UNIVERSAL, NULL); + GiveManaBack(); + // doCast(opponent, AUTO_SHOT); + // return; + } + + if (CHIMERA_SHOT && isTimerReady(ChimeraShot_Timer)) { + m_creature->InterruptNonMeleeSpells(true, AUTO_SHOT); + doCast(opponent, CHIMERA_SHOT); + ChimeraShot_Timer = 100; + // m_creature->MonsterSay("Chimera Sting!", LANG_UNIVERSAL, NULL); + GiveManaBack(); + // doCast(opponent, AUTO_SHOT); + // return; + } + + if(ARCANE_SHOT && isTimerReady(ArcaneShot_cd)) + { + m_creature->InterruptNonMeleeSpells( true, AUTO_SHOT ); + doCast(opponent, ARCANE_SHOT); + // m_creature->MonsterSay("Arcane shot!", LANG_UNIVERSAL, NULL); + ArcaneShot_cd = 60; + GiveManaBack(); + // doCast(opponent, AUTO_SHOT); + // return; + } + + if(AIMED_SHOT && isTimerReady(AimedShot_Timer)) + { + m_creature->InterruptNonMeleeSpells( true, AUTO_SHOT ); + doCast(opponent, AIMED_SHOT); + // m_creature->MonsterSay("Arcane shot!", LANG_UNIVERSAL, NULL); + AimedShot_Timer = 100; + GiveManaBack(); + // doCast(opponent, AUTO_SHOT); + // return; + } + + + doCast(opponent, AUTO_SHOT); + + } + + void DoNonCombatActions() + { + if (ASPECT_OF_THE_WILD && !HasAuraName(m_creature, "Aspect of the Wild")) { + doCast(master, ASPECT_OF_THE_WILD); + } + } + + void ReduceCD(const uint32 diff) + { + if(GC_Timer > 0) --GC_Timer; + if(ArcaneShot_cd > 0) --ArcaneShot_cd; + if(ChimeraShot_Timer > 0) --ChimeraShot_Timer; + if(SilencingShot_Timer > 0) --SilencingShot_Timer; + if(AimedShot_Timer > 0) --AimedShot_Timer; + } + + void ReceiveEmote(Player *player, uint32 emote) + { + //debug to see what auras are on bot + if(emote == TEXTEMOTE_BONK) ((hunter_botAI*)m_creature->AI())->listAuras(m_creature); + + //debug to see what auras are on me + if(emote == TEXTEMOTE_SALUTE) ((hunter_botAI*)m_creature->AI())->listAuras(player); + + + if(emote == TEXTEMOTE_STAND) + { + if(m_creature->GetCharmerGUID() != player->GetGUID()) + { + m_creature->HandleEmoteCommand(TEXTEMOTE_RUDE); + return; + } + + player->SetBotCommandState (COMMAND_STAY); + } + if(emote == TEXTEMOTE_WAVE) + { + player->SetBotCommandState (COMMAND_FOLLOW); + } + + + } + + }; +}; + +void AddSC_hunter_bot() +{ + new hunter_bot(); +} diff --git a/src/server/scripts/Bots/bot_hunter_ai.h b/src/server/scripts/Bots/bot_hunter_ai.h new file mode 100644 index 0000000..23b9c3d --- /dev/null +++ b/src/server/scripts/Bots/bot_hunter_ai.h @@ -0,0 +1,26 @@ + + +#include "bot_ai.h" + + +#define AUTO_SHOT 75 +#define TRANQ_SHOT TRANQ_SHOT_A[SPELL_LEVEL] +#define SCORPID_STING SCORPID_STING_A[SPELL_LEVEL] +#define HUNTERS_MARK HUNTERS_MARK_A[SPELL_LEVEL] + +#define ARCANE_SHOT ARCANE_SHOT_A[SPELL_LEVEL] +#define CHIMERA_SHOT CHIMERA_SHOT_A[SPELL_LEVEL] +#define AIMED_SHOT AIMED_SHOT_A[SPELL_LEVEL] +#define SILENCING_SHOT SILENCING_SHOT_A[SPELL_LEVEL] +#define ASPECT_OF_THE_WILD ASPECT_OF_THE_WILD_A[SPELL_LEVEL] + +uint32 TRANQ_SHOT_A[] = { 0, 0, 19801, 19801, 19801, 19801, 19801, 19801, 19801, 19801 }; +uint32 HUNTERS_MARK_A[] = { 14325, 14325, 14325, 14325, 14325, 14325, 14325, 14325, 14325, 14325 }; +uint32 SCORPID_STING_A[] = { 0, 0, 0, 0, 0, 0, 3043, 3043, 3043, 3043 }; + +uint32 ARCANE_SHOT_A[] = { 3044, 14281, 14282, 14284, 14285, 14286, 27019, 49044, 49045, 49045 }; +uint32 CHIMERA_SHOT_A[] = { 0, 0, 0, 0, 0, 0, 53209, 53209, 53209, 53209 }; +uint32 AIMED_SHOT_A[] = { 0, 0, 19434, 20900, 20902, 20903, 20904, 49049, 49050, 49050 }; +uint32 SILENCING_SHOT_A[] = { 0, 0, 0, 34490, 34490, 34490, 34490, 34490, 34490, 34490 }; + +uint32 ASPECT_OF_THE_WILD_A[] = {0, 0, 0, 0, 20043, 20190, 27045, 49071, 49071 }; diff --git a/src/server/scripts/Bots/bot_mage_ai.cpp b/src/server/scripts/Bots/bot_mage_ai.cpp new file mode 100644 index 0000000..8a680b5 --- /dev/null +++ b/src/server/scripts/Bots/bot_mage_ai.cpp @@ -0,0 +1,524 @@ + /* ScriptData + SDName: pvp_mage + SD%Complete: 0 + SDComment: Original PVP mage by Brats reworked by Azrael with the help of Machiavelli + SDCategory: Custom + EndScriptData */ +#include "ScriptPCH.h" +#include "bot_mage_ai.h" +#include "Group.h" + + +class mage_bot : public CreatureScript +{ +public: + mage_bot() : CreatureScript("mage_bot") { } + + CreatureAI *GetAI(Creature *pCreature) const + { + return new mage_botAI(pCreature); + } + + struct mage_botAI : public bot_ai + { + mage_botAI(Creature *c) :bot_ai(c) + { + //master = NULL; + Reset(); + } + + bool oom_spam; + + uint8 state; + uint8 next_state; + uint32 next_state_timer; + Unit *opponent; + + void Reset() + { + Blizzard_cd = 0; + FireBlast_cd = 0; + BlastWave_cd = 0; + CounterSpell_cd = 0; + FrostNova_cd = 0; + PoM_cd = 0; + Ward_cd = 0; + DragonBreath_cd = 0; + Blink_cd = 0; + Potion_cd = 0; + Combustion_cd = 0; + Evocation_cd = 0; + FirstAid_cd = 0; + GC_Timer = 0; + CastedArcaneIntellect = false; + CastedDampenMagic = false; + CastedArmor1 = false; + blink_timer = 10; + blink = false; + //wait = false; + oom_spam = false; + + state = 1; + next_state = 0; + next_state_timer = 0; + + opponent = NULL; + + if (master) { + setStats(CLASS_DRUID, m_creature->getRace(), master->getLevel()); + } + } + + void UpdateAI(const uint32 diff) + { + if(IAmDead()) return; + + ReduceCD(diff); + + if(!m_creature->isInCombat()) + { + //buff master because master might be in different group + if(!HasAuraName(master, GetSpellName(ARCANEINTELLECT)) && + master->isAlive() && + isTimerReady(GC_Timer)) + doCast(master, ARCANEINTELLECT, true); + + //buff myself + if(!m_creature->HasAura(ARCANEINTELLECT) && isTimerReady(GC_Timer)) + doCast(m_creature, ARCANEINTELLECT, true); + + //check group members + Group::MemberSlotList const &a = ((Player*)master)->GetGroup()->GetMemberSlots(); + for(Group::member_citerator itr = a.begin(); itr != a.end(); itr++) + { + Player *tPlayer = ((Player *)master)->GetObjPlayer(itr->guid); + if(tPlayer == NULL) continue; + + //buff group + if(tPlayer->isAlive() && + !m_creature->isInCombat() && + isTimerReady(GC_Timer) && + !HasAuraName(tPlayer, ARCANEINTELLECT)) + doCast(tPlayer, ARCANEINTELLECT, true); + } + + //no other buffs till full mana + if(m_creature->GetPower(POWER_MANA) == m_creature->GetMaxPower(POWER_MANA)) + { + //sLog->outError("mana is %u, total mana is %u", m_creature->GetPower(POWER_MANA), m_creature->GetMaxPower(POWER_MANA)); + //if(!HasAuraName(m_creature, "Mana Shield")) + //doCast(m_creature, MANASHIELD); + + if(!HasAuraName(m_creature, DAMPENMAGIC) && isTimerReady(GC_Timer)) + { + doCast(m_creature, DAMPENMAGIC, true); + GiveManaBack(); + CastedDampenMagic = true; + //MonsterSay("DAMPEN MAGIC", LANG_UNIVERSAL, NULL); + } + + if(!HasAuraName(m_creature, ICEARMOR) && isTimerReady(GC_Timer)) + { + doCast(m_creature, ICEARMOR, true); + GiveManaBack(); + //MonsterSay("ice armor", LANG_UNIVERSAL, NULL); + CastedArmor1 = true; + } + } + } //end if !isInCombat + + m_creature->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_TAUNT, true); + m_creature->ApplySpellImmune(0, IMMUNITY_EFFECT, SPELL_EFFECT_ATTACK_ME, true); + if(m_creature->GetHealth() < m_creature->GetMaxHealth()*0.5 && isTimerReady(Potion_cd)) + { + doCast(m_creature, HEALINGPOTION); + Potion_cd = 1500; + } + if(m_creature->GetPower(POWER_MANA) < m_creature->GetMaxPower(POWER_MANA)*0.3 && isTimerReady(Evocation_cd)) + { + doCast(m_creature, EVOCATION); + Evocation_cd = EVOCATION_CD; + } + if(m_creature->GetPower(POWER_MANA) < m_creature->GetMaxPower(POWER_MANA)*0.2) + { + if(isTimerReady(Potion_cd)) + { + doCast(m_creature, MANAPOTION); + //MonsterSay("MANA POTION", LANG_UNIVERSAL, NULL); + Potion_cd = Potion_cd; + } else { + if(oom_spam == false) + { + //MonsterSay("OOM", LANG_UNIVERSAL, NULL); + oom_spam = true; + } + ScriptedAI::UpdateAI(diff); + //return; //can't do anything without mana + } + } + oom_spam = false; + + ScriptedAI::UpdateAI(diff); + + opponent = SelectUnit(SELECT_TARGET_TOPAGGRO, 0); + if(!opponent && !m_creature->getVictim()) + { + ResetOrGetNextTarget(); + return; + } + + //Armour(diff); + CheckSpellSteal(diff); + DoNormalAttack(diff); + Counter(diff); + } + + void Aggro(Unit *who){} + + void Armour(const uint32 diff) + { + Unit *opponent = SelectUnit(SELECT_TARGET_TOPAGGRO, 0); + + if(!CastedArmor1 && (opponent->getClass() == CLASS_MAGE || opponent->getClass() == CLASS_PRIEST || opponent->getClass() == CLASS_WARLOCK) && isTimerReady(GC_Timer)) + { + doCast(m_creature, MAGEARMOR, true); + CastedArmor1 = true; + //MonsterSay("MAGE Armor", LANG_UNIVERSAL, NULL); + } + else if(!CastedArmor1 && isTimerReady(GC_Timer)) + { + doCast(m_creature, ICEARMOR, true); + //MonsterSay("ice armor 2", LANG_UNIVERSAL, NULL); + CastedArmor1 = true; + } else { + doCast(m_creature, ICEARMOR, true); + //MonsterSay("ice armor 3", LANG_UNIVERSAL, NULL); + CastedArmor1 = true; + return; + } + } //end Armour + + + void JustDied(Unit *Killer) + { + master->SetBotCommandState(COMMAND_FOLLOW); + state = 1; + next_state = 0; + next_state_timer = 0; + } + + void AttackStart(Unit *u) + { + Aggro(u); + ScriptedAI::AttackStartCaster(u, 31); + m_creature->AddThreat(u, 0.001f); + } + + void KilledUnit(Unit *) + { + ((Player*)master)->SetBotCommandState(COMMAND_FOLLOW); + state = 1; + next_state = 0; + next_state_timer = 0; + } + + void Counter(const uint32 diff) + { + if(opponent == NULL) return; + if(opponent->isDead()) return; + if(opponent->IsNonMeleeSpellCasted(true) && isTimerReady(CounterSpell_cd)) + { + doCast(opponent, COUNTERSPELL); + CounterSpell_cd = COUNTERSPELL_CD; + } + } + + void CheckSpellSteal(const uint32 diff) + { + if(opponent == NULL) return; + if(opponent->isDead()) return; + if( //Druids: + HasAuraName(opponent, "Mark of the Wild") || + HasAuraName(opponent, "Rejuvenation") || + HasAuraName(opponent, "Regrowth") || + //opponent->HasAura(33763) || //lifebloom + //Mage: + //opponent->HasAura(12043) || //POM + //Warlock: + HasAuraName(opponent, "Demon Armor") || + HasAuraName(opponent, "Fel Armor") || + //Priest: + HasAuraName(opponent, "Power Word: Shield") || + HasAuraName(opponent, "Power Word: Fortitude")) + { + if(isTimerReady(GC_Timer)) + doCast(opponent, SPELLSTEAL); + } + } + + + + void DoNormalAttack(const uint32 diff) + { + AttackerSet m_attackers = master->getAttackers(); + if(opponent == NULL) return; + if(opponent->isDead()) return; + bool defensive = false; + + //opponent is attacking me, go defensive, ie point blank spells + if(opponent->getVictim() && opponent->getVictim()->GetGUID() == m_creature->GetGUID()) + defensive = true; + + //sLog->outError("%s: state = %u, next_state = %u, next_state_time = %u", m_creature->GetName(), state, next_state, next_state_timer); + + switch(state) + { + case 0: + if(next_state_timer <= 0) + state = next_state; + else + --next_state_timer; + + break; + case 1: + if(opponent->GetHealth()*100 > 80) + //state = 2; + state = 6; + else + state = 5; + break; + case 2: + if(isTimerReady(GC_Timer)) + { + //MonsterSay("POLYMORPH", LANG_UNIVERSAL, NULL); + //doCast(opponent, POLYMORPH); + //next_state_timer = 21; + next_state_timer = 2; + next_state = 3; + state = 0; + } + case 3: + if(opponent->HasAura(POLYMORPH) || m_creature->HasAura(POM)) + { + next_state_timer = (m_creature->HasAura(POM)) ? 15 : 61; + int damage = 957 + rand()%(1215-957+1) + 1.15*BONUS_DAMAGE; + //m_creature->CastCustomSpell(opponent, PYROBLAST, &damage, NULL, NULL, false, NULL, NULL); + doCast(opponent, PYROBLAST); + //MonsterSay("PYROBLAST", LANG_UNIVERSAL, NULL); + //GC_Timer = 1500; + next_state = 4; + state = 0; + } else state = 5; + break; + case 4: + if(isTimerReady(PoM_cd)) + { + int damage = 957 + rand()%(1215-957+1) + 1.15*BONUS_DAMAGE; + //m_creature->CastCustomSpell(opponent, PYROBLAST, &damage, NULL, NULL, false, NULL, NULL); + doCast(opponent, PYROBLAST); + //MonsterSay("PYROBLAST", LANG_UNIVERSAL, NULL); + PoM_cd = POM_CD; + next_state = 5; + state = 0; + next_state_timer = 5; + } + else state = 5; + break; + case 5: + if(m_creature->GetPower(POWER_MANA)*100/m_creature->GetMaxPower(POWER_MANA) < 25.0) + break; + + if(m_creature->GetHealth() < m_creature->GetMaxHealth()*0.3 && isTimerReady(FirstAid_cd)) + { + doCast(opponent, FROSTNOVA); + GiveManaBack(); + FrostNova_cd = FROSTNOVA_CD; + //MonsterSay("FROSTNOVA", LANG_UNIVERSAL, NULL); + next_state = 8; + next_state_timer = 15; + state = 0; + } + if(LIVINGBOMB && isTimerReady(Living_Bomb_cd)) + { + doCast(opponent, LIVINGBOMB); + GiveManaBack(); + Living_Bomb_cd = LIVING_BOMB_CD; + //MonsterSay("LIVING BOMB", LANG_UNIVERSAL, NULL); + next_state = 7; + next_state_timer = 2; + state = 0; + } + else if(isTimerReady(Scorch_cd)) + { + //MonsterSay("SCORCH", LANG_UNIVERSAL, NULL); + doCast(opponent, SCORCH); + Scorch_cd = SCORCH_CD; + next_state = 7; + next_state_timer = 5; + state = 0; + } + else if(isTimerReady(GC_Timer)) + { + int damage = 377 + rand()%(407-377+1) + 0.2128*BONUS_DAMAGE; + //m_creature->CastCustomSpell(opponent, ARCANEEXPLOSION, &damage, NULL, NULL, false, NULL, NULL); + if(ARCANEEXPLOSION > 0 && defensive) + doCast(opponent, ARCANEEXPLOSION); + else + doCast(opponent, FIREBALL); + GiveManaBack(); + //MonsterSay("Arcane Explosion", LANG_UNIVERSAL, NULL); + next_state = 7; + next_state_timer = 2; + state = 0; + } + /*if(isTimerReady(Combustion_cd)) + { + doCast(m_creature, COMBUSTION); + Combustion_cd = COMBUSTION_CD; + //MonsterSay("COMBUSTION", LANG_UNIVERSAL, NULL); + next_state = 6; + next_state_timer = 15; + state = 0; + } + else if(BLIZZARD && isTimerReady (Blizzard_cd) && m_attackers.size() > 1) + { + doCast(opponent, 62576); + //GiveManaBack(); + Blizzard_cd = BLIZZARD_CD; + //MonsterSay("Blizzard", LANG_UNIVERSAL, NULL); + //sLog->outError("blizzard - %u", BLIZZARD); + //GiveManaBack(); + next_state = 6; + next_state_timer = 100; + state = 0; + }*/ + else if(FIREBLAST && isTimerReady(FireBlast_cd) && !defensive) + { + int damage = 677 + rand()%(802-677+1) + 0.4286*BONUS_DAMAGE; + //m_creature->CastCustomSpell(opponent, FIREBLAST, &damage, NULL, NULL, false, NULL, NULL); + doCast(opponent, FIREBLAST); + GiveManaBack(); + FireBlast_cd = FIREBLAST_CD; + //MonsterSay("FIREBLAST", LANG_UNIVERSAL, NULL); + next_state = 7; + next_state_timer = 3; + state = 0; + } + else if(BLASTWAVE && isTimerReady(BlastWave_cd) && defensive && !opponent->HasAura(BLASTWAVE)) + { + int damage = 628 + rand()%(739-628+1) + 0.1357*BONUS_DAMAGE; + //m_creature->CastCustomSpell(opponent, BLASTWAVE, &damage, NULL, NULL, false, NULL, NULL); + doCast(opponent, BLASTWAVE); + GiveManaBack(); + BlastWave_cd = BLASTWAVE_CD; + //MonsterSay("BLAST WAVE", LANG_UNIVERSAL, NULL); + next_state = 7; + next_state_timer = 2; + state = 0; + } + else if(DRAGONBREATH && isTimerReady(DragonBreath_cd) && defensive && !opponent->HasAura(DRAGONBREATH)) + { + int damage = 693 + rand()%(806-693+1) + 0.1357*BONUS_DAMAGE; + //m_creature->CastCustomSpell(opponent, DRAGONBREATH, &damage, NULL, NULL, false, NULL, NULL); + doCast(opponent, DRAGONBREATH); + GiveManaBack(); + //MonsterSay("Dragon Breath", LANG_UNIVERSAL, NULL); + DragonBreath_cd = DRAGONBREATH_CD; + next_state = 6; + next_state_timer = 2; + state = 0; + } + + break; + case 6: + if(isTimerReady(GC_Timer)) + { + int damage = 645 + rand()%(822-645+1) + 1.00*BONUS_DAMAGE; + //m_creature->CastCustomSpell(opponent, FIREBALL, &damage, NULL, NULL, false, NULL, NULL); + doCast(opponent, FIREBALL); + GiveManaBack(); + next_state = 5; + //MonsterSay("Fireball", LANG_UNIVERSAL, NULL); + next_state_timer = 3; + state = 0; + } + break; + case 7: + if(//HasAuraName(opponent, "Ice Block") || + HasAuraName(opponent, ICEBLOCK) || + HasAuraName(opponent, "Divine Shield") || + opponent->HasAura(5573)) //divine protection + { + doCast(m_creature, BANDAGE); + FirstAid_cd = FirstAid_cd; + state = 9; + } + else state = 5; + break; + case 8: + doCast(opponent, BLINK); //Needs some serious testing + //blink is not working, so comment out for now + //blink = true; + //m_creature->Say("BLINK", LANG_UNIVERSAL, NULL); + //GC_Timer = 1500; + //MonsterSay("Blink", LANG_UNIVERSAL, NULL); + next_state = 9; + next_state_timer = 5; + state = 0; + break; + case 9: + /*if(blink) + { + wait = true; + wait_timer = 60; + doCast(m_creature, BANDAGE); + FirstAid_cd = FirstAid_cd; + //m_creature->Say("BANDAGE", LANG_UNIVERSAL, NULL); + blink = false; + //GC_Timer = 1500; + next_state = 10; + next_state_timer = 35; + } else*/ + next_state = 5; + state = 0; + break; + case 10: + if(!m_creature->HasAura(BANDAGE)) + state = 5; + break; + default: + state = 1; + } + } + + void ReduceCD(const uint32 diff) + { + if(Living_Bomb_cd > 0) --Living_Bomb_cd; + if(FireBlast_cd > 0) --FireBlast_cd; + if(Blizzard_cd > 0) --Blizzard_cd; + if(BlastWave_cd > 0) --BlastWave_cd; + if(CounterSpell_cd > 0) --CounterSpell_cd; + if(FrostNova_cd > 0) --FrostNova_cd; + if(PoM_cd > 0) --PoM_cd; + if(Ward_cd > 0) --Ward_cd; + if(Scorch_cd > 0) --Scorch_cd; + if(DragonBreath_cd > 0) --DragonBreath_cd; + if(Blink_cd > 0) --Blink_cd; + if(Combustion_cd > 0) --Combustion_cd; + if(Potion_cd > 0) --Potion_cd; + if(Evocation_cd > 0) --Evocation_cd; + if(FirstAid_cd >0) --FirstAid_cd; + if(GC_Timer > 0) --GC_Timer; + } + + void ReceiveBowEmote(Player *player) + { + ((mage_botAI*)m_creature->AI())->doCast(player, ARCANEINTELLECT, true ); + } + }; +}; + +void AddSC_mage_bot() +{ + new mage_bot(); +} diff --git a/src/server/scripts/Bots/bot_mage_ai.h b/src/server/scripts/Bots/bot_mage_ai.h new file mode 100644 index 0000000..af681b7 --- /dev/null +++ b/src/server/scripts/Bots/bot_mage_ai.h @@ -0,0 +1,124 @@ + +#include "bot_ai.h" + + +uint32 FireBlast_cd; +uint32 BlastWave_cd; +uint32 CounterSpell_cd; +uint32 FrostNova_cd; +uint32 PoM_cd; +uint32 Ward_cd; +uint32 Blizzard_cd; +uint32 DragonBreath_cd; +uint32 Blink_cd; +uint32 Combustion_cd; +uint32 Scorch_cd; +uint32 Potion_cd; +uint32 Evocation_cd; +uint32 Living_Bomb_cd; +uint32 FirstAid_cd; +uint32 GC_Timer; +uint32 blink_timer; +//uint32 wait_timer; + +bool CastedDampenMagic; +bool CastedArcaneIntellect; +bool CastedArmor1; +//bool wait; +bool blink; + +//arcane spells cooldown +#define ARCANETORRENT_CD 120 +#define EVOCATION_CD 4800 +#define COUNTERSPELL_CD 240 +#define POM_CD 1200 +#define BLINK_CD 1500 + +//fire spells cooldown +#define BLASTWAVE_CD 350 +#define DRAGONBREATH_CD 450 +#define WARD_CD 300 +#define FIREBLAST_CD 650 //8000 if not fire mage +#define COMBUSTION_CD 1800 +#define SCORCH_CD 900 +#define LIVING_BOMB_CD 600 + +//frost spells cooldown +#define FROSTNOVA_CD 250 +#define CONEOFCOLD_CD 100 +#define ICEBLOCK_CD 3000 +#define BLIZZARD_CD 300 + +//arcane spells +#define DAMPENMAGIC SPELL_DAMPENMAGIC_A[SPELL_LEVEL] +#define MANASHIELD SPELL_MANASHIELD_A[SPELL_LEVEL] +#define MAGEARMOR SPELL_MAGEARMOR_A[SPELL_LEVEL] +#define SPELLSTEAL SPELL_SPELLSTEAL_A[SPELL_LEVEL] +#define ARCANEMISSILES SPELL_ARCANEMISSILES_A[SPELL_LEVEL] +#define ARCANEINTELLECT SPELL_ARCANEINTELLECT_A[SPELL_LEVEL] +#define ARCANEEXPLOSION SPELL_ARCANEEXPLOSION_A[SPELL_LEVEL] +#define POLYMORPH SPELL_POLYMORPH_A[SPELL_LEVEL] +#define COUNTERSPELL 2139 +#define EVOCATION 12051 +#define POM 12043 +#define REMCURSE 15729 +#define BLINK 38932 +#define ARCANETORRENT 28730 + + + +//fire spells +#define FIREBALL SPELL_FIREBALL_A[SPELL_LEVEL] +#define BLASTWAVE SPELL_BLASTWAVE_A[SPELL_LEVEL] +#define DRAGONBREATH SPELL_DRAGONBREATH_A[SPELL_LEVEL] +#define FIREBLAST SPELL_FIREBLAST_A[SPELL_LEVEL] +#define FIREWARD SPELL_FIREWARD_A[SPELL_LEVEL] +#define PYROBLAST SPELL_PYROBLAST_A[SPELL_LEVEL] +#define COMBUSTION SPELL_COMBUSTION_A[SPELL_LEVEL] +#define SCORCH SPELL_SCORCH_A[SPELL_LEVEL] +#define MOLTENARMOR 30482 +#define LIVINGBOMB SPELL_LIVING_BOMB_A[SPELL_LEVEL] + +//frost spells +#define FROSTNOVA SPELL_FROSTNOVA_A[SPELL_LEVEL] +#define FROSTWARD SPELL_FROSTWARD_A[SPELL_LEVEL] +#define CONEOFCOLD SPELL_CONEOFCOLD_A[SPELL_LEVEL] +#define ICEARMOR SPELL_ICEARMOR_A[SPELL_LEVEL] +#define ICEBLOCK 45438 +#define BLIZZARD SPELL_BLIZZARD_A[SPELL_LEVEL] + + +//others +#define BONUS_DAMAGE 986 //Spell Bonus +#define BANDAGE 27031 +#define MANAPOTION 28499 +#define REJUVEPOTION 28517 +#define HEALINGPOTION 28495 + +//arcane spells +uint32 SPELL_DAMPENMAGIC_A[] = { 0, 8450, 8451, 10173, 10173, 10174, 33944, 43015, 43015 }; +uint32 SPELL_MANASHIELD_A[] = { 0, 0, 1463, 8495, 10191, 10192, 27131, 43019, 43020 }; +uint32 SPELL_MAGEARMOR_A[] = { 0, 0, 0, 6117, 22782, 22783, 27125, 43023, 43024 }; +uint32 SPELL_SPELLSTEAL_A[] = { 0, 0, 0, 0, 0, 0, 0, 30449, 30449 }; +uint32 SPELL_ARCANEMISSILES_A[] = {5143, 5144, 5145, 8416, 8417, 10212, 27075, 42843, 42846 }; +uint32 SPELL_ARCANEINTELLECT_A[] = { 1459, 1460, 1461, 1461, 10156, 10157, 10157, 27126, 42995}; +uint32 SPELL_ARCANEEXPLOSION_A[] = { 0, 1449, 8437, 8439, 10201, 10202, 27080, 42990, 42921 }; +uint32 SPELL_POLYMORPH_A[] = { 0, 0, 12824, 12824, 12825, 12825, 28271, 28272, 61721 }; + +//fire spells +uint32 SPELL_FIREBALL_A[] = { 143, 145, 8400, 8401, 10148, 25306, 38692, 42834, 42834 }; +uint32 SPELL_BLASTWAVE_A[] = { 0, 0, 0, 11113, 13019, 13020, 13021, 42944, 42945 }; +uint32 SPELL_DRAGONBREATH_A[] = { 0, 0, 0, 0, 0, 31661, 31661, 31661, 31661 }; +uint32 SPELL_FIREBLAST_A[] = {2136, 2137, 2138, 8412, 10197, 10199, 10199, 10199, 10199 }; +uint32 SPELL_FIREWARD_A[] = { 0, 0, 543, 8457, 8458, 10223, 10225, 27128, 43010 }; +uint32 SPELL_COMBUSTION_A[] = { 0, 0, 0, 0, 11129, 11129, 11129, 11129, 11129 }; +uint32 SPELL_PYROBLAST_A[] = { 0, 0, 11366, 12522, 12525, 12526, 27132, 42890, 42891 }; +uint32 SPELL_SCORCH_A[] = { 0, 0, 2948, 8445, 10205, 10207, 27073, 27074, 42859 }; +uint32 SPELL_LIVING_BOMB_A[] = { 0, 0, 0, 0, 0, 44457, 44457, 44457, 44457 }; + +//frost spells +uint32 SPELL_FROSTNOVA_A[] = {0, 122, 865, 6131, 10230, 27088, 42917, 42917, 42917 }; +uint32 SPELL_FROSTWARD_A[] = { 0, 0, 6143, 8461, 8462, 10177, 28609, 32796, 43012 }; +uint32 SPELL_CONEOFCOLD_A[] = { 0, 0, 120, 8492, 10159, 10161, 27087, 42930, 42931 }; +uint32 SPELL_ICEARMOR_A[] = {0, 0, 0, 7302, 7320, 10219, 10220, 27124, 43008 }; +uint32 SPELL_BLIZZARD_A[] = { 0, 0, 10, 8427, 10185, 10186, 10187, 27085, 42940 }; diff --git a/src/server/scripts/Bots/bot_paladin_ai.cpp b/src/server/scripts/Bots/bot_paladin_ai.cpp new file mode 100644 index 0000000..5a53215 --- /dev/null +++ b/src/server/scripts/Bots/bot_paladin_ai.cpp @@ -0,0 +1,276 @@ +#include "ScriptPCH.h" +#include "bot_paladin_ai.h" + + +class paladin_bot : public CreatureScript +{ +public: + paladin_bot() : CreatureScript("paladin_bot") { } + + CreatureAI *GetAI(Creature *pCreature) const + { + return new paladin_botAI(pCreature); + } + + struct paladin_botAI : public bot_ai + { + paladin_botAI(Creature *c) :bot_ai(c) + { + Reset(); + } + + bool oom_spam; + + Unit *opponent; + + int32 LOH_Timer; + int32 HOJ_Timer; + + void Reset() + { + oom_spam = false; + + opponent = NULL; + + GC_Timer = 0; + LOH_Timer = 0; + HOJ_Timer = 0; + + if (master) { + setStats(CLASS_PALADIN, m_creature->getRace(), master->getLevel()); + } + } + + bool CureTarget(Unit *target) + { + if (!isTimerReady(GC_Timer)) return false; + if (HasAuraName(target, "Withering Heat")) + { + //sLog->outError ("PaladingBotAI.CureTarget: curing %s of withering heat"); + doCast(target, CLEANSE); + } + if (HasAuraName(target, "Ancient Dread")) + { + //sLog->outError ("PaladingBotAI.CureTarget: curing %s of Ancient Dread"); + doCast(target, CLEANSE); + } + if (HasAuraName(target, "Ancient Dread")) + { + //sLog->outError ("PaladingBotAI.CureTarget: curing %s of Ancient Dread"); + doCast(target, CLEANSE); + } + if (HasAuraName(target, "Arcane Buffet")) + { + //sLog->outError ("PaladingBotAI.CureTarget: curing %s of Arcane Buffet"); + doCast(target, CLEANSE); + } + if (HasAuraName(target, "Shadow Buffet")) + { + //sLog->outError ("PaladingBotAI.CureTarget: curing %s of Shadow Buffet"); + doCast(target, CLEANSE); + } + if (HasAuraName(target, "Flame Buffet")) + { + //sLog->outError ("PaladingBotAI.CureTarget: curing %s of Flame Buffet"); + doCast(target, CLEANSE); + } + if (HasAuraName(target, "Frost Buffet")) + { + //sLog->outError ("PaladingBotAI.CureTarget: curing %s of Frost Buffet"); + doCast(target, CLEANSE); + } + return true; + } + + bool HealTarget(Unit *target, uint8 hp) + { + if (!isTimerReady(GC_Timer)) return false; + if (m_creature->IsNonMeleeSpellCasted(true)) return false; + if(!target || target->isDead()) return false; + if(hp < 25 && isTimerReady(LOH_Timer)) + { + // 33% to cast loh, else just do a fast heal + uint64 m_rand = urand(1, 3); + switch(m_rand) + { + case 1: { + std::string loh = "Lay of Hands on "; + loh += target->GetName(); + loh += "."; + + m_creature->MonsterSay(loh.c_str(), LANG_UNIVERSAL, NULL); + + doCast(target, LAY_OF_HANDS); + LOH_Timer = 1600; + return true; + } + case 2: + case 3: { + doCast(target, FLASH_OF_LIGHT); + GiveManaBack(15); + return true; + } + } + + } + if(hp < 60) { doCast(target, FLASH_OF_LIGHT); GiveManaBack(15); return true; } + if(hp < 80) { doCast(target, HOLY_LIGHT); GiveManaBack(20); return true; } + + return true; + } //end HealTarget + + + void UpdateAI(const uint32 diff) + { + if(IAmDead()) return; + + ReduceCD(diff); + + // opponent = master->getVictim() ? master->getVictim() : SelectUnit(SELECT_TARGET_TOPAGGRO, 0); + opponent = m_creature->getVictim(); + if(!opponent ) + { + ResetOrGetNextTarget(); + DoNonCombatActions(); + return; + } + + m_creature->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_TAUNT, true); + m_creature->ApplySpellImmune(0, IMMUNITY_EFFECT, SPELL_EFFECT_ATTACK_ME, true); + if(m_creature->GetHealth() < m_creature->GetMaxHealth()*0.5 && isTimerReady(Potion_cd)) + { + doCast(m_creature, HEALINGPOTION); + Potion_cd = Potion_cd; + } + + oom_spam = false; + + //buff and heal master's group + if(master->GetGroup()) + { + BuffAndHealGroup(master); + CureGroup(master); + } + + // Heal myself + HealTarget (m_creature, m_creature->GetHealth()*100 / m_creature->GetMaxHealth()); + + + + DoNormalAttack(diff); + Counter(diff); + + ScriptedAI::UpdateAI(diff); + } + + void Aggro(Unit *who){ + } + + void Armour(const uint32 diff) + { + + } //end Armour + + void DoNonCombatActions() + { + if (m_creature->HasAura(DEVOTION_AURA)) Aura = DEVOTIONAURA; + else if (m_creature->HasAura(FIRE_RESISTANCE_AURA)) Aura = FIRERESISTANCEAURA; + else Aura = NOAURA; + + //buff myself + if(!master->HasAura(DEVOTION_AURA) && isTimerReady(GC_Timer)) { + doCast(m_creature, DEVOTION_AURA, true); + } + else + // I already have devotion aura and its not mine, cast different aura + if (master->HasAura(DEVOTION_AURA) && + !master->HasAura(DEVOTION_AURA, m_creature->GetGUID()) && + Aura == NOAURA && + isTimerReady(GC_Timer)) + doCast(m_creature, FIRE_RESISTANCE_AURA, true); + + if(!m_creature->HasAura(SEAL_OF_LIGHT) && isTimerReady(GC_Timer)) + doCast(m_creature, SEAL_OF_LIGHT, true); + + //buff and heal master's group + if(master->GetGroup()) + { + RezGroup(REDEMPTION, master); + BuffAndHealGroup(master); + CureGroup(master); + } + + } + + void BuffTarget(Unit *target) + { + if(!target) return; + switch(target->getClass()) + { + case CLASS_MAGE: + case CLASS_PRIEST: + case CLASS_WARLOCK: + if (!HasAuraName(target, GetSpellName(BLESSING_OF_WISDOM))) doCast(target, BLESSING_OF_WISDOM, true); + break; + case CLASS_PALADIN: + if (!HasAuraName(target, GetSpellName(BLESSING_OF_SANCTUARY))) doCast(target, BLESSING_OF_SANCTUARY, true); + break; + default: + if (!HasAuraName(target, GetSpellName(BLESSING_OF_KINGS))) doCast(target, BLESSING_OF_KINGS, true); + break; + } + + } + + + void JustDied(Unit *Killer) + { + master->SetBotCommandState(COMMAND_FOLLOW); + } + + void AttackStart(Unit *u) + { + Aggro(u); + ScriptedAI::AttackStart(u); + } + + void KilledUnit(Unit *) + { + ((Player*)master)->SetBotCommandState(COMMAND_FOLLOW); + } + + void Counter(const uint32 diff) + { + if(opponent == NULL) return; + if(opponent->isDead()) return; + if(opponent->IsNonMeleeSpellCasted(true) && isTimerReady(HOJ_Timer)) + { + doCast(opponent, HAMMER_OF_JUSTICE); + HOJ_Timer = 600; + } + } + + void DoNormalAttack(const uint32 diff) + { + if(!opponent->HasAura(JUDGEMENT_OF_LIGHT) && isTimerReady(GC_Timer)) + doCast(opponent, JUDGEMENT_OF_LIGHT, true); + + } + + void ReduceCD(const uint32 diff) + { + if(GC_Timer > 0) --GC_Timer; + if(LOH_Timer > 0) --LOH_Timer; + if(HOJ_Timer > 0) --HOJ_Timer; + } + void ReceiveBowEmote(Player *player) + { + BuffTarget(player); + } + }; +}; + +void AddSC_paladin_bot() +{ + new paladin_bot(); +} diff --git a/src/server/scripts/Bots/bot_paladin_ai.h b/src/server/scripts/Bots/bot_paladin_ai.h new file mode 100644 index 0000000..f6ccab8 --- /dev/null +++ b/src/server/scripts/Bots/bot_paladin_ai.h @@ -0,0 +1,62 @@ + +#include "bot_ai.h" + + +enum AURAS { + DEVOTIONAURA, + FIRERESISTANCEAURA, + NOAURA +} ; + +AURAS Aura; + + +// misc +#define BANDAGE 27031 +#define MANAPOTION 28499 +#define REJUVEPOTION 28517 +#define HEALINGPOTION 28495 + +// Heals +#define FLASH_OF_LIGHT FLASH_OF_LIGHT_A[SPELL_LEVEL] +#define HOLY_LIGHT HOLY_LIGHT_A[SPELL_LEVEL] +#define LAY_OF_HANDS LAY_OF_HANDS_A[SPELL_LEVEL] +#define REDEMPTION REDEMPTION_A[SPELL_LEVEL] + +#define CLEANSE CLEANSE_A[SPELL_LEVEL] + +uint32 FLASH_OF_LIGHT_A[] = {0, 0, 19939, 19940, 19941, 19942, 27137, 48784, 48785, 48785}; +uint32 HOLY_LIGHT_A[] = {635, 647, 1026, 3472, 10328, 10329, 27135, 48781, 48782, 48782}; +uint32 LAY_OF_HANDS_A[] = {0, 633, 633, 2800, 2800, 10310, 10310, 27154, 48788, 48788}; +uint32 REDEMPTION_A[] = {0, 7328, 10322, 10324, 20772, 20772, 20773, 48949, 48950, 48950}; +uint32 CLEANSE_A[] = {0, 0, 0, 0, 4987, 4987, 4987, 4987, 4987}; + +// Seals +#define SEAL_OF_LIGHT SEAL_OF_LIGHT_A[SPELL_LEVEL] +#define JUDGEMENT_OF_LIGHT JUDGEMENT_OF_LIGHT_A[SPELL_LEVEL] + +uint32 SEAL_OF_LIGHT_A[] = {0, 0, 0, 20165, 20165, 20165, 20165, 20165, 20165, 20165}; +uint32 JUDGEMENT_OF_LIGHT_A[] = {0, 0, 0, 20185, 20185, 20185, 20185, 20185, 20185, 20185}; + +// Blessings +#define BLESSING_OF_WISDOM BLESSING_OF_WISDOM_A[SPELL_LEVEL] +#define BLESSING_OF_KINGS BLESSING_OF_KINGS_A[SPELL_LEVEL] +#define BLESSING_OF_SANCTUARY BLESSING_OF_SANCTUARY_A[SPELL_LEVEL] + +uint32 BLESSING_OF_WISDOM_A[] = {0, 19742, 19850, 19852, 19853, 19854, 25290, 48935, 48936, 48936}; +uint32 BLESSING_OF_KINGS_A[] = {0, 0, 56525, 56525, 56525, 56525, 56525, 56525, 56525, 56525}; +uint32 BLESSING_OF_SANCTUARY_A[] = {0, 0, 0, 67480, 67480, 67480, 67480, 67480, 67480, 67480}; + + +// Auras +#define FIRE_RESISTANCE_AURA FIRE_RESISTANCE_AURA_A[SPELL_LEVEL] +#define DEVOTION_AURA DEVOITION_AURA_A[SPELL_LEVEL] + +uint32 FIRE_RESISTANCE_AURA_A[] = {0, 0, 0, 19891, 19899, 19899, 19900, 27153, 48947, 48947}; +uint32 DEVOITION_AURA_A[] = {465, 10290, 643, 10291, 1032, 10292, 10293, 48941, 48942, 48942}; + + +// Others +#define HAMMER_OF_JUSTICE HAMMER_OF_JUSTICE_A[SPELL_LEVEL] + +uint32 HAMMER_OF_JUSTICE_A[] = {0, 853, 5588, 5588, 5589, 10308, 10308, 37369, 37369}; diff --git a/src/server/scripts/Bots/bot_priest_ai.cpp b/src/server/scripts/Bots/bot_priest_ai.cpp new file mode 100644 index 0000000..f6a6941 --- /dev/null +++ b/src/server/scripts/Bots/bot_priest_ai.cpp @@ -0,0 +1,276 @@ +#include "ScriptPCH.h" +#include "bot_priest_ai.h" +#include "Group.h" + +class priest_bot : public CreatureScript +{ +public: + priest_bot() : CreatureScript("priest_bot") { } + + CreatureAI *GetAI(Creature *pCreature) const + { + return new priest_botAI(pCreature); + } + + struct priest_botAI : public bot_ai + { + priest_botAI(Creature *c) :bot_ai(c) + { + Reset(); + } + + int32 Heal_Timer; + int32 Renew_Timer; + int32 Self_Renew_Timer; + int32 PWS_Timer; + int32 Others_Heal_Timer; + int32 Oom_timer; + int32 Fade_Timer; + int32 Potion_Timer; + int32 Rez_Timer; + + Unit *mobsTarget; + + void Reset() + { + GC_Timer = 0; + Heal_Timer = 0; + Renew_Timer = 0; + Self_Renew_Timer = 0; + PWS_Timer = 0; + Others_Heal_Timer = 0; + Oom_timer = 0; + Fade_Timer = 0; + Potion_Timer = 0; + Rez_Timer = 0; + + if (master) { + setStats(CLASS_PRIEST, m_creature->getRace(), master->getLevel()); + } + } + + void EnterEvadeMode(){ Oom_timer = 0; } + + void Aggro(Unit *who){} + + void AttackStart(Unit *u) + { + m_creature->GetMotionMaster()->MoveFollow(master, urand(5, 10), PET_FOLLOW_ANGLE); + + } + + void KilledUnit(Unit *) + { + ((Player*)master)->SetBotCommandState(COMMAND_FOLLOW); + } + + void MoveInLineOfSight(Unit *target) + { + if (master==NULL || master==0) return; + + if(target->GetGUID() == master->GetGUID()) return; + + if(!target->IsFriendlyTo(master)) return; + + if(target->isAlive() && ((target->GetHealth()*100) < 100)) + m_creature->CastSpell(target, SPELL_HEAL, false); + } + + bool isTimerReady(int32 timer) + { + if(timer <= 0 && GC_Timer <= 0) return true; + else return false; + } //end isTimerReady + + void decrementTimers() + { + if(GC_Timer > 0) --GC_Timer; + if(Heal_Timer > 0) --Heal_Timer; + if(Others_Heal_Timer > 0) --Others_Heal_Timer; + if(Fade_Timer > 0) --Fade_Timer; + if(Self_Renew_Timer > 0) --Self_Renew_Timer; + if(Renew_Timer > 0) --Renew_Timer; + if(PWS_Timer > 0) --PWS_Timer; + if(Potion_Timer > 0) --Potion_Timer; + } //end decrementTImers + + void UpdateAI(const uint32 diff) + { + decrementTimers(); + + if(IAmDead()) return; + + if(!m_creature->isInCombat()) + { + DoNonCombatActions(); + } + + //buff and heal master's group + BuffAndHealGroup(master); + + + //check group members + Group::MemberSlotList const &a =((Player*)master)->GetGroup()->GetMemberSlots(); + for(Group::member_citerator itr = a.begin(); itr != a.end(); itr++) + { + Player *tPlayer = ((Player *)master)->GetObjPlayer(itr->guid); + if(tPlayer == NULL) continue; + } + + //if low on mana, drink a potion + if(m_creature->GetPower(POWER_MANA) < 400 && isTimerReady(Potion_Timer)) + { + doCast(m_creature, MANAPOTION, true); + Potion_Timer = 1500; + } + //if after drinking a potion still low on mana + //let everyone know that you are oom. + if(m_creature->GetPower(POWER_MANA)/m_creature->GetMaxPower(POWER_MANA) < 10) + { + if(Oom_timer == 0) + { + //MonsterSay("OOM", LANG_UNIVERSAL, NULL); + Oom_timer = 1; + } + } + + // Heal myself + if(m_creature->GetHealth()*100 / m_creature->GetMaxHealth() < 90) + { + if(Fade_Timer <= 0 && m_creature->isInCombat() && + isTimerReady(Fade_Timer)) + { + doCast(m_creature, SPELL_FADE); + Fade_Timer = 30; + return; + } + + + HealTarget (m_creature, m_creature->GetHealth()*100 / m_creature->GetMaxHealth()); + + } + + //now try to heal bots and pets. DoSelectLowestHpFriendly will get + //everyone in group including bots and pets. Unfortunately it can + //not be triggered for % of lost HP, so we just set it to -1000. + //This means low level players wont be healed because they wont have + //enough HP. + if(isTimerReady(Others_Heal_Timer)) + { + Unit *target; + if(target = DoSelectLowestHpFriendly(40, 1000)) + { + doCast(target, SPELL_HEAL, false); + GiveManaBack(); + Others_Heal_Timer = 50; + } + else if(target = DoSelectLowestHpFriendly(40, 500)) + { + if(!target->HasAura(SPELL_RENEW, 0)) + { + doCast(target, SPELL_RENEW, false); + GiveManaBack(); + Others_Heal_Timer = 100; + } + } + } //end if isTimerReady(Others_Heal_Timer) + } //end UpdateAI + + bool HealTarget(Unit *target, uint8 hp) + { + if (!isTimerReady(GC_Timer)) return false; + if (m_creature->IsNonMeleeSpellCasted(true)) return false; + if(!target || target->isDead()) return false; + // if(hp < 60) { doCast(target, FLASH_OF_LIGHT); GiveManaBack(15); return true; } + // if(hp < 80) { doCast(target, HOLY_LIGHT); GiveManaBack(20); return true; } + + if((hp < 50) && + isTimerReady(PWS_Timer) && + !target->isDead()) + { + doCast(target, SPELL_PW_SHIELD); + PWS_Timer = 120; + //free (buff); + } + + if((hp < 90) && + hp >75 && + isTimerReady(Renew_Timer)) + { + doCast(target, SPELL_RENEW, true); + GiveManaBack(); + Renew_Timer = 90; + Heal_Timer = Heal_Timer + 5; //wait 5 seconds before casting a real heal + return true; + } + + if((hp < 75) && + isTimerReady(Heal_Timer) && + !target->isDead()) + { + doCast(target, SPELL_HEAL); + GiveManaBack(); + Heal_Timer = 10; + return true; + } + + return true; + } + + void BuffTarget(Unit *target) + { + if (!HasAuraName(target, GetSpellName(SPELL_FORTITUDE))) { + doCast(target, SPELL_FORTITUDE, false); + GiveManaBack(); + } + if (!HasAuraName(target, GetSpellName(SPELL_PRAYER_OF_SHADOW))) { + doCast(target, SPELL_PRAYER_OF_SHADOW, false); + GiveManaBack(); + } + } + + void DoNonCombatActions() + { + //if eating or drinking don't do anything + if(m_creature->HasAura(10256) || m_creature->HasAura(1137)) return; + + Feast(); + + //buff master + if(!HasAuraName(master, SPELL_PRAYER_OF_SHADOW, 0) && isTimerReady(GC_Timer)) doCast(master, SPELL_PRAYER_OF_SHADOW, true); + if(!HasAuraName(master, FEAR_WARD, 0) && isTimerReady(GC_Timer)) doCast(master, FEAR_WARD, true); + if(!HasAuraName(master, DIVINE_SPIRIT, 0) && isTimerReady(GC_Timer)) { + doCast(master, DIVINE_SPIRIT, true); + GiveManaBack(); + } + + //buff myself + if(!m_creature->HasAura(SPELL_INNER_FIRE, 0) && isTimerReady(GC_Timer)) doCast(m_creature, SPELL_INNER_FIRE, true); + if(!m_creature->HasAura(SPELL_FORTITUDE, 0) && isTimerReady(GC_Timer)) doCast(m_creature, SPELL_FORTITUDE, true); + //if(m_creature->getRace() == RACE_UNDEAD_PLAYER && !m_creature->HasAura(SPELL_TOUCH_OF_WEAKNESS, 0) && isTimerReady(GC_Timer)) doCast(m_creature, SPELL_TOUCH_OF_WEAKNESS); + + //buff and heal master's group + if(master->GetGroup()) + { + RezGroup(SPELL_RESURRECTION, master); + //BuffAndHealGroup(master); + // CureGroup(master); + } + + + } + + void ReceiveBowEmote(Player *player) + { + ((priest_botAI*)m_creature->AI())->doCast(player, SPELL_FORTITUDE, true); + ((priest_botAI*)m_creature->AI())->doCast(player, SPELL_PRAYER_OF_SHADOW, false); + } + + }; //end priest_bot +}; + + +void AddSC_priest_bot() +{ + new priest_bot(); +} diff --git a/src/server/scripts/Bots/bot_priest_ai.h b/src/server/scripts/Bots/bot_priest_ai.h new file mode 100644 index 0000000..09bf1bb --- /dev/null +++ b/src/server/scripts/Bots/bot_priest_ai.h @@ -0,0 +1,33 @@ +#include "bot_ai.h" + + +#define FEAR_WARD 6346 + +#define SPELL_PW_SHIELD SPELL_PW_SHIELD_A[SPELL_LEVEL] +//#define SPELL_DIVINE_SPIRIT 14818 + +#define SPELL_FORTITUDE SPELL_FORTITUDE_A[SPELL_LEVEL] +#define SPELL_HEAL SPELL_HEAL_A[SPELL_LEVEL] +#define SPELL_RENEW SPELL_RENEW_A[SPELL_LEVEL] +#define SPELL_FADE SPELL_FADE_A[SPELL_LEVEL] +#define SPELL_RESURRECTION SPELL_RESURRECTION_A[SPELL_LEVEL] +//define SPELL_VAMPIRIC_EMBRACE +//#define SPELL_INNER_FOCUS 14751 + +//BUFFS +#define SPELL_INNER_FIRE SPELL_INNER_FIRE_A[SPELL_LEVEL] +#define SPELL_PRAYER_OF_SHADOW SPELL_PRAYER_OF_SHADOW_A[SPELL_LEVEL] +#define DIVINE_SPIRIT DIVINE_SPIRIT_A[SPELL_LEVEL] +//#define SPELL_TOUCH_OF_WEAKNESS 19265 //rank 5 + +uint32 SPELL_FORTITUDE_A[] = { 1243, 1244, 1245, 2791, 10937, 10938, 25389, 48161, 48161 }; +uint32 SPELL_RENEW_A[] = { 8362, 11640, 6075, 6077, 10927, 10928, 25315, 48068, 48068 }; +uint32 SPELL_HEAL_A[] = { 29170, 29170, 2055, 6064, 10963, 10964, 10965, 25213, 25213 }; +uint32 SPELL_PW_SHIELD_A[] = { 17, 592, 3747, 6066, 10899, 10900, 10901, 25218, 25218 }; +uint32 SPELL_FADE_A[] = { 586, 586, 586, 586, 586, 586, 586, 586, 586 }; +uint32 SPELL_PRAYER_OF_SHADOW_A[] = { 0, 0, 0, 0, 0, 27683, 27683, 39374, 39374, 39374 }; + +uint32 SPELL_INNER_FIRE_A[] = { 588, 7128, 602, 1006, 10951, 10952, 25431, 25431, 48040, 48040 }; +uint32 DIVINE_SPIRIT_A[] = {0, 0, 0, 14752, 14818, 14819, 27841, 25312, 48073, 48073}; + +uint32 SPELL_RESURRECTION_A[] = { 2006, 2006, 2006, 2010, 10880, 10881, 20770, 25435, 25435 }; diff --git a/src/server/scripts/Bots/bot_rogue_ai.cpp b/src/server/scripts/Bots/bot_rogue_ai.cpp new file mode 100644 index 0000000..916a991 --- /dev/null +++ b/src/server/scripts/Bots/bot_rogue_ai.cpp @@ -0,0 +1,313 @@ +#include "ScriptPCH.h" +#include "bot_rogue_ai.h" + +class rogue_bot : public CreatureScript +{ +public: + rogue_bot() : CreatureScript("rogue_bot") { } + + CreatureAI *GetAI(Creature *pCreature) const + { + return new rogue_botAI(pCreature); + } + +struct rogue_botAI : public bot_ai +{ + rogue_botAI(Creature *c) : bot_ai(c) + { + Reset(); + } + + int32 GC_Timer; // global cooldown + int32 BS_Timer; + int32 SinisterStrike_Timer; + int32 Eviscerate_Timer; + int32 SliceDice_Timer; + int32 Rupture_Timer; + int32 Kick_Timer; + int32 Poison_Timer; + int32 Potion_Timer; + int32 Shadowstep_Timer; + int32 Mutilate_Timer; + + uint8 energy; + uint8 comboPoints; + + Unit *opponent; + + void Reset() + { + GC_Timer = 0; + BS_Timer = 50; + Mutilate_Timer = 0; + SinisterStrike_Timer = 30; + Eviscerate_Timer = 90; + SliceDice_Timer = 75; + Rupture_Timer = 80; + Kick_Timer = 20; + Poison_Timer = 15; + Potion_Timer=0; + Shadowstep_Timer = 0; + comboPoints = 0; + energy = 100; + + opponent = NULL; + + m_creature->setPowerType(POWER_ENERGY); + m_creature->SetMaxPower(POWER_ENERGY, 100); + m_creature->SetPower(POWER_ENERGY, 100); + + if (master) { + // if (!m_creature->HasAura(61331)) m_creature->AddAura(61331,m_creature); // Aggression + // if (!m_creature->HasAura(14137)) m_creature->AddAura(14137,m_creature); // Lethality + // if (!m_creature->HasAura(14166)) m_creature->AddAura(14166,m_creature); // Improved Slice and Dice + // if (!m_creature->HasAura(14164)) m_creature->AddAura(14164,m_creature); // Improved Eviserate + + setStats(CLASS_ROGUE, m_creature->getRace(), master->getLevel()); + + if (m_creature->getLevel()>70 && !m_creature->HasAura(37169)) m_creature->AddAura(37169,m_creature); // Death Mantle item set + } + + } + + void EnterEvadeMode(){} + + void Aggro(Unit *who){} + + void MoveInLineOfSight(Unit *target){} + + bool isTimerReady(int32 timer) + { + if(timer <= 0 && GC_Timer <= 0) return true; + else return false; + } //end isTimerReady + + void doCast(Unit *victim, uint32 spellId, bool triggered = false) + { + if(spellId == 0) return; + m_creature->SetStandState(UNIT_STAND_STATE_STAND); + GC_Timer = 10; + DoCast(victim, spellId, triggered); + } //end doCast + + void decrementTimers() + { + if(GC_Timer > 0) --GC_Timer; + if(BS_Timer > 0) --BS_Timer; + if(Kick_Timer > 0) --Kick_Timer; + if(SliceDice_Timer > 0) --SliceDice_Timer; + if(SinisterStrike_Timer > 0) --SinisterStrike_Timer; + if(Eviscerate_Timer > 0) --Eviscerate_Timer; + if(Rupture_Timer > 0) --Rupture_Timer; + if(Poison_Timer > 0) --Poison_Timer; + if(Potion_Timer > 0) --Potion_Timer; + if(Shadowstep_Timer > 0) --Shadowstep_Timer; + if (Mutilate_Timer > 0) Mutilate_Timer--; + } //end decrementTImers + + void KilledUnit(Unit *) + { + ((Player*)master)->SetBotCommandState(COMMAND_FOLLOW); + } + + void AttackStart(Unit *u) + { + Aggro(u); + ScriptedAI::AttackStart(u); + } + + void UpdateAI(const uint32 diff) + { + decrementTimers(); + + if(IAmDead()) return; + + opponent = SelectUnit(SELECT_TARGET_TOPAGGRO, 0); + if(!opponent && !m_creature->getVictim()) + { + comboPoints = 0; + energy = 100; + ResetOrGetNextTarget(); + return; + } + + m_creature->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_TAUNT, true); + m_creature->ApplySpellImmune(0, IMMUNITY_EFFECT, SPELL_EFFECT_ATTACK_ME, true); + + energy += 5; + if(comboPoints > 5) comboPoints = 5; + + //interrupt any spells + if(opponent->IsNonMeleeSpellCasted(true) && energy >= 25) + { + if(isTimerReady(Kick_Timer)) + { + doCast(opponent, KICK); + Kick_Timer = 100; + energy -= 25; + } + else if(comboPoints > 0) + { + switch(comboPoints) + { + case 1: doCast(opponent, KIDNEY_SHOT_1); --comboPoints; break; + case 2: doCast(opponent, KIDNEY_SHOT_2); comboPoints-=2; break; + default: doCast(opponent, KIDNEY_SHOT_3); comboPoints-=3; break; + } + energy -= 25; + } + } + + if(!opponent->isInFrontInMap(m_creature, 5) && isTimerReady(BS_Timer)) + { + doCast(opponent, BACKSTAB); + BS_Timer = 50; + energy -= 60; + } + else if(isTimerReady(Shadowstep_Timer) && energy > 10 && m_creature->GetDistance(opponent) < 25) + { + //doCast(opponent, SHADOWSTEP); + Shadowstep_Timer = 300; + energy -= 10; + + //NPCs can't really shadowstep so fake it + float x = opponent->GetPositionX(); + float y = opponent->GetPositionY(); + float z = opponent->GetPositionZ(); + float o = opponent->GetOrientation(); + + m_creature->Relocate(x-4, y-4, z, o); + m_creature->SendMonsterMoveWithSpeed(x-4, y-4, 1, 0); + doCast(opponent, BACKSTAB); + return; + } + + if(isTimerReady(SliceDice_Timer) && comboPoints > 0 && !m_creature->HasAura(SLICE_DICE)) + { + doCast(opponent, SLICE_DICE); + + // since npcs don't use combo points, they can only case + // first level of spell. So only remove 1 combo point + --comboPoints; + + SliceDice_Timer = 75; + energy -= 25; + } + + 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 = m_creature->GetUInt32Value(UNIT_FIELD_ATTACK_POWER); + //float minDmg = m_creature->GetFloatValue(UNIT_FIELD_MINDAMAGE); + float minDmg = m_creature->GetWeaponDamageRange(BASE_ATTACK, MINDAMAGE); + int damage = irand(int32(base_attPower*5*0.03f),int32(base_attPower*5*0.07f))+minDmg+m_creature->getLevel(); + + // compensate for lack of attack power + damage = damage*(rand()%4+1); + + m_creature->CastCustomSpell(opponent, MUTILATE, &damage, NULL, NULL, false, NULL, NULL); + + //doCast (m_creature, MUTILATE); + Mutilate_Timer = 75; + ++comboPoints; + energy -= 60; + } + + if (isTimerReady(Eviscerate_Timer) && comboPoints>0) { + uint32 base_attPower = m_creature->GetUInt32Value(UNIT_FIELD_ATTACK_POWER); + //float minDmg = m_creature->GetFloatValue(UNIT_FIELD_MINDAMAGE); + float minDmg = m_creature->GetWeaponDamageRange(BASE_ATTACK, MINDAMAGE); + int damage = irand(int32(base_attPower*5*0.03f),int32(base_attPower*5*0.07f))+minDmg+m_creature->getLevel(); +//sLog->outError ("EVISCERTE: base_attPower = %u", base_attPower); +//sLog->outError("\tminDmg = %f", minDmg); +//sLog->outError("\tmaxDmg = %f", m_creature->GetWeaponDamageRange(BASE_ATTACK, MAXDAMAGE)); +//sLog->outError ("\tMINDAMAGE after setting it = %f", m_creature->GetWeaponDamageRange(BASE_ATTACK, MINDAMAGE)); + +// compensate for lack of attack power +//sLog->outError ("\tdamage before = %u", damage); + damage = damage*(rand()%4+1); +//sLog->outError ("\tdamage after = %u", damage); + // Eviscerate and Envenom Bonus Damage (Deathmantle item set effect) + if(m_creature->HasAura(37169)) + damage += comboPoints*40; + + m_creature->CastCustomSpell(opponent, EVISCERATE, &damage, NULL, NULL, false, NULL, NULL); + //doCast(opponent, EVISCERATE); + comboPoints=0; + + energy -= 30; + Eviscerate_Timer = 90; + // return; + } + + if(isTimerReady(SinisterStrike_Timer) && comboPoints < 5) + { + doCast(opponent, SINISTER_STRIKE); + //m_creature->Say("sinister strike", LANG_UNIVERSAL, NULL); + ++comboPoints; + SinisterStrike_Timer = 20; + energy -= 40; + } + + if(isTimerReady(Rupture_Timer)) + { + doCast(opponent, RUPTURE); + comboPoints = 0; + Rupture_Timer = 80; + energy -= 40; + } + + /*if(!opponent->HasAuraType(SPELL_AURA_MOD_DISARM)) + doCast(opponent, DISMANTLE);*/ + + /* + since npc can't really use poison, we'll pretend that we were able to poison his blades. + */ + if(isTimerReady(Poison_Timer)) + { + //Deadly Poison has 40% chance of proccing + switch(rand()%5) + { + case 0: + case 1: + case 2: + break; + case 4: + case 5: + if(DEADLY_POISON) DoCast(opponent, DEADLY_POISON, true); break; + } + + //Wound Poison has 50% chance of procing + switch(rand()%2) + { + case 0: + break; + case 1: + if(WOUND_POISON) DoCast(opponent, WOUND_POISON, true); break; + break; + } + + Poison_Timer = 15; + } + + //if low on health, drink a potion + if(m_creature->GetHealth() < m_creature->GetMaxHealth()*0.6 && isTimerReady(Potion_Timer)) + { + doCast(m_creature, HEALINGPOTION); + Potion_Timer = 1500; + } + + m_creature->SetPower(POWER_ENERGY, energy); + + ScriptedAI::UpdateAI(diff); + } //end UpdateAI + + +}; //end rogue_bot +}; + +void AddSC_rogue_bot() +{ + new rogue_bot(); +} diff --git a/src/server/scripts/Bots/bot_rogue_ai.h b/src/server/scripts/Bots/bot_rogue_ai.h new file mode 100644 index 0000000..c7a295a --- /dev/null +++ b/src/server/scripts/Bots/bot_rogue_ai.h @@ -0,0 +1,30 @@ +#include "bot_ai.h" + + +#define BACKSTAB BACKSTAB_A[SPELL_LEVEL] +#define SINISTER_STRIKE SINISTER_STRIKE_A[SPELL_LEVEL] +#define SLICE_DICE SLICE_DICE_A[SPELL_LEVEL] +#define EVISCERATE EVISCERATE_A[SPELL_LEVEL] +#define KICK KICK_A[SPELL_LEVEL] +#define RUPTURE RUPTURE_A[SPELL_LEVEL] +#define WOUND_POISON WOUND_POISON_A[SPELL_LEVEL] +#define DEADLY_POISON DEADLY_POISON_A[SPELL_LEVEL] +#define DISMANTLE DISMANTLE_A[SPELL_LEVEL] +#define KIDNEY_SHOT_1 8643 +#define KIDNEY_SHOT_2 30832 +#define KIDNEY_SHOT_3 41389 +#define SHADOWSTEP SHADOWSTEP_A[SPELL_LEVEL] +#define MUTILATE MUTILATE_A[SPELL_LEVEL] + + +uint32 MUTILATE_A[] = {0, 0, 0, 0, 48664, 48664, 48664,48664, 48664}; +uint32 BACKSTAB_A[] = { 53, 2589, 2591, 8721, 11279, 11280, 11281, 25300, 25300 }; +uint32 SINISTER_STRIKE_A[] = { 1757, 1758, 1759, 8621, 11293, 11294, 26862, 48638, 48638 }; +uint32 SLICE_DICE_A[] = { 0, 5171, 5171, 5171, 6774, 6774, 6774, 6774, 6774, 6774 }; +uint32 EVISCERATE_A[]= { 11300, 11300, 11300, 11300, 11300, 11300, 11300, 11300, 11300 }; +uint32 KICK_A[] = { 0, 1766, 1767, 1767, 1768, 1769, 38768, 38768, 38768 }; +uint32 RUPTURE_A[] = { 0, 0, 1943, 8640, 11273, 11274, 11275, 26867, 48672, 48672 }; +uint32 WOUND_POISON_A[] = { 0, 0, 0, 13218, 13222, 13223, 13224, 27189, 57974, 57974 }; +uint32 DEADLY_POISON_A[] = { 0, 0, 0, 2818, 2819, 11354, 26968, 57969, 57969 }; +uint32 DISMANTLE_A[] = { 0, 0, 51722, 51722, 51722, 51722, 51722, 51722, 51722 }; +uint32 SHADOWSTEP_A[] = { 0, 0, 0, 0, 0, 36554, 36554, 36554, 36554 }; diff --git a/src/server/scripts/Bots/bot_shaman_ai.cpp b/src/server/scripts/Bots/bot_shaman_ai.cpp new file mode 100644 index 0000000..398ca51 --- /dev/null +++ b/src/server/scripts/Bots/bot_shaman_ai.cpp @@ -0,0 +1,385 @@ +#include "ScriptPCH.h" +#include "bot_shaman_ai.h" + +class shaman_bot : public CreatureScript +{ +public: + shaman_bot() : CreatureScript("shaman_bot") { } + + CreatureAI *GetAI(Creature *pCreature) const + { + return new shaman_botAI(pCreature); + } + +struct shaman_botAI : public bot_ai +{ + shaman_botAI(Creature *c) : bot_ai(c) + { + Reset(); + } + + int32 GC_Timer; //global cooldown + int32 Heal_Timer; + int32 Lesser_Healing_Timer; + int32 Self_Lesser_Healing_Timer; + int32 Flame_Shock_Timer; + int32 Earth_Shock_Timer; + int32 Lightning_Bolt_Timer; + int32 Others_Heal_Timer; + int32 Oom_timer; + int32 Potion_Timer; + int32 Rez_Timer; + int32 Earth_Totem_Timer; + int32 Water_Totem_Timer; + int32 Fire_Totem_Timer; + int32 Wind_Totem_Timer; + + Unit *mobsTarget; + Unit *opponent; + + void Reset() + { + GC_Timer = 0; + Heal_Timer = 0; + Lesser_Healing_Timer = 0; + Self_Lesser_Healing_Timer = 0; + Flame_Shock_Timer = 20; + Lightning_Bolt_Timer = 60; + Earth_Shock_Timer = 150; + Others_Heal_Timer = 0; + Oom_timer = 0; + + Earth_Totem_Timer = 0; + Fire_Totem_Timer = 0; + Water_Totem_Timer = 0; + Wind_Totem_Timer = 0; + + Potion_Timer = 0; + Rez_Timer = 0; + + opponent = NULL; + + if (master) { + setStats(CLASS_SHAMAN, m_creature->getRace(), master->getLevel()); + } + } + + bool isTimerReady(int32 timer) + { + if(timer <= 0 && GC_Timer <= 0) return true; + else return false; + } //end isTimerReady + + void doCast(Unit *victim, uint32 spellId, bool triggered = false) + { + if(spellId == 0) return; + m_creature->SetStandState(UNIT_STAND_STATE_STAND); + GC_Timer = 40; + DoCast(victim, spellId, triggered); + } //end doCast + + void decrementTimers() + { + if(GC_Timer > 0) --GC_Timer; + if(Heal_Timer > 0) --Heal_Timer; + if(Others_Heal_Timer > 0) --Others_Heal_Timer; + if(Flame_Shock_Timer > 0) --Flame_Shock_Timer; + if(Earth_Shock_Timer > 0) --Earth_Shock_Timer; + if(Lightning_Bolt_Timer > 0) --Lightning_Bolt_Timer; + if(Rez_Timer > 0) --Rez_Timer; + if(Potion_Timer > 0) --Potion_Timer; + if(Earth_Totem_Timer > 0) --Earth_Totem_Timer; + if(Fire_Totem_Timer > 0) --Fire_Totem_Timer; + if(Water_Totem_Timer > 0) --Water_Totem_Timer; + if(Wind_Totem_Timer > 0) --Wind_Totem_Timer; + } //end decrementTImers + + void KilledUnit(Unit *) + { + ((Player*)master)->SetBotCommandState(COMMAND_FOLLOW); + } + + void Aggro(Unit *who){} + + void EnterEvadeMode(){ Oom_timer = 0; } + + void UpdateAI(const uint32 diff) + { + decrementTimers(); + + if(IAmDead()) return; + + if(m_creature->GetPower(POWER_MANA) < 400 && + isTimerReady(Potion_Timer)) + { + doCast(m_creature, MANAPOTION); + Potion_Timer = 150; + } + if(m_creature->GetPower(POWER_MANA)/m_creature->GetMaxPower(POWER_MANA) < 10) + { + if(Oom_timer == 0) + { + //MonsterSay("OOM", LANG_UNIVERSAL, NULL); + Oom_timer = 1; + } + } + + BuffAndHealGroup(master); + + // Heal myself + HealTarget (m_creature, m_creature->GetHealth()*100 / m_creature->GetMaxHealth()); + + //the rest are combat so return if not fighting + opponent = SelectUnit(SELECT_TARGET_TOPAGGRO, 0); + if(!opponent && !m_creature->getVictim()) + { + DoNonCombatActions(); + ResetOrGetNextTarget(); + return; + } + + //Cast totems. + if(m_creature->isInCombat() && + isTimerReady(Earth_Totem_Timer) && + !master->HasAura(SPELL_STONESKIN_AURA, 0)) + { + doCast(m_creature, SPELL_STONESKIN_TOTEM); + GiveManaBack(); + Earth_Totem_Timer = 90; + return; + } + + if(m_creature->isInCombat() && + isTimerReady(Fire_Totem_Timer)) + { + doCast(m_creature, SPELL_SEARING_TOTEM); + GiveManaBack(); + Fire_Totem_Timer = 180; + return; + } + + if(m_creature->isInCombat() && + isTimerReady(Wind_Totem_Timer)) + { + doCast(m_creature, SPELL_WINDFURY_TOTEM); + GiveManaBack(); + Wind_Totem_Timer = 180; + return; + } + + if(m_creature->isInCombat()) + { + switch(master->getClass()) + { + case CLASS_WARRIOR: + case CLASS_DEATH_KNIGHT: + case CLASS_ROGUE: + { + if(isTimerReady(Water_Totem_Timer) && + !master->HasAura(SPELL_HEALINGSTREAM_AURA)) + { + doCast(m_creature, SPELL_HEALINGSTREAM_TOTEM); + Water_Totem_Timer = 90; + GiveManaBack(); + return; + } + break; + } + default: //everyone else gets a mana totem + { + if(isTimerReady (Water_Totem_Timer) && + !master->HasAura(SPELL_MANASPRING_AURA, 0)) + { + doCast(m_creature, SPELL_MANASPRING_TOTEM); + Water_Totem_Timer = 90; + GiveManaBack(); + return; + } + } + } //end switch + + } + + + if(isTimerReady(Flame_Shock_Timer)) + { + doCast(opponent, SPELL_FLAME_SHOCK); + Flame_Shock_Timer = 120; + return; + } + + if(isTimerReady(Lightning_Bolt_Timer)) + { + doCast(opponent, SPELL_LIGHTNING_BOLT); + Lightning_Bolt_Timer = 180; + return; + } + + if(isTimerReady(Earth_Shock_Timer)) + { + doCast(opponent, SPELL_EARTH_SHOCK); + Earth_Shock_Timer = 250; + return; + } + + //now try to heal bots and pets. DoSelectLowestHpFriendly will get + //everyone in group including bots and pets. Unfortunately it can + //not be triggered for % of lost HP, so we just set it to -1000. + //This means low level players wont be healed because they wont have + //enough HP. + Unit *target = DoSelectLowestHpFriendly(40, 1000); + if(target) + { + if(CanCast(target, GetSpellStore()->LookupEntry (SPELL_CHAIN_HEAL))) + { + doCast(target, SPELL_CHAIN_HEAL, false); + Others_Heal_Timer = 50; + } + } else { + target = DoSelectLowestHpFriendly(40, 500); //now try someone with less HP lost + if(target) + { + if(CanCast(target, GetSpellStore()->LookupEntry (SPELL_CHAIN_HEAL))) + { + doCast(target, SPELL_CHAIN_HEAL, false); + Others_Heal_Timer = 100; + } + } + } + + ScriptedAI::UpdateAI(diff); + + } //end UpdateAI + + void DoNonCombatActions() + { + Feast(); + + if(isTimerReady(GC_Timer) && !m_creature->HasAura(SPELL_LIGHTNING_SHIELD, 0)) + doCast(m_creature, SPELL_LIGHTNING_SHIELD); + + //Casts buffs + // if(!m_creature->isInCombat()) + // { + //if(!m_creature->HasAura(SPELL_WINDFURY_WEAPON, 0)) doCast(m_creature, SPELL_WINDFURY_WEAPON); + // if(isTimerReady(GC_Timer) && !m_creature->HasAura(SPELL_LIGHTNING_SHIELD, 0)) + // doCast(m_creature, SPELL_LIGHTNING_SHIELD); + // } + + + //Heal/rez others + // + //check group members, this doesn't check bots/pets. They will be done later. Preference + //goes to real players first. + // + //buff and heal group + if(master->GetGroup()) + { + RezGroup(SPELL_SHAMAN_REZZ, master); + BuffAndHealGroup(master); + // CureGroup(master); + } + + + /* + Group::MemberSlotList const &a =((Player*)master)->GetGroup()->GetMemberSlots(); + for(Group::member_citerator itr = a.begin(); itr != a.end(); itr++) + { + Player *tPlayer = ((Player *)master)->GetObjPlayer(itr->guid); + if(tPlayer == NULL) continue; + //healing others + if(tPlayer->isAlive() && + isTimerReady(Others_Heal_Timer) && + tPlayer->GetGUID() != master->GetGUID() && + tPlayer->GetHealth()*100 / tPlayer->GetMaxHealth() < 75 && + CanCast(tPlayer, GetSpellStore()->LookupEntry (SPELL_CHAIN_HEAL))) + { + doCast(tPlayer, SPELL_CHAIN_HEAL, false); + Others_Heal_Timer = 100; + } + + //rezzes + if(tPlayer->isDead() && + !m_creature->isInCombat() && + //CanCast(tPlayer, GetSpellStore()->LookupEntry (SPELL_SHAMAN_REZZ)) && + m_creature->GetDistance(tPlayer) < 40 && + isTimerReady(Rez_Timer)) + { + char *str = (char *)malloc(32); + sprintf(str, "Rezzing %s", tPlayer->GetName()); + m_creature->MonsterSay(str, LANG_UNIVERSAL, NULL); + free(str); + doCast(tPlayer, SPELL_SHAMAN_REZZ, false); + Rez_Timer = 160; + } + } + */ + + + +/* + if((master->GetHealth()*100 / master->GetMaxHealth() < 90) && Lesser_Healing_Timer <= 0) + { + doCast(master, SPELL_LESSER_HEALING); + Lesser_Healing_Timer = 90; + Heal_Timer = Heal_Timer + 5; //wait 5 seconds before casting a real heal + //if(master->isInCombat()) && master->getVictim() == NULL) return; + return; + } else if(Lesser_Healing_Timer >= 0) --Lesser_Healing_Timer; + + if((master->GetHealth()*100 / master->GetMaxHealth() < 75) && isTimerReady(Heal_Timer)) + { + doCast(master, SPELL_CHAIN_HEAL); + Heal_Timer = 10; + } + + if(m_creature->GetHealth()*100 / m_creature->GetMaxHealth() < 90) + { + if(Self_Lesser_Healing_Timer <= 0) + { + doCast(m_creature, SPELL_LESSER_HEALING); + Self_Lesser_Healing_Timer = 90; + return; + } else if(Self_Lesser_Healing_Timer >= 0) + --Self_Lesser_Healing_Timer; + } +*/ + } + + bool HealTarget(Unit *target, uint8 hp) + { + if (!isTimerReady(GC_Timer)) return false; + if (m_creature->IsNonMeleeSpellCasted(true)) return false; + if(!target || target->isDead()) return false; + + if(hp < 90 && Lesser_Healing_Timer <= 0) + { + doCast(target, SPELL_LESSER_HEALING); + Lesser_Healing_Timer = 90; + Heal_Timer = Heal_Timer + 5; //wait 5 seconds before casting a real heal + //if(master->isInCombat()) && master->getVictim() == NULL) return; + return true; + } else if(Lesser_Healing_Timer >= 0) --Lesser_Healing_Timer; + + if(hp < 75 && isTimerReady(Heal_Timer)) + { + doCast(target, SPELL_CHAIN_HEAL); + Heal_Timer = 10; + } + return true; + } //end HealTarget + + void ReceiveBowEmote(Player *player) + { + doCast(m_creature, SPELL_MANASPRING_TOTEM); + } + + +}; //end shaman_bot +}; + + +void AddSC_shaman_bot() +{ + new shaman_bot(); +} diff --git a/src/server/scripts/Bots/bot_shaman_ai.h b/src/server/scripts/Bots/bot_shaman_ai.h new file mode 100644 index 0000000..0574c5d --- /dev/null +++ b/src/server/scripts/Bots/bot_shaman_ai.h @@ -0,0 +1,48 @@ +#include "bot_ai.h" + +#define SPELL_CHAIN_HEAL SPELL_CHAIN_HEAL_A[SPELL_LEVEL] +#define SPELL_LESSER_HEALING SPELL_LESSER_HEALING_A[SPELL_LEVEL] + +#define SPELL_SHAMAN_REZZ SPELL_SHAMAN_REZZ_A[SPELL_LEVEL] + +//Nukes +#define SPELL_FLAME_SHOCK SPELL_FLAME_SHOCK_A[SPELL_LEVEL] +#define SPELL_LIGHTNING_BOLT SPELL_LIGHTNING_BOLT_A[SPELL_LEVEL] +#define SPELL_EARTH_SHOCK SPELL_EARTH_SHOCK_A[SPELL_LEVEL] + +//BUFFS +//#define SPELL_WINDFURY_WEAPON 10486 //rank 3 +#define SPELL_LIGHTNING_SHIELD SPELL_LIGHTNING_SHIELD_A[SPELL_LEVEL] +#define SPELL_STONESKIN_AURA SPELL_STONESKIN_AURA_A[SPELL_LEVEL] +#define SPELL_HEALINGSTREAM_AURA SPELL_HEALINGSTREAM_AURA_A[SPELL_LEVEL] +#define SPELL_MANASPRING_AURA SPELL_MANASPRING_AURA_A[SPELL_LEVEL] + +//Totems +#define SPELL_STONESKIN_TOTEM SPELL_STONESKIN_TOTEM_A[SPELL_LEVEL] +#define SPELL_HEALINGSTREAM_TOTEM SPELL_HEALINGSTREAM_TOTEM_A[SPELL_LEVEL] +#define SPELL_MANASPRING_TOTEM SPELL_MANASPRING_TOTEM_A[SPELL_LEVEL] +#define SPELL_SEARING_TOTEM SPELL_SEARING_TOTEM_A[SPELL_LEVEL] +#define SPELL_WINDFURY_TOTEM SPELL_WINDFURY_TOTEM_A[SPELL_LEVEL] + +uint32 SPELL_CHAIN_HEAL_A[] = { 0, 0, 0, 0, 1064, 10623, 25422, 25423, 55459, 55459 }; +uint32 SPELL_LESSER_HEALING_A[] = { 0, 0, 8004, 8010, 10466, 10467, 10468, 25420, 49275, 49275 }; + +uint32 SPELL_LIGHTNING_BOLT_A[] = { 403, 548, 915, 6041, 10392, 15207, 25448, 45296, 45296 }; +uint32 SPELL_FLAME_SHOCK_A[] = { 0, 8050, 8052, 8053, 10447, 10448, 29228, 25457, 49232, 49232 }; +uint32 SPELL_EARTH_SHOCK_A[] = { 8042, 8045, 8046, 10412, 10413, 10414, 10414, 25454, 49230, 49230 }; + +uint32 SPELL_LIGHTNING_SHIELD_A[] = {324, 325, 905, 945, 8134, 10431, 25469, 25472, 49280, 49280 }; +uint32 SPELL_SHAMAN_REZZ_A[] = {0, 2008, 20609, 20610, 20776, 20776, 20777, 25590, 49277, 49277 }; + +uint32 SPELL_STONESKIN_AURA_A[] = { 8072, 8156, 8156, 10403, 10404, 10405, 25506, 58752, 58752, 58752 }; +uint32 SPELL_STONESKIN_TOTEM_A[] = { 8071, 8154, 8155, 10406, 10407, 10408, 25508, 25509, 25509, 25509 }; + +uint32 SPELL_HEALINGSTREAM_AURA_A[] = { 0, 0, 5672, 6371, 6372, 10460, 10461, 25566, 58765, 58765 }; +uint32 SPELL_HEALINGSTREAM_TOTEM_A[] = { 0, 0, 5394, 6375, 6377, 10462, 10463, 25567, 58757, 58757 }; + +uint32 SPELL_MANASPRING_AURA_A[] = { 0, 0, 5677, 10491, 10493, 10494, 25569, 25569, 58775, 58775 }; +uint32 SPELL_MANASPRING_TOTEM_A[] = { 0, 0, 5675, 10495, 10496, 10497, 25570, 25570, 58771, 58771 }; + +uint32 SPELL_SEARING_TOTEM_A[] = { 0, 3599, 6363, 6364, 6365, 10437, 10438, 25533, 58699, 58699 }; + +uint32 SPELL_WINDFURY_TOTEM_A[] = { 0, 0, 0, 0, 8512, 8512, 8512, 8512, 8512, 8512 }; diff --git a/src/server/scripts/Bots/bot_warlock_ai.cpp b/src/server/scripts/Bots/bot_warlock_ai.cpp new file mode 100644 index 0000000..7619575 --- /dev/null +++ b/src/server/scripts/Bots/bot_warlock_ai.cpp @@ -0,0 +1,292 @@ +#include "ScriptPCH.h" +#include "bot_warlock_ai.h" + + +class warlock_bot : public CreatureScript +{ +public: + warlock_bot() : CreatureScript("warlock_bot") { } + + CreatureAI *GetAI(Creature *pCreature) const + { + return new warlock_botAI(pCreature); + } + + struct warlock_botAI : public bot_ai + { + warlock_botAI(Creature *c) :bot_ai(c) + { + Reset(); + pet = NULL; + } + + bool oom_spam; + + uint8 state; + uint8 next_state; + uint32 next_state_timer; + + Creature *pet; + Unit *opponent; + + void Reset() + { + FirstAid_cd = 0; + GC_Timer = 0; + + conflagarate_cd = 0; + chaos_bolt_cd = 0; + + oom_spam = false; + + uint8 state = 1; + next_state = 0; + next_state_timer = 0; + + opponent = NULL; + + if (master) { + setStats(CLASS_WARLOCK, m_creature->getRace(), master->getLevel()); + + if (!m_creature->HasAura(56235)) m_creature->AddAura(56235,m_creature); // Glyph of Conflagrate + if (!m_creature->HasAura(63302)) m_creature->AddAura(63302,m_creature); // Glyph of Haunt + if (!m_creature->HasAura(17834)) m_creature->AddAura(17834,m_creature); // Improved Immolation + if (!m_creature->HasAura(17814)) m_creature->AddAura(17814,m_creature); // Improved Corruption + + } + } + + void CreatePet() + { + pet = master->GetBotsPet(60237); + + if(pet == NULL) + return; + + pet->UpdateCharmAI(); + pet->setFaction(m_creature->getFaction()); + pet->SetReactState(REACT_DEFENSIVE); + pet->GetMotionMaster()->MoveFollow(m_creature, 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.0f + pet->GetStat(STAT_STRENGTH)*2.0f; + + 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)*2+master->getLevel()); + pet->UpdateDamagePhysical(BASE_ATTACK); + + } + + void UpdateAI(const uint32 diff) + { + + ReduceCD(); + + if(IAmDead()) return; + + if(pet && pet != NULL && pet->isDead()) + { + master->SetBotsPetDied(); + pet = NULL; + } + + //if we think we have a pet, but master doesn't, it means we teleported + if(pet && master->m_botHasPet == false) + { + master->SetBotsPetDied(); + pet = NULL; + } + + m_creature->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_TAUNT, true); + m_creature->ApplySpellImmune(0, IMMUNITY_EFFECT, SPELL_EFFECT_ATTACK_ME, true); + + if(m_creature->GetHealth() < m_creature->GetMaxHealth()*0.3 && isTimerReady(Potion_cd)) + { + doCast(m_creature, HEALINGPOTION); + Potion_cd = Potion_cd; + } + if(m_creature->GetPower(POWER_MANA) < m_creature->GetMaxPower(POWER_MANA)*0.2) + { + if(isTimerReady(Potion_cd)) + { + doCast(m_creature, MANAPOTION); + //MonsterSay("MANA POTION", LANG_UNIVERSAL, NULL); + Potion_cd = Potion_cd; + } else { + if(oom_spam == false) + { + //MonsterSay("OOM", LANG_UNIVERSAL, NULL); + oom_spam = true; + } + ScriptedAI::UpdateAI(diff); + //return; //can't do anything without mana + } + } + oom_spam = false; + + ScriptedAI::UpdateAI(diff); + + opponent = SelectUnit(SELECT_TARGET_TOPAGGRO, 0); + if(!opponent && !m_creature->getVictim()) + { + ResetOrGetNextTarget(); + + //to reduce the number of crashes, remove pet whenever we are not in combat + if(pet != NULL && pet->isAlive()) + { + master->SetBotsPetDied(); + pet = NULL; + } + + return; + } + + if(pet == NULL) + CreatePet(); + + if (pet && pet->isAlive() && + !pet->isInCombat() && + m_creature->getVictim()) { + pet->Attack (m_creature->getVictim(), true); + pet->GetMotionMaster()->MoveChase(m_creature->getVictim(), 1, 0); + } + + if(m_creature->HasUnitState(UNIT_STAT_CASTING)) + return; + + DoNormalAttack(diff); + } + + void Aggro(Unit *who){} + + void JustDied(Unit *Killer) + { + master->SetBotCommandState(COMMAND_FOLLOW); + state = 1; + next_state = 0; + next_state_timer = 0; + if(pet && pet != NULL) + { + master->SetBotsPetDied(); + pet = NULL; + } + } + + void AttackStart(Unit *u) + { + Aggro(u); + ScriptedAI::AttackStartCaster(u, 25); + } + + void KilledUnit(Unit *) + { + ((Player*)master)->SetBotCommandState(COMMAND_FOLLOW); + if(pet && pet->isAlive()) pet->GetMotionMaster()->MoveFollow(m_creature, PET_FOLLOW_DIST*urand(1, 2), PET_FOLLOW_ANGLE); + } + + void DoNormalAttack(const uint32 diff) + { + AttackerSet m_attackers = master->getAttackers(); + if(opponent == NULL) return; + if(opponent->isDead()) return; + + //double check that pet didn't just die + if(pet && pet != NULL && pet->isDead()) + { + master->SetBotsPetDied(); + pet = NULL; + } + + //send in the pet + if(pet && pet != NULL && pet->isAlive() && !pet->isInCombat()) pet->AI()->AttackStart(opponent); + + if(!isTimerReady(GC_Timer)) return; + + if(opponent->HasUnitMovementFlag(UNIT_FLAG_FLEEING)) + { + //MonsterSay("Mob is fleeing!", LANG_UNIVERSAL, NULL); + return; + } + + + //if(RAIN_OF_FIRE && m_attackers.size() > 1) + //{ + //doCast(opponent, RAIN_OF_FIRE); + //return; + //} + if(CURSE_OF_THE_ELEMENTS && !HasAuraName(opponent, CURSE_OF_THE_ELEMENTS)) + { + doCast(opponent, CURSE_OF_THE_ELEMENTS); + GiveManaBack(); + //return; + } + + if(CORRUPTION && !HasAuraName (opponent, CORRUPTION, m_creature->GetGUID())) + { + doCast(opponent, CORRUPTION); + GiveManaBack(); + //return; + } + + if(HAUNT && !HasAuraName (opponent, HAUNT, m_creature->GetGUID())) + { + doCast(opponent, HAUNT); + GiveManaBack(); + return; + } + + if(UNSTABLE_AFFLICTION && !HasAuraName (opponent, UNSTABLE_AFFLICTION, m_creature->GetGUID())) + { + doCast(opponent, UNSTABLE_AFFLICTION); + GiveManaBack(); + return; + } + + if(!HasAuraName(opponent, IMMOLATE, m_creature->GetGUID())) + { + doCast(opponent, IMMOLATE); + GiveManaBack(); + return; + } else if(CONFLAGRATE && isTimerReady(conflagarate_cd)) + { + doCast(opponent, CONFLAGRATE); + conflagarate_cd = CONFLAGRATE_CD; + GiveManaBack(); + return; + } + + if(CHAOS_BOLT && isTimerReady(chaos_bolt_cd)) + { + doCast(opponent, CHAOS_BOLT); + GiveManaBack(); + chaos_bolt_cd = CHAOS_BOLT_CD; + return; + } else + doCast(opponent, SHADOW_BOLT); + GiveManaBack(); + } //DoNormalAttack + + void ReduceCD() + { + if(Potion_cd > 0) --Potion_cd; + if(FirstAid_cd > 0) --FirstAid_cd; + if(GC_Timer > 0) --GC_Timer; + + if(conflagarate_cd > 0) --conflagarate_cd; + if(chaos_bolt_cd > 0) --chaos_bolt_cd; + } + }; +}; + +void AddSC_warlock_bot() +{ + new warlock_bot(); +} diff --git a/src/server/scripts/Bots/bot_warlock_ai.h b/src/server/scripts/Bots/bot_warlock_ai.h new file mode 100644 index 0000000..f0dc1e2 --- /dev/null +++ b/src/server/scripts/Bots/bot_warlock_ai.h @@ -0,0 +1,41 @@ +#include "bot_ai.h" + + +uint32 conflagarate_cd; +uint32 chaos_bolt_cd; + +#define CONFLAGRATE_CD 100 +#define CHAOS_BOLT_CD 120 +#define RAIN_OF_FIRE_CD 300 + +#define PET_VOIDWALKER 697 + +//Curses +#define CURSE_OF_THE_ELEMENTS SPELL_CURSE_OF_THE_ELEMENTS_A[SPELL_LEVEL] + +//DESTRUCTION +#define SHADOW_BOLT SPELL_SHADOW_BOLT_A[SPELL_LEVEL] +#define IMMOLATE SPELL_IMMOLATE_A[SPELL_LEVEL] +#define CONFLAGRATE SPELL_CONFLAGRATE_A[SPELL_LEVEL] +#define CHAOS_BOLT SPELL_CHAOS_BOLT_A[SPELL_LEVEL] +#define RAIN_OF_FIRE SPELL_RAIN_OF_FIRE_A[SPELL_LEVEL] + +//AFFLICTION +#define HAUNT SPELL_HAUNT_A[SPELL_LEVEL] +#define CORRUPTION SPELL_CORRUPTION_A[SPELL_LEVEL] +#define UNSTABLE_AFFLICTION SPELL_UNSTABLE_AFFLICTION_A[SPELL_LEVEL] + +//curses +uint32 SPELL_CURSE_OF_THE_ELEMENTS_A[] = { 0, 0, 0, 1490, 11721, 11721, 11722, 27728, 47865 }; + +//destruction spells +uint32 SPELL_SHADOW_BOLT_A[] = { 686, 705, 1088, 7641, 11659, 11660, 25307, 47808, 47809 }; +uint32 SPELL_IMMOLATE_A[] = { 348, 707, 1094, 2941, 11665, 11667, 25309, 47810, 47811 }; +uint32 SPELL_CONFLAGRATE_A[] = { 0, 0, 0, 0, 17962, 17962, 17962, 17962, 17962 }; +uint32 SPELL_CHAOS_BOLT_A[] = { 0, 0, 0, 0, 0, 50796, 50796, 50796, 50796 }; +uint32 SPELL_RAIN_OF_FIRE_A[] = { 0, 0, 5740, 6219, 11677, 11678, 11678, 27212, 27212 }; + +//affliction spells +uint32 SPELL_HAUNT_A[] = { 0, 0, 0, 0, 0, 0, 59164, 59164, 59164 }; +uint32 SPELL_CORRUPTION_A[] = { 172, 6222, 7648, 11671, 11672, 25311, 47812, 47835, 47836 }; +uint32 SPELL_UNSTABLE_AFFLICTION_A[] = { 0, 0, 0, 0, 0, 30404, 30405, 47843, 47843 }; diff --git a/src/server/scripts/Bots/bot_warrior_ai.cpp b/src/server/scripts/Bots/bot_warrior_ai.cpp new file mode 100644 index 0000000..f424528 --- /dev/null +++ b/src/server/scripts/Bots/bot_warrior_ai.cpp @@ -0,0 +1,501 @@ + /* ScriptData + SDName: pvp_warrior + SD%Complete: 0 + SDComment: paytheo help from Gasilli + SDCategory: Custom + EndScriptData */ +#include "ScriptPCH.h" +//#include "../../game/Player.h" +#include "bot_warrior_ai.h" + +bool castDemoralizingShout; +bool battleStance; +bool defensiveStance; +bool berserkerStance; + +class warrior_bot : public CreatureScript +{ +public: + warrior_bot() : CreatureScript("warrior_bot") { } + + CreatureAI *GetAI(Creature *pCreature) const + { + return new warrior_botAI(pCreature); + } + + struct warrior_botAI : public bot_ai + { + warrior_botAI(Creature *c) : bot_ai(c) + { + Reset(); + } + + uint32 charge_cd; + uint32 deathWish_cd; + uint32 mortalStrike_cd; + uint32 overpower_cd; + uint32 retaliation_recklessness_shieldwall_cd; + uint32 berserkerRage_cd; + uint32 challengingShout_cd; + uint32 battleShout_cd; + uint32 intercept_cd; + uint32 intimidatingShout_cd; + uint32 pummel_cd; + uint32 whirlwind_cd; + uint32 bloodrage_cd; + uint32 disarm_cd; + uint32 intervene_cd; + uint32 shieldBash_cd; + uint32 spellReflection_cd; + uint32 potion_cd; + uint32 firstAid_cd; + uint32 pvpTrinket_cd; + uint32 taunt_cd; + uint32 sunder_cd; + uint32 rage; + uint32 yellRage; + uint32 GCD; + int32 Noggenfogger_Timer; + + + void Reset() + { + charge_cd = 0; + deathWish_cd = 0; + mortalStrike_cd = 0; + overpower_cd = 0; + retaliation_recklessness_shieldwall_cd = 0; + berserkerRage_cd = 0; + challengingShout_cd = 0; + battleShout_cd = 0; + intercept_cd = 0; + intimidatingShout_cd = 0; + pummel_cd = 0; + whirlwind_cd = 0; + bloodrage_cd = 0; + disarm_cd = 0; + intervene_cd = 0; + shieldBash_cd = 0; + spellReflection_cd = 0; + potion_cd = 0; + firstAid_cd = 0; + pvpTrinket_cd = 0; + taunt_cd = 0; + sunder_cd = 0; + rage = 0; + yellRage = 0; + GCD = 0; + castDemoralizingShout = false; + battleStance = true; + defensiveStance = false; + berserkerStance = false; + + Noggenfogger_Timer = 0; + m_creature->setPowerType(POWER_RAGE); + + if (master) { + setStats(CLASS_WARRIOR, m_creature->getRace(), master->getLevel()); + } + } + + void EnterEvadeMode(){} + + void doCast(Unit *victim, uint32 spellId, bool triggered = false) + { + if(spellId == 0) return; + + GCD = 2000; + DoCast(victim, spellId, triggered); + } //end doCast + + bool isTimerReady(int32 timer, uint32 diff) + { + if(timer <= 0 && GCD < diff) return true; + else return false; + } + + + void UpdateAI(const uint32 diff) + { + ReduceCD(diff); + + if(IAmDead()) return; + + //Use Noggenfogger potion if a tauren + if(master->GetBotRace() == RACE_TAUREN && + isTimerReady(Noggenfogger_Timer, diff)) + { + uint64 m_rand = urand(1, 2); + switch(m_rand) + { + case 1: + doCast(m_creature, SPELL_NOGGENFOGGER_SKELETON, true); + break; + case 2: + doCast(m_creature, SPELL_NOGGENFOGGER_SMALL, true); + break; + } + Noggenfogger_Timer = 6000; //10 minutes + } + + + Unit *opponent = SelectUnit(SELECT_TARGET_TOPAGGRO, 0); + if(!opponent && !m_creature->getVictim()) + { + ResetOrGetNextTarget(); + if(rage > 0) + { + --rage; + //m_creature->SetPower(POWER_RAGE, rage*10); + } + return; + } + + rage = m_creature->GetPower(POWER_RAGE); + + m_creature->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_TAUNT, true); + m_creature->ApplySpellImmune(0, IMMUNITY_EFFECT, SPELL_EFFECT_ATTACK_ME, true); + + if(m_creature->GetHealth() < m_creature->GetMaxHealth()*0.2 && + potion_cd<diff && GCD < diff) + { + doCast(m_creature, HEALINGPOTION); + potion_cd = POTIONCD; + } + + if(!m_creature->HasAura(BATTLESHOUT) && rage > 10 && GCD < diff && + battleShout_cd<diff) + { + doCast(m_creature, BATTLESHOUT); + //rage -= 10; + battleShout_cd = BATTLESHOUT_CD; + } + + //if(rage > 100) + //{ + //rage = 0; + //} + + if(bloodrage_cd < diff && + m_creature->isInCombat() && + opponent && + GCD < diff) + { + doCast(m_creature, BLOODRAGE); + bloodrage_cd = BLOODRAGE_CD; + //rage += 10; + } + + if(!m_creature->SelectVictim() || !m_creature->getVictim()) + { + ResetOrGetNextTarget(); + return; + } + + BreakCC(diff); + Attack(diff); + + ScriptedAI::UpdateAI(diff); + } + + + void AttackStart(Unit *u) + { + Aggro(u); + ScriptedAI::AttackStart(u); + } + + void Aggro(Unit *who) + { + //Unit *opponent = SelectUnit(SELECT_TARGET_TOPAGGRO, 0); + Unit *opponent = who; + + if(!opponent) return; + + if((m_creature->GetDistance(opponent) > 15) && + (m_creature->GetDistance(opponent) < 25) && + charge_cd <= 0) + { + doCast(opponent, CHARGE); + charge_cd = CHARGE_CD; + } + + if(opponent->getClass() == CLASS_ROGUE || + opponent->getClass() == CLASS_WARRIOR || + opponent->getClass() == CLASS_SHAMAN || + opponent->getClass() == CLASS_DRUID || + opponent->getClass() == CLASS_PALADIN || + opponent->getClass() == CLASS_HUNTER) + castDemoralizingShout = true; + } //end Aggro + + void KilledUnit(Unit *Victim) + { + master->SetBotCommandState(COMMAND_FOLLOW); + //DoPlaySoundToSet(m_creature, 8852); + //m_creature->Yell("Haha! Im just getting warmed up!", LANG_UNIVERSAL, NULL); + } + + void JustDied(Unit *Killer) + { + DoPlaySoundToSet(m_creature, 8860); + master->SetBotCommandState(COMMAND_FOLLOW); + //m_creature->Yell("An honorable.. death..", LANG_UNIVERSAL, NULL); + } + + void BreakCC(const uint32 diff) + { + if(pvpTrinket_cd < diff && GCD < diff) + { + if(m_creature->HasAuraType(SPELL_AURA_MOD_ROOT) || + m_creature->HasAuraType(SPELL_AURA_MOD_CONFUSE) || //dragons breath/blind/poly + m_creature->HasAura(8983) || //Druid bash rank 3 + m_creature->HasAura(27006) || //Druid pounce rank 4 + m_creature->HasAura(33786) || //Druid cyclone + m_creature->HasAura(22570, 1) || //Druid maim + m_creature->HasAura(10308) || //Paladin hammer of justice rank 4 + m_creature->HasAura(30414, 1) || //Warlock shadowfury rank 3 + m_creature->HasAura(6215) || //Warlock fear rank 3 **REMOVE THIS & IMPLEMENT IN BERSERKER RAGE** + m_creature->HasAura(17928) || //Warlock howlofterror rank 3 **REMOVE THIS & IMPLEMENT IN BERSERKER RAGE** + m_creature->HasAura(10890) || //Priest psychic scream rank 4 **REMOVE THIS & IMPLEMENT IN BERSERKER RAGE** + m_creature->HasAura(14902) || //Rogue Cheap shot + m_creature->HasAura(8643) || //Rogue Kidney shot Rank 2 + m_creature->HasAura(38764, 2) || //Rogue Gouge Rank 6 **REMOVE THIS & IMPLEMENT IN BERSERKER RAGE** + m_creature->HasAura(12809)) //Warrior concussion blow + { + doCast(m_creature, PVPTRINKET); //I think it would be better to instead of applying individual spells that apply the + pvpTrinket_cd = PVPTRINKET_CD; //effect SPELL_AURA_MOD_STUN, just add that type and start removing bad choices e.g. impact. + } + + if(m_creature->HasAura(11297) && m_creature->GetDistance(m_creature->getVictim()) < 10) + { //if warrior sapped and creature is less then 10 yards from warrior, cast pvp trinket and attempt to demoralizing shout him out of stealth + doCast(m_creature, PVPTRINKET); + pvpTrinket_cd = PVPTRINKET_CD; + castDemoralizingShout = true; + } + } + } //BreakCC + + void Attack(const uint32 diff) + { + //Unit *opponent = SelectUnit(SELECT_TARGET_TOPAGGRO, 0); + Unit *opponent = m_creature->getVictim(); + if(!opponent) return; + + if((m_creature->GetDistance(opponent) > 13) && + (m_creature->GetDistance(opponent) < 25) && + !m_creature->HasAuraType(SPELL_AURA_MOD_ROOT) && + !m_creature->HasAuraType(SPELL_AURA_MOD_STUN) && + !m_creature->HasAuraType(SPELL_AURA_MOD_CONFUSE) && + (intercept_cd < diff) && + (rage > 10) && + (GCD < diff)) + { + if(berserkerStance == true) + { + doCast(opponent, INTERCEPT, true); + intercept_cd = INTERCEPT_CD; + //rage -= 10; + } else stanceChange(3); + } + + if(disarm_cd < diff && + (opponent->GetHealth()*100/opponent->GetMaxHealth()) < 80 && + rage > 15 && + !HasAuraName(opponent, GetSpellName(DISARM)) && + GCD < 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 -= 15; + disarm_cd = DISARM_CD; + } else stanceChange(2); + } + } + + //opponent is not attacking me so try to taunt it + if(opponent->getVictim() && + opponent->getVictim()->GetGUID() != m_creature->GetGUID() && + taunt_cd < diff && + GCD < diff) + { + if(battleStance != true) + { + doCast(m_creature, DEFENSIVESTANCE, true); + defensiveStance = true; + } + doCast(opponent, TAUNT, true); + doCast(m_creature, TAUNT_VISUAL, true); + taunt_cd = TAUNT_CD; + } + + /* if(sunder_cd < diff && GCD < diff) + { + sLog->outError ("SUNDER"); + doCast(opponent, SUNDER, true); + //rage -= 15; + sunder_cd = SUNDER_CD; + } + */ + if((opponent->GetHealth()*100/opponent->GetMaxHealth()) < 15 && + rage > 15 && + GCD < diff) + { + if(battleStance == true || berserkerStance == true) + { + int damage = (rage*4 + (m_creature->getLevel()*10))/2; + + m_creature->CastCustomSpell(opponent, EXECUTE, &damage, NULL, NULL, false, NULL, NULL); + //rage = 0; + //m_creature->SetPower(POWER_RAGE, 0); + GCD = 20; + } else stanceChange(5); + } + + if(mortalStrike_cd < diff && + rage > 15 && + GCD < diff) + { + doCast(opponent, MORTALSTRIKE, true); + mortalStrike_cd = MORTALSTRIKE_CD; + //rage -= 15; + } + + if(castDemoralizingShout == true && + !opponent->HasAura(DEMORALIZINGSHOUT) && + rage < 10 && GCD < diff) + { + doCast(opponent, DEMORALIZINGSHOUT, true); + //rage -= 10; + castDemoralizingShout = false; + } + + if(opponent->IsNonMeleeSpellCasted(true) && + pummel_cd < diff && + rage > 10 && + GCD < diff) + { + if(berserkerStance == true) + { + doCast(opponent, PUMMEL, true); + pummel_cd = PUMMEL_CD; + //rage -= 10; + } else stanceChange(3); + } + + if(whirlwind_cd < diff && + rage > 25 && + GCD < diff) + { + if(berserkerStance == true) + { + doCast(opponent, WHIRLWIND, true); + whirlwind_cd = WHIRLWIND_CD; + //rage -= 25; + } else stanceChange(3); + } + + if(!opponent->HasAura(REND) && + rage > 10 && + GCD < diff) + { + if(battleStance == true || defensiveStance == true) + { + doCast(opponent, REND, true); + //rage -= 10; + } else stanceChange(1); + } + + if(!opponent->HasAura(HAMSTRING, 1) && + rage > 10 && + GCD < diff) + { + if(battleStance == true || + berserkerStance == true) + { + doCast(opponent, HAMSTRING, true); + //rage -= 10; + } else stanceChange(5); + } + + } + + void stanceChange(uint32 stance) + { + //if(rage > 20) + //rage = 20; + + if(stance == 5) + { + switch(rand()%2) + { + case 0: + stance = 1; break; + case 1: + stance = 3; break; + } + } + + battleStance = false; + defensiveStance = false; + berserkerStance = false; + + switch(stance) + { + case 1: + doCast(m_creature, BATTLESTANCE); + battleStance = true; + break; + case 2: + doCast(m_creature, DEFENSIVESTANCE); + defensiveStance = true; + break; + case 3: + doCast(m_creature, BERSERKERSTANCE); + berserkerStance = true; + break; + } + } + + void ReduceCD(const uint32 diff) + { + if(!(deathWish_cd < diff)) deathWish_cd -= diff; + if(!(mortalStrike_cd < diff)) mortalStrike_cd -= diff; + if(!(overpower_cd < diff)) overpower_cd -= diff; + if(!(retaliation_recklessness_shieldwall_cd < diff)) retaliation_recklessness_shieldwall_cd -= diff; + if(!(berserkerRage_cd < diff)) berserkerRage_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(!(bloodrage_cd < diff)) bloodrage_cd -= diff; + if(!(disarm_cd < diff)) disarm_cd -= diff; + if(!(intervene_cd < diff)) intervene_cd -= diff; + if(!(shieldBash_cd < diff)) shieldBash_cd -= diff; + if(!(spellReflection_cd < diff)) spellReflection_cd -= diff; + if(!(potion_cd < diff)) potion_cd -= diff; + if(!(yellRage < diff)) yellRage -= diff; + if(!(firstAid_cd < diff)) firstAid_cd -= diff; + if(!(taunt_cd < diff)) taunt_cd -= diff; + if(!(sunder_cd < diff)) sunder_cd -= diff; + if(!(battleShout_cd < diff)) battleShout_cd -= diff; + if(!(GCD < diff)) GCD -= diff; + else GCD = 0; + + if(charge_cd > 0) --charge_cd; //this is treated different + if(Noggenfogger_Timer >= 0) --Noggenfogger_Timer; + + } + }; +}; + +void AddSC_warrior_bot() +{ + new warrior_bot(); +} diff --git a/src/server/scripts/Bots/bot_warrior_ai.h b/src/server/scripts/Bots/bot_warrior_ai.h new file mode 100644 index 0000000..8d1e0ef --- /dev/null +++ b/src/server/scripts/Bots/bot_warrior_ai.h @@ -0,0 +1,87 @@ +#include "bot_ai.h" + + +//Cooldown/Timers +#define CHARGE_CD 100 +#define DEATHWISH_CD 180000 +#define MORTALSTRIKE_CD 7000 +#define OVERPOWER_CD 5000 +#define RETALIATION_RECKLESSNESS_SHIELDWALL_CD 1800000 +#define BERSERKERRAGE_CD 30000 +#define INTERCEPT_CD 15000 +#define INTIMIDATINGSHOUT_CD 120000 +#define PUMMEL_CD 10000 +#define WHIRLWIND_CD 10000 +#define BLOODRAGE_CD 60000 +#define DISARM_CD 60000 +#define INTERVENE_CD 30000 +#define BATTLESHOUT_CD 30000 +#define SHIELDBASH_CD 12000 +#define SPELLREFLECTION_CD 10000 +#define PVPTRINKET_CD 120000 +#define TAUNT_CD 8000 +#define SUNDER_CD 8000 + +//others +#define PVPTRINKET 42292 +#define YELLRAGE_CD 5000 + +#define SPELL_NOGGENFOGGER_SMALL 16595 +#define SPELL_NOGGENFOGGER_SKELETON 16591 + +//spells +#define TAUNT 355 +#define TAUNT_VISUAL 34105 +#define CHALLENGING_SHOUT 1161 + +/* + * Some spells NPCs can't seem to cast. So for those that I couldn't + * get to work, I used an NPC equivalent version. Most of these don't + * have different levels so it is one spell for all levels. This + * leads it to be over/under powered in some levels. Oh well. + * An example is BattleShout. + */ + +//Defensive Stance +#define DEFENSIVESTANCE 71 +#define DISARM 676 //DISARM_A[SPELL_LEVEL] +#define BLOODRAGE 29131 //2687 original warrior spell + + +//Berserker Stance +#define BERSERKERSTANCE 7366 //2458 original warrior spell +#define BERSERKERRAGE 18499 + +//#define BATTLESHOUT 26043 +#define COMMANDINGSHOUT 469 +#define BATTLESHOUT BATTLESHOUT_A[SPELL_LEVEL] +//#define EXECUTE EXECUTE_A[SPELL_LEVEL] +#define EXECUTE 38959 //25236 original warrior spell +#define WHIRLWIND WHIRLDWIND_A[SPELL_LEVEL] +//#define PUMMEL PUMMEL_A[SPELL_LEVEL] +#define PUMMEL 15615 //6554 original warrior spell +//#define DEMORALIZINGSHOUT 29584 //25203 original warrior spell +#define DEMORALIZINGSHOUT 29584 //25203 original warrior spell +#define INTERCEPT 27577 //25275 original warrior spell + + +//Battle Stance +#define BATTLESTANCE 7165 //2457 original warrior one +//#define CHARGE CHARGE_A[SPELL_LEVEL] +#define CHARGE 37511 //11578 original warrior one +#define HAMSTRING HAMSTRING_A[SPELL_LEVEL] +#define REND REND_A[SPELL_LEVEL] +#define MORTALSTRIKE MORTALSTRIKE_A[SPELL_LEVEL] +#define SUNDER SUNDER_A[SPELL_LEVEL] + + +//uint32 BATTLESHOUT_A[] = { 6673, 5242, 6192, 11549, 11550, 11551, 25289, 2048, 47436 }; +uint32 BATTLESHOUT_A[] = { 9128, 9128, 9128, 27578, 27578, 26043, 26043, 26043, 26043 }; +uint32 REND_A[] = { 772, 6546, 6547, 6548, 11572, 11573, 11574, 25208, 46845 }; +uint32 CHARGE_A[] = { 100, 100, 6178, 11578, 11578, 11578, 11578, 11578, 11578 }; +uint32 HAMSTRING_A[] = { 1715, 1715, 1715, 7372, 7372, 7373, 7373, 25212, 25212 }; +uint32 EXECUTE_A[] = { 1715, 1715, 1715, 7372, 7372, 7373, 7373, 25212, 25212 }; +uint32 WHIRLDWIND_A[] = { 0, 0, 0, 0, 1680, 1680, 1680, 1680, 1680 }; +uint32 PUMMEL_A[] = { 0, 0, 0, 0, 6552, 6552, 6552, 6552, 6552 }; +uint32 MORTALSTRIKE_A[] = { 0, 0, 0, 0, 12294, 21552, 21553, 25248, 47485 }; +uint32 SUNDER_A[] = { 0, 7386, 7405, 8380, 11596, 11597, 25225, 47467, 47467 }; diff --git a/src/server/scripts/Bots/script_bot_giver.cpp b/src/server/scripts/Bots/script_bot_giver.cpp new file mode 100644 index 0000000..cc4b3b5 --- /dev/null +++ b/src/server/scripts/Bots/script_bot_giver.cpp @@ -0,0 +1,234 @@ + +#include "ScriptPCH.h" +#include <cstring> +#include "Group.h" + +//This function is called when the player opens the gossip menubool +class script_bot_giver : public CreatureScript +{ + public: + + script_bot_giver() + : CreatureScript("script_bot_giver") + { + } + + bool OnGossipSelect(Player *player, Creature *creature, uint32 sender, uint32 action) + { + switch(sender) + { + case 6006: SendCreateNPCBotMenu(player, creature, action); break; + case 6001: SendCreateNPCBot(player, creature, action); break; + case 6002: SendCreatePlayerBotMenu(player, creature, action); break; + case 6003: SendCreatePlayerBot(player, creature, action); break; + case 6004: SendRemovePlayerBotMenu(player, creature, action); break; + case 6005: SendRemovePlayerBot(player, creature, action); break; + } + return true; + } + + bool OnGossipHello(Player *player, Creature *creature) + { + WorldSession *session = player->GetSession(); + uint8 count = 0; + + for(PlayerBotMap::const_iterator itr = session->GetPlayerBotsBegin(); itr != session->GetPlayerBotsEnd(); ++itr) + { + if(count == 0) + player->ADD_GOSSIP_ITEM(0, "Abandon Your Player?", 6004, GOSSIP_ACTION_INFO_DEF + 100); + + ++count; + } + + if(player->HaveBot()) + { + player->ADD_GOSSIP_ITEM(0, "Abandon Your Minion?", 6001, GOSSIP_ACTION_INFO_DEF + 101); + } else + player->ADD_GOSSIP_ITEM(0, "Recruit a Minion", 6006, GOSSIP_ACTION_INFO_DEF + 2); + + if(count < player->GetMaxPlayerBot()) player->ADD_GOSSIP_ITEM(0, "Recruit a Player", 6002, GOSSIP_ACTION_INFO_DEF + 1); + + player->PlayerTalkClass->SendGossipMenu(907, creature->GetGUID()); + return true; + } + + void SendCreatePlayerBot(Player *player, Creature *creature, uint32 action) + { + std::list<std::string> *names; + names = player->GetCharacterList(); + if(names == NULL || names->empty()) + { + player->CLOSE_GOSSIP_MENU(); + return; + } + + int8 x = action - GOSSIP_ACTION_INFO_DEF - 1; + + std::list<std::string>::iterator iter, next; + for(iter = names->begin(); iter != names->end(); iter++) + { + if (x==0) player->CreatePlayerBot((*iter).c_str()); + else { + if(x == 1) + { + player->CreatePlayerBot((*iter).c_str()); + break; + } + --x; + } + } + + player->CLOSE_GOSSIP_MENU(); + } //end SendCreatePlayerBot + + void SendCreatePlayerBotMenu(Player *player, Creature *creature, uint32 action) + { + std::list<std::string> *names; + names = player->GetCharacterList(); + if(names == NULL || names->empty()) + { + player->CLOSE_GOSSIP_MENU(); + return; + } + + player->PlayerTalkClass->ClearMenus(); + player->ADD_GOSSIP_ITEM(9, "ADD ALL" , 6003, GOSSIP_ACTION_INFO_DEF + 1); + int8 x = 2; + + std::list<std::string>::iterator iter, next; + for(iter = names->begin(); iter != names->end(); iter++) + { + //sLog->outError("character : %s", (*iter).c_str()); + player->ADD_GOSSIP_ITEM(9, (*iter).c_str() , 6003, GOSSIP_ACTION_INFO_DEF + x); + ++x; + } + player->SEND_GOSSIP_MENU(907, creature->GetGUID()); + } //end SendCreatePlayerBotMenu + + void SendRemovePlayerBotAll(Player *player, Creature *creature) { + for (int8 x = 2; x<=10; x++ ) + { + SendRemovePlayerBot (player, creature, GOSSIP_ACTION_INFO_DEF + 2); + } + } + + void SendRemovePlayerBot(Player *player, Creature *creature, uint32 action) + { + int8 x = action - GOSSIP_ACTION_INFO_DEF - 1; + + if (x == 0) { + SendRemovePlayerBotAll(player, creature); + return; + } + + WorldSession *session = player->GetSession(); + for(PlayerBotMap::const_iterator itr = session->GetPlayerBotsBegin(); itr != session->GetPlayerBotsEnd(); ++itr) + { + if(x == 1 && itr->second && itr->second->GetGroup()) + { + Player *m_bot = itr->second; + Group *m_group = m_bot->GetGroup(); + + //removing bot from group + if(m_group->IsMember(m_bot->GetGUID())) + { + //deleting bot from group + if(m_group->RemoveMember(m_bot->GetGUID(), GROUP_REMOVEMETHOD_DEFAULT) < 1) // 99 means I'm a bot + { + //no one left in group so deleting group + delete m_group; + //sObjectMgr->RemoveGroup(m_group); + } + } + session->LogoutPlayerBot(m_bot->GetGUID(), true); + break; + } + --x; + } + player->CLOSE_GOSSIP_MENU(); + } //end SendRemovePlayerBot + + void SendRemovePlayerBotMenu(Player *player, Creature *creature, uint32 action) + { + player->PlayerTalkClass->ClearMenus(); + player->ADD_GOSSIP_ITEM(9, "REMOVE ALL", 6005, GOSSIP_ACTION_INFO_DEF + 1); + + uint8 x = 2; + WorldSession *session = player->GetSession(); + for(PlayerBotMap::const_iterator itr = session->GetPlayerBotsBegin(); itr != session->GetPlayerBotsEnd(); ++itr) + { + Player *bot = itr->second; + player->ADD_GOSSIP_ITEM(9, bot->GetName(), 6005, GOSSIP_ACTION_INFO_DEF + x); + ++x; + } + player->SEND_GOSSIP_MENU(907, creature->GetGUID()); + } //end SendRemovePlayerBotMenu + + void SendCreateNPCBot(Player *player, Creature *creature, uint32 action) + { + uint8 bot_class = 0; + if(action == GOSSIP_ACTION_INFO_DEF + 101) //abandon bot + { + if(player->HaveBot()) + player->SetBotMustDie(); + player->CLOSE_GOSSIP_MENU(); + return; + } + else if(action == GOSSIP_ACTION_INFO_DEF + 1){ //playerbot + 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) + { + //sLog->outError("script_bot_giver.SendCreateNPCBot class = %u", bot_class); + player->CreateNPCBot(bot_class); + } + //else + //creature->Say("Invalid selection.", LANG_UNIVERSAL, NULL); + player->CLOSE_GOSSIP_MENU(); + return; + } + + void SendCreateNPCBotMenu(Player *player, Creature *creature, uint32 action) + { + player->PlayerTalkClass->ClearMenus(); + player->ADD_GOSSIP_ITEM(9, "Recruit a Warrior", 6001, GOSSIP_ACTION_INFO_DEF + 2); + player->ADD_GOSSIP_ITEM(9, "Recruit a Hunter", 6001, GOSSIP_ACTION_INFO_DEF + 3); + player->ADD_GOSSIP_ITEM(9, "Recruit a Paladin", 6001, GOSSIP_ACTION_INFO_DEF + 4); + player->ADD_GOSSIP_ITEM(9, "Recruit a Shaman", 6001, GOSSIP_ACTION_INFO_DEF + 5); + player->ADD_GOSSIP_ITEM(9, "Recruit a Rogue", 6001, GOSSIP_ACTION_INFO_DEF + 6); + player->ADD_GOSSIP_ITEM(3, "Recruit a Druid", 6001, GOSSIP_ACTION_INFO_DEF + 7); + player->ADD_GOSSIP_ITEM(3, "Recruit a Mage", 6001, GOSSIP_ACTION_INFO_DEF + 8); + player->ADD_GOSSIP_ITEM(3, "Recruit a Priest", 6001, GOSSIP_ACTION_INFO_DEF + 9); + player->ADD_GOSSIP_ITEM(3, "Recruit a Warlock", 6001, GOSSIP_ACTION_INFO_DEF + 10); + //player->ADD_GOSSIP_ITEM(9, "Recruit a Death Knight", 1, GOSSIP_ACTION_INFO_DEF + 11); + player->SEND_GOSSIP_MENU(907, creature->GetGUID()); + } //end SendCreateNPCBotMenu +}; + +//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/scripts/CMakeLists.txt b/src/server/scripts/CMakeLists.txt index 17d4bfc..95ec7b9 100644 --- a/src/server/scripts/CMakeLists.txt +++ b/src/server/scripts/CMakeLists.txt @@ -40,6 +40,7 @@ set(scripts_STAT_SRCS if(SCRIPTS) include(Custom/CMakeLists.txt) + include(Bots/CMakeLists.txt) include(World/CMakeLists.txt) include(OutdoorPvP/CMakeLists.txt) include(EasternKingdoms/CMakeLists.txt) diff --git a/src/server/shared/Common.h b/src/server/shared/Common.h index cefefa7..77a76dc 100755 --- a/src/server/shared/Common.h +++ b/src/server/shared/Common.h @@ -19,6 +19,10 @@ #ifndef TRINITYCORE_COMMON_H #define TRINITYCORE_COMMON_H +#ifndef PLAYERBOT_EXISTS +#define PLAYERBOT_EXISTS +#endif + // config.h needs to be included 1st // TODO this thingy looks like hack ,but its not, need to // make separate header however, because It makes mess here. diff --git a/src/server/worldserver/worldserver.conf.dist b/src/server/worldserver/worldserver.conf.dist index 188d6d8..40d4631 100644 --- a/src/server/worldserver/worldserver.conf.dist +++ b/src/server/worldserver/worldserver.conf.dist @@ -2738,3 +2738,39 @@ LevelReq.Mail = 1 # ################################################################################################### + +################################################################################ +# BOT CONFIGURATION +# +# Bot.FollowDistanceMin +# Bot.FollowDistanceMax +# Min. and max. follow distance for bots +# Default: 0.5 / 2.0 +# Bot.MaxPlayerBots +# Maximum number of Player Bots allowed per account +# Default: 9 +# Bot.PlayerBotsFly +# If PlayerBots fly with you when you use the flight master +# Default: 0 +# 0 = false +# 1 = true +# Bot.LootMethod +# Type of loot method +# Default: 2 +# 0 = Free for all +# 1 = Round robin +# 2 = Master loot +# 3 = Group loot +# 4 = Need before greed +# Bot.SaveOrgLocation +# Puts playerbots back to their original location after use +# Default: 0 +# 0 = false - keep playerbots where they were camped out +# 1 = true - puts playerbots back to where they were originally summoned +################################################################################ +Bot.FollowDistanceMin = 0.5 +Bot.FollowDistanceMax = 2.0 +Bot.MaxPlayerBots = 9 +Bot.PlayerBotsFly=0 +Bot.LootMethod=2 +Bot.SaveOrgLocation=0 -- 1.7.3.1.msysgit.0