.circleci/config.yml | 27 +- .github/workflows/gcc-build.yml | 7 + README.md | 44 +- sql/Bots/1_world_bot_appearance.sql | 305 + sql/Bots/2_world_bot_extras.sql | 358 + sql/Bots/3_world_bots.sql | 547 + sql/Bots/4_world_generate_bot_equips.sql | 159 + sql/Bots/5_world_botgiver.sql | 18 + sql/Bots/characters_bots.sql | 29 + sql/Bots/locales/deDE/npc_text_locale.sql | 409 + sql/Bots/locales/esES/npc_text_locale.sql | 382 + sql/Bots/locales/esMX/npc_text_locale.sql | 382 + sql/Bots/locales/ruRU/npc_text_locale.sql | 380 + sql/Bots/locales/zhCN/creature_template_locale.sql | 742 + sql/Bots/locales/zhCN/npc_text_locale.sql | 410 + sql/Bots/locales/zhTW/npc_text_locale.sql | 410 + sql/Bots/merge_sqls_auth_unix.sh | 2 + sql/Bots/merge_sqls_auth_windows.bat | 1 + sql/Bots/merge_sqls_characters_unix.sh | 3 + sql/Bots/merge_sqls_characters_windows.bat | 1 + sql/Bots/merge_sqls_world_unix.sh | 3 + sql/Bots/merge_sqls_world_windows.bat | 1 + .../auth/2021_09_14_00_rbac_permissions.sql | 39 + .../auth/2021_09_14_01_rbac_linked_permissions.sql | 39 + .../auth/2021_12_29_00_rbac_permissions.sql | 7 + .../auth/2021_12_29_01_rbac_linked_permissions.sql | 7 + .../auth/2022_06_24_00_rbac_permissions.sql | 7 + .../auth/2022_06_24_01_rbac_linked_permissions.sql | 7 + .../auth/2022_07_04_00_rbac_permissions.sql | 7 + .../auth/2022_07_04_01_rbac_linked_permissions.sql | 7 + .../auth/2022_11_30_00_rbac_permissions.sql | 7 + .../auth/2022_11_30_rbac_linked_permissions.sql | 7 + .../auth/2023_08_12_00_rbac_linked_permissions.sql | 2 + ...15_characters_npcbot_NPCBots_4.7.8a-4.7.27a.sql | 1 + .../2020_06_21_characters_npcbot_43fceb98.sql | 172 + ...8_characters_npcbot_NPCBots_4.7.34a-4.7.39a.sql | 1 + .../characters/2021_01_04_characters_npcbot.sql | 1 + .../2021_01_24_characters_npcbot_stats.sql | 32 + .../characters/2021_08_27_characters_npcbot.sql | 2 + .../2022_11_15_characters_npcbot_transmog.sql | 10 + .../2022_11_27_characters_npcbot_group_member.sql | 10 + ...023_05_16_00_characters_npcbot_gear_storage.sql | 11 + .../2023_05_26_00_characters_npcbot_transmog.sql | 2 + .../characters/2024_03_12_00_characters_npcbot.sql | 20 + .../2024_05_29_00_characters_npcbot_logs.sql | 19 + .../2020_07_08_creature_template_npcbot_extras.sql | 2 + .../updates/world/2020_09_25_creature_template.sql | 2 + .../world/2020_10_14_creature_classlevelstats.sql | 611 + .../updates/world/2020_10_14_creature_template.sql | 50 + .../updates/world/2020_10_15_creature_template.sql | 2 + sql/Bots/updates/world/2020_11_07_npc_text.sql | 329 + sql/Bots/updates/world/2020_12_11_npc_text.sql | 4 + sql/Bots/updates/world/2021_01_04_npc_text.sql | 19 + sql/Bots/updates/world/2021_01_05_npc_text.sql | 13 + sql/Bots/updates/world/2021_01_08_npc_text.sql | 9 + sql/Bots/updates/world/2021_02_01_npc_text.sql | 2 + sql/Bots/updates/world/2021_03_29_npc_text.sql | 10 + .../2021_08_20_creature_template_npcbot_extras.sql | 2 + sql/Bots/updates/world/2021_08_27_npc_text.sql | 4 + .../updates/world/2021_10_12_creature_template.sql | 8 + ...22_01_02_00_creature_template_npcbot_extras.sql | 12 + .../2022_01_02_01_generate_equips_necromancer.sql | 57 + .../world/2022_01_02_02_creature_template.sql | 21 + sql/Bots/updates/world/2022_01_02_03_npc_text.sql | 15 + sql/Bots/updates/world/2022_01_31_npc_text.sql | 10 + ...22_06_15_00_creature_template_npcbot_extras.sql | 2 + sql/Bots/updates/world/2022_06_17_npc_text.sql | 3 + ...22_06_22_00_creature_template_npcbot_extras.sql | 11 + .../2022_06_22_01_generate_equips_sea_witch.sql | 50 + .../world/2022_06_22_02_creature_template.sql | 21 + sql/Bots/updates/world/2022_06_22_03_npc_text.sql | 26 + sql/Bots/updates/world/2022_06_23_00_command.sql | 39 + sql/Bots/updates/world/2022_06_24_00_npc_text.sql | 7 + sql/Bots/updates/world/2022_06_24_01_npc_text.sql | 8 + sql/Bots/updates/world/2022_06_24_02_command.sql | 4 + sql/Bots/updates/world/2022_07_04_00_command.sql | 4 + sql/Bots/updates/world/2022_07_25_00_npc_text.sql | 7 + sql/Bots/updates/world/2022_11_15_00_npc_text.sql | 7 + sql/Bots/updates/world/2022_11_30_00_command.sql | 4 + sql/Bots/updates/world/2022_12_08_00_npc_text.sql | 8 + sql/Bots/updates/world/2022_12_17_00_command.sql | 7 + sql/Bots/updates/world/2022_12_17_01_command.sql | 2 + sql/Bots/updates/world/2022_12_23_00_command.sql | 6 + sql/Bots/updates/world/2023_01_02_00_npc_text.sql | 8 + sql/Bots/updates/world/2023_01_10_00_command.sql | 4 + sql/Bots/updates/world/2023_01_11_00_command.sql | 5 + sql/Bots/updates/world/2023_03_10_00_command.sql | 4 + sql/Bots/updates/world/2023_03_10_01_command.sql | 4 + ...12_00_creature_template_npcbot_wander_nodes.sql | 18 + .../world/2023_03_14_00_creature_wander_nodes.sql | 2 + ...21_00_creature_template_npcbot_wander_nodes.sql | 964 + ...04_00_creature_template_npcbot_wander_nodes.sql | 2343 +++ ...11_00_creature_template_npcbot_wander_nodes.sql | 27 + ...16_00_creature_template_npcbot_wander_nodes.sql | 27 + ...4_18_00_creature_template_npcbot_appearance.sql | 2 + ...22_00_creature_template_npcbot_wander_nodes.sql | 25 + .../world/2023_04_28_00_creature_template.sql | 3 + ...14_00_creature_template_npcbot_wander_nodes.sql | 2 + sql/Bots/updates/world/2023_05_16_00_npc_text.sql | 13 + .../world/2023_05_20_00_creature_template.sql | 24 + .../2023_05_20_01_creature_equip_template.sql | 13 + ...23_05_20_02_creature_template_npcbot_extras.sql | 15 + sql/Bots/updates/world/2023_05_20_03_npc_text.sql | 27 + ...23_06_02_00_creature_template_npcbot_extras.sql | 2 + .../world/2023_06_03_00_creature_template.sql | 2 + ...09_00_creature_template_npcbot_wander_nodes.sql | 2627 +++ ...16_00_creature_template_npcbot_wander_nodes.sql | 9 + sql/Bots/updates/world/2023_06_18_00_command.sql | 32 + ..._01_creature_template_npcbot_disabled_items.sql | 6 + ..._00_creature_template_npcbot_disabled_items.sql | 53 + .../world/2023_06_19_01_creature_template.sql | 2 + sql/Bots/updates/world/2023_06_22_00_command.sql | 2 + ...24_00_creature_template_npcbot_wander_nodes.sql | 55 + ...26_00_creature_template_npcbot_wander_nodes.sql | 5 + ..._00_creature_template_npcbot_disabled_items.sql | 11 + ...8_13_00_creature_template_npcbot_appearance.sql | 2 + sql/Bots/updates/world/2024_03_12_00_npc_text.sql | 7 + sql/Bots/updates/world/2024_03_18_00_npc_text.sql | 2 + sql/Bots/updates/world/2024_03_19_00_npc_text.sql | 17 + sql/Bots/updates/world/2024_05_29_00_command.sql | 3 + sql/Bots/updates/world/2024_08_14_00_npc_text.sql | 7 + sql/Bots/updates/world/2024_08_15_00_command.sql | 3 + sql/Bots/updates/world/2024_08_31_00_command.sql | 3 + src/common/Utilities/EventProcessor.h | 1 + .../Database/Implementation/CharacterDatabase.cpp | 29 + .../Database/Implementation/CharacterDatabase.h | 24 + src/server/game/AI/CoreAI/UnitAI.cpp | 3 + src/server/game/AI/NpcBots/bot_Events.h | 74 + src/server/game/AI/NpcBots/bot_GridNotifiers.h | 1410 ++ src/server/game/AI/NpcBots/bot_ai.cpp | 20134 +++++++++++++++++++ src/server/game/AI/NpcBots/bot_ai.h | 828 + src/server/game/AI/NpcBots/bot_archmage_ai.cpp | 397 + src/server/game/AI/NpcBots/bot_bm_ai.cpp | 941 + src/server/game/AI/NpcBots/bot_crypt_lord_ai.cpp | 842 + src/server/game/AI/NpcBots/bot_dark_ranger_ai.cpp | 603 + src/server/game/AI/NpcBots/bot_death_knight_ai.cpp | 2118 ++ src/server/game/AI/NpcBots/bot_dreadlord_ai.cpp | 547 + src/server/game/AI/NpcBots/bot_druid_ai.cpp | 2934 +++ src/server/game/AI/NpcBots/bot_hunter_ai.cpp | 2253 +++ src/server/game/AI/NpcBots/bot_mage_ai.cpp | 1838 ++ src/server/game/AI/NpcBots/bot_necromancer_ai.cpp | 755 + src/server/game/AI/NpcBots/bot_paladin_ai.cpp | 2550 +++ src/server/game/AI/NpcBots/bot_priest_ai.cpp | 2011 ++ src/server/game/AI/NpcBots/bot_rogue_ai.cpp | 2055 ++ src/server/game/AI/NpcBots/bot_sea_witch_ai.cpp | 785 + src/server/game/AI/NpcBots/bot_shaman_ai.cpp | 2849 +++ src/server/game/AI/NpcBots/bot_spellbreaker_ai.cpp | 612 + src/server/game/AI/NpcBots/bot_sphynx_ai.cpp | 569 + src/server/game/AI/NpcBots/bot_warlock_ai.cpp | 2100 ++ src/server/game/AI/NpcBots/bot_warrior_ai.cpp | 2196 ++ src/server/game/AI/NpcBots/botcommands.cpp | 4453 ++++ src/server/game/AI/NpcBots/botcommon.h | 591 + src/server/game/AI/NpcBots/botdatamgr.cpp | 3110 +++ src/server/game/AI/NpcBots/botdatamgr.h | 230 + src/server/game/AI/NpcBots/botdpstracker.cpp | 133 + src/server/game/AI/NpcBots/botdpstracker.h | 38 + src/server/game/AI/NpcBots/botdump.cpp | 1024 + src/server/game/AI/NpcBots/botdump.h | 51 + src/server/game/AI/NpcBots/botgearscore.cpp | 141 + src/server/game/AI/NpcBots/botgearscore.h | 16 + src/server/game/AI/NpcBots/botgiver.cpp | 298 + src/server/game/AI/NpcBots/botgossip.h | 141 + src/server/game/AI/NpcBots/botlog.cpp | 87 + src/server/game/AI/NpcBots/botlog.h | 39 + src/server/game/AI/NpcBots/botmgr.cpp | 3110 +++ src/server/game/AI/NpcBots/botmgr.h | 350 + src/server/game/AI/NpcBots/botspell.cpp | 2095 ++ src/server/game/AI/NpcBots/botspell.h | 317 + src/server/game/AI/NpcBots/bottext.h | 426 + src/server/game/AI/NpcBots/botwanderful.cpp | 352 + src/server/game/AI/NpcBots/botwanderful.h | 169 + src/server/game/AI/NpcBots/bpet_ai.cpp | 2656 +++ src/server/game/AI/NpcBots/bpet_ai.h | 198 + src/server/game/AI/NpcBots/bpet_archmage.cpp | 151 + src/server/game/AI/NpcBots/bpet_crypt_lord.cpp | 383 + src/server/game/AI/NpcBots/bpet_dark_ranger.cpp | 212 + src/server/game/AI/NpcBots/bpet_death_knight.cpp | 190 + src/server/game/AI/NpcBots/bpet_dreadlord.cpp | 163 + src/server/game/AI/NpcBots/bpet_druid.cpp | 146 + src/server/game/AI/NpcBots/bpet_hunter.cpp | 1000 + src/server/game/AI/NpcBots/bpet_mage.cpp | 177 + src/server/game/AI/NpcBots/bpet_necromancer.cpp | 191 + src/server/game/AI/NpcBots/bpet_priest.cpp | 172 + src/server/game/AI/NpcBots/bpet_sea_witch.cpp | 251 + src/server/game/AI/NpcBots/bpet_shaman.cpp | 191 + src/server/game/AI/NpcBots/bpet_warlock.cpp | 408 + src/server/game/AI/NpcBots/lib/botlogtraits.h | 46 + src/server/game/AI/NpcBots/lib/bottraits.h | 156 + src/server/game/AI/SmartScripts/SmartScript.cpp | 3 + src/server/game/Accounts/RBAC.h | 39 + src/server/game/Battlegrounds/Battleground.cpp | 571 + src/server/game/Battlegrounds/Battleground.h | 39 + src/server/game/Battlegrounds/BattlegroundMgr.h | 5 + .../game/Battlegrounds/BattlegroundQueue.cpp | 159 + src/server/game/Battlegrounds/BattlegroundQueue.h | 4 + .../game/Battlegrounds/Zones/BattlegroundAB.cpp | 251 + .../game/Battlegrounds/Zones/BattlegroundAB.h | 12 + .../game/Battlegrounds/Zones/BattlegroundAV.cpp | 355 + .../game/Battlegrounds/Zones/BattlegroundAV.h | 22 + .../game/Battlegrounds/Zones/BattlegroundWS.cpp | 450 + .../game/Battlegrounds/Zones/BattlegroundWS.h | 14 + src/server/game/Combat/CombatManager.cpp | 58 + src/server/game/Combat/ThreatManager.cpp | 21 + src/server/game/Conditions/ConditionMgr.cpp | 47 + src/server/game/DataStores/DBCStores.cpp | 4 +- src/server/game/DataStores/DBCStores.h | 2 +- src/server/game/DungeonFinding/LFGMgr.cpp | 180 + src/server/game/DungeonFinding/LFGScripts.cpp | 9 + src/server/game/Entities/Creature/Creature.cpp | 579 + src/server/game/Entities/Creature/Creature.h | 90 + src/server/game/Entities/Creature/CreatureData.h | 24 +- .../game/Entities/Creature/TemporarySummon.cpp | 28 + src/server/game/Entities/GameObject/GameObject.cpp | 79 + src/server/game/Entities/Object/Object.cpp | 245 +- src/server/game/Entities/Object/Object.h | 10 +- src/server/game/Entities/Player/KillRewarder.cpp | 29 + src/server/game/Entities/Player/Player.cpp | 185 + src/server/game/Entities/Player/Player.h | 24 + src/server/game/Entities/Totem/Totem.cpp | 25 + src/server/game/Entities/Unit/StatSystem.cpp | 46 +- src/server/game/Entities/Unit/Unit.cpp | 926 + src/server/game/Entities/Unit/Unit.h | 15 +- src/server/game/Entities/Vehicle/Vehicle.cpp | 42 + src/server/game/Globals/ObjectMgr.cpp | 97 + src/server/game/Globals/ObjectMgr.h | 21 + src/server/game/Grids/Notifiers/GridNotifiers.h | 4 + src/server/game/Groups/Group.cpp | 358 + src/server/game/Groups/Group.h | 17 + src/server/game/Groups/GroupMgr.cpp | 7 + src/server/game/Groups/GroupRefManager.h | 11 + src/server/game/Groups/GroupReference.cpp | 20 + src/server/game/Groups/GroupReference.h | 20 + src/server/game/Handlers/BattleGroundHandler.cpp | 74 + src/server/game/Handlers/GroupHandler.cpp | 29 + src/server/game/Handlers/ItemHandler.cpp | 13 + src/server/game/Handlers/QueryHandler.cpp | 39 + src/server/game/Handlers/SpellHandler.cpp | 109 + src/server/game/Instances/InstanceScript.cpp | 40 + src/server/game/Instances/InstanceScript.h | 7 + src/server/game/Maps/Map.cpp | 42 + src/server/game/Maps/MapManager.cpp | 17 + src/server/game/Movement/MotionMaster.cpp | 20 + .../MovementGenerators/FollowMovementGenerator.cpp | 12 + src/server/game/Movement/Spline/MoveSplineInit.cpp | 5 + src/server/game/OutdoorPvP/OutdoorPvP.cpp | 17 + src/server/game/Scripting/ScriptMgr.cpp | 8 + src/server/game/Scripting/ScriptMgr.h | 2 + src/server/game/Server/WorldSession.cpp | 18 + src/server/game/Spells/Auras/SpellAuraEffects.cpp | 99 + src/server/game/Spells/Auras/SpellAuras.cpp | 55 + src/server/game/Spells/Spell.cpp | 234 + src/server/game/Spells/Spell.h | 3 + src/server/game/Spells/SpellEffects.cpp | 217 + src/server/game/Spells/SpellInfo.cpp | 65 + src/server/game/Spells/SpellInfo.h | 29 + src/server/game/Spells/SpellMgr.cpp | 12 + src/server/scripts/Commands/cs_npc.cpp | 31 + .../CavernsOfTime/BattleForMountHyjal/hyjalAI.cpp | 5 + .../BattleForMountHyjal/hyjal_trash.cpp | 8 + .../boss_icecrown_gunship_battle.cpp | 28 + .../IcecrownCitadel/instance_icecrown_citadel.cpp | 21 + .../Northrend/Naxxramas/boss_four_horsemen.cpp | 4 +- .../boss_shirrak_the_dead_watcher.cpp | 7 + .../Outland/BlackTemple/boss_warlord_najentus.cpp | 16 + src/server/scripts/Spells/spell_dk.cpp | 17 + src/server/scripts/Spells/spell_druid.cpp | 65 + src/server/scripts/Spells/spell_generic.cpp | 37 + src/server/scripts/Spells/spell_hunter.cpp | 16 + src/server/scripts/Spells/spell_item.cpp | 23 + src/server/scripts/Spells/spell_mage.cpp | 6 + src/server/scripts/Spells/spell_paladin.cpp | 64 + src/server/scripts/Spells/spell_priest.cpp | 12 + src/server/scripts/Spells/spell_rogue.cpp | 56 + src/server/scripts/Spells/spell_shaman.cpp | 127 + src/server/scripts/Spells/spell_warrior.cpp | 9 + src/server/shared/DataStores/DBCStructure.h | 15 +- src/server/shared/DataStores/DBCfmt.h | 4 +- src/server/shared/SharedDefines.h | 7 + src/server/worldserver/worldserver.conf.dist | 619 + 279 files changed, 95568 insertions(+), 77 deletions(-) create mode 100644 sql/Bots/1_world_bot_appearance.sql create mode 100644 sql/Bots/2_world_bot_extras.sql create mode 100644 sql/Bots/3_world_bots.sql create mode 100644 sql/Bots/4_world_generate_bot_equips.sql create mode 100644 sql/Bots/5_world_botgiver.sql create mode 100644 sql/Bots/characters_bots.sql create mode 100644 sql/Bots/locales/deDE/npc_text_locale.sql create mode 100644 sql/Bots/locales/esES/npc_text_locale.sql create mode 100644 sql/Bots/locales/esMX/npc_text_locale.sql create mode 100644 sql/Bots/locales/ruRU/npc_text_locale.sql create mode 100644 sql/Bots/locales/zhCN/creature_template_locale.sql create mode 100644 sql/Bots/locales/zhCN/npc_text_locale.sql create mode 100644 sql/Bots/locales/zhTW/npc_text_locale.sql create mode 100644 sql/Bots/merge_sqls_auth_unix.sh create mode 100644 sql/Bots/merge_sqls_auth_windows.bat create mode 100644 sql/Bots/merge_sqls_characters_unix.sh create mode 100644 sql/Bots/merge_sqls_characters_windows.bat create mode 100644 sql/Bots/merge_sqls_world_unix.sh create mode 100644 sql/Bots/merge_sqls_world_windows.bat create mode 100644 sql/Bots/updates/auth/2021_09_14_00_rbac_permissions.sql create mode 100644 sql/Bots/updates/auth/2021_09_14_01_rbac_linked_permissions.sql create mode 100644 sql/Bots/updates/auth/2021_12_29_00_rbac_permissions.sql create mode 100644 sql/Bots/updates/auth/2021_12_29_01_rbac_linked_permissions.sql create mode 100644 sql/Bots/updates/auth/2022_06_24_00_rbac_permissions.sql create mode 100644 sql/Bots/updates/auth/2022_06_24_01_rbac_linked_permissions.sql create mode 100644 sql/Bots/updates/auth/2022_07_04_00_rbac_permissions.sql create mode 100644 sql/Bots/updates/auth/2022_07_04_01_rbac_linked_permissions.sql create mode 100644 sql/Bots/updates/auth/2022_11_30_00_rbac_permissions.sql create mode 100644 sql/Bots/updates/auth/2022_11_30_rbac_linked_permissions.sql create mode 100644 sql/Bots/updates/auth/2023_08_12_00_rbac_linked_permissions.sql create mode 100644 sql/Bots/updates/characters/2020_05_15_characters_npcbot_NPCBots_4.7.8a-4.7.27a.sql create mode 100644 sql/Bots/updates/characters/2020_06_21_characters_npcbot_43fceb98.sql create mode 100644 sql/Bots/updates/characters/2020_10_08_characters_npcbot_NPCBots_4.7.34a-4.7.39a.sql create mode 100644 sql/Bots/updates/characters/2021_01_04_characters_npcbot.sql create mode 100644 sql/Bots/updates/characters/2021_01_24_characters_npcbot_stats.sql create mode 100644 sql/Bots/updates/characters/2021_08_27_characters_npcbot.sql create mode 100644 sql/Bots/updates/characters/2022_11_15_characters_npcbot_transmog.sql create mode 100644 sql/Bots/updates/characters/2022_11_27_characters_npcbot_group_member.sql create mode 100644 sql/Bots/updates/characters/2023_05_16_00_characters_npcbot_gear_storage.sql create mode 100644 sql/Bots/updates/characters/2023_05_26_00_characters_npcbot_transmog.sql create mode 100644 sql/Bots/updates/characters/2024_03_12_00_characters_npcbot.sql create mode 100644 sql/Bots/updates/characters/2024_05_29_00_characters_npcbot_logs.sql create mode 100644 sql/Bots/updates/world/2020_07_08_creature_template_npcbot_extras.sql create mode 100644 sql/Bots/updates/world/2020_09_25_creature_template.sql create mode 100644 sql/Bots/updates/world/2020_10_14_creature_classlevelstats.sql create mode 100644 sql/Bots/updates/world/2020_10_14_creature_template.sql create mode 100644 sql/Bots/updates/world/2020_10_15_creature_template.sql create mode 100644 sql/Bots/updates/world/2020_11_07_npc_text.sql create mode 100644 sql/Bots/updates/world/2020_12_11_npc_text.sql create mode 100644 sql/Bots/updates/world/2021_01_04_npc_text.sql create mode 100644 sql/Bots/updates/world/2021_01_05_npc_text.sql create mode 100644 sql/Bots/updates/world/2021_01_08_npc_text.sql create mode 100644 sql/Bots/updates/world/2021_02_01_npc_text.sql create mode 100644 sql/Bots/updates/world/2021_03_29_npc_text.sql create mode 100644 sql/Bots/updates/world/2021_08_20_creature_template_npcbot_extras.sql create mode 100644 sql/Bots/updates/world/2021_08_27_npc_text.sql create mode 100644 sql/Bots/updates/world/2021_10_12_creature_template.sql create mode 100644 sql/Bots/updates/world/2022_01_02_00_creature_template_npcbot_extras.sql create mode 100644 sql/Bots/updates/world/2022_01_02_01_generate_equips_necromancer.sql create mode 100644 sql/Bots/updates/world/2022_01_02_02_creature_template.sql create mode 100644 sql/Bots/updates/world/2022_01_02_03_npc_text.sql create mode 100644 sql/Bots/updates/world/2022_01_31_npc_text.sql create mode 100644 sql/Bots/updates/world/2022_06_15_00_creature_template_npcbot_extras.sql create mode 100644 sql/Bots/updates/world/2022_06_17_npc_text.sql create mode 100644 sql/Bots/updates/world/2022_06_22_00_creature_template_npcbot_extras.sql create mode 100644 sql/Bots/updates/world/2022_06_22_01_generate_equips_sea_witch.sql create mode 100644 sql/Bots/updates/world/2022_06_22_02_creature_template.sql create mode 100644 sql/Bots/updates/world/2022_06_22_03_npc_text.sql create mode 100644 sql/Bots/updates/world/2022_06_23_00_command.sql create mode 100644 sql/Bots/updates/world/2022_06_24_00_npc_text.sql create mode 100644 sql/Bots/updates/world/2022_06_24_01_npc_text.sql create mode 100644 sql/Bots/updates/world/2022_06_24_02_command.sql create mode 100644 sql/Bots/updates/world/2022_07_04_00_command.sql create mode 100644 sql/Bots/updates/world/2022_07_25_00_npc_text.sql create mode 100644 sql/Bots/updates/world/2022_11_15_00_npc_text.sql create mode 100644 sql/Bots/updates/world/2022_11_30_00_command.sql create mode 100644 sql/Bots/updates/world/2022_12_08_00_npc_text.sql create mode 100644 sql/Bots/updates/world/2022_12_17_00_command.sql create mode 100644 sql/Bots/updates/world/2022_12_17_01_command.sql create mode 100644 sql/Bots/updates/world/2022_12_23_00_command.sql create mode 100644 sql/Bots/updates/world/2023_01_02_00_npc_text.sql create mode 100644 sql/Bots/updates/world/2023_01_10_00_command.sql create mode 100644 sql/Bots/updates/world/2023_01_11_00_command.sql create mode 100644 sql/Bots/updates/world/2023_03_10_00_command.sql create mode 100644 sql/Bots/updates/world/2023_03_10_01_command.sql create mode 100644 sql/Bots/updates/world/2023_03_12_00_creature_template_npcbot_wander_nodes.sql create mode 100644 sql/Bots/updates/world/2023_03_14_00_creature_wander_nodes.sql create mode 100644 sql/Bots/updates/world/2023_03_21_00_creature_template_npcbot_wander_nodes.sql create mode 100644 sql/Bots/updates/world/2023_04_04_00_creature_template_npcbot_wander_nodes.sql create mode 100644 sql/Bots/updates/world/2023_04_11_00_creature_template_npcbot_wander_nodes.sql create mode 100644 sql/Bots/updates/world/2023_04_16_00_creature_template_npcbot_wander_nodes.sql create mode 100644 sql/Bots/updates/world/2023_04_18_00_creature_template_npcbot_appearance.sql create mode 100644 sql/Bots/updates/world/2023_04_22_00_creature_template_npcbot_wander_nodes.sql create mode 100644 sql/Bots/updates/world/2023_04_28_00_creature_template.sql create mode 100644 sql/Bots/updates/world/2023_05_14_00_creature_template_npcbot_wander_nodes.sql create mode 100644 sql/Bots/updates/world/2023_05_16_00_npc_text.sql create mode 100644 sql/Bots/updates/world/2023_05_20_00_creature_template.sql create mode 100644 sql/Bots/updates/world/2023_05_20_01_creature_equip_template.sql create mode 100644 sql/Bots/updates/world/2023_05_20_02_creature_template_npcbot_extras.sql create mode 100644 sql/Bots/updates/world/2023_05_20_03_npc_text.sql create mode 100644 sql/Bots/updates/world/2023_06_02_00_creature_template_npcbot_extras.sql create mode 100644 sql/Bots/updates/world/2023_06_03_00_creature_template.sql create mode 100644 sql/Bots/updates/world/2023_06_09_00_creature_template_npcbot_wander_nodes.sql create mode 100644 sql/Bots/updates/world/2023_06_16_00_creature_template_npcbot_wander_nodes.sql create mode 100644 sql/Bots/updates/world/2023_06_18_00_command.sql create mode 100644 sql/Bots/updates/world/2023_06_18_01_creature_template_npcbot_disabled_items.sql create mode 100644 sql/Bots/updates/world/2023_06_19_00_creature_template_npcbot_disabled_items.sql create mode 100644 sql/Bots/updates/world/2023_06_19_01_creature_template.sql create mode 100644 sql/Bots/updates/world/2023_06_22_00_command.sql create mode 100644 sql/Bots/updates/world/2023_06_24_00_creature_template_npcbot_wander_nodes.sql create mode 100644 sql/Bots/updates/world/2023_06_26_00_creature_template_npcbot_wander_nodes.sql create mode 100644 sql/Bots/updates/world/2023_07_01_00_creature_template_npcbot_disabled_items.sql create mode 100644 sql/Bots/updates/world/2023_08_13_00_creature_template_npcbot_appearance.sql create mode 100644 sql/Bots/updates/world/2024_03_12_00_npc_text.sql create mode 100644 sql/Bots/updates/world/2024_03_18_00_npc_text.sql create mode 100644 sql/Bots/updates/world/2024_03_19_00_npc_text.sql create mode 100644 sql/Bots/updates/world/2024_05_29_00_command.sql create mode 100644 sql/Bots/updates/world/2024_08_14_00_npc_text.sql create mode 100644 sql/Bots/updates/world/2024_08_15_00_command.sql create mode 100644 sql/Bots/updates/world/2024_08_31_00_command.sql create mode 100644 src/server/game/AI/NpcBots/bot_Events.h create mode 100644 src/server/game/AI/NpcBots/bot_GridNotifiers.h create mode 100644 src/server/game/AI/NpcBots/bot_ai.cpp create mode 100644 src/server/game/AI/NpcBots/bot_ai.h create mode 100644 src/server/game/AI/NpcBots/bot_archmage_ai.cpp create mode 100644 src/server/game/AI/NpcBots/bot_bm_ai.cpp create mode 100644 src/server/game/AI/NpcBots/bot_crypt_lord_ai.cpp create mode 100644 src/server/game/AI/NpcBots/bot_dark_ranger_ai.cpp create mode 100644 src/server/game/AI/NpcBots/bot_death_knight_ai.cpp create mode 100644 src/server/game/AI/NpcBots/bot_dreadlord_ai.cpp create mode 100644 src/server/game/AI/NpcBots/bot_druid_ai.cpp create mode 100644 src/server/game/AI/NpcBots/bot_hunter_ai.cpp create mode 100644 src/server/game/AI/NpcBots/bot_mage_ai.cpp create mode 100644 src/server/game/AI/NpcBots/bot_necromancer_ai.cpp create mode 100644 src/server/game/AI/NpcBots/bot_paladin_ai.cpp create mode 100644 src/server/game/AI/NpcBots/bot_priest_ai.cpp create mode 100644 src/server/game/AI/NpcBots/bot_rogue_ai.cpp create mode 100644 src/server/game/AI/NpcBots/bot_sea_witch_ai.cpp create mode 100644 src/server/game/AI/NpcBots/bot_shaman_ai.cpp create mode 100644 src/server/game/AI/NpcBots/bot_spellbreaker_ai.cpp create mode 100644 src/server/game/AI/NpcBots/bot_sphynx_ai.cpp create mode 100644 src/server/game/AI/NpcBots/bot_warlock_ai.cpp create mode 100644 src/server/game/AI/NpcBots/bot_warrior_ai.cpp create mode 100644 src/server/game/AI/NpcBots/botcommands.cpp create mode 100644 src/server/game/AI/NpcBots/botcommon.h create mode 100644 src/server/game/AI/NpcBots/botdatamgr.cpp create mode 100644 src/server/game/AI/NpcBots/botdatamgr.h create mode 100644 src/server/game/AI/NpcBots/botdpstracker.cpp create mode 100644 src/server/game/AI/NpcBots/botdpstracker.h create mode 100644 src/server/game/AI/NpcBots/botdump.cpp create mode 100644 src/server/game/AI/NpcBots/botdump.h create mode 100644 src/server/game/AI/NpcBots/botgearscore.cpp create mode 100644 src/server/game/AI/NpcBots/botgearscore.h create mode 100644 src/server/game/AI/NpcBots/botgiver.cpp create mode 100644 src/server/game/AI/NpcBots/botgossip.h create mode 100644 src/server/game/AI/NpcBots/botlog.cpp create mode 100644 src/server/game/AI/NpcBots/botlog.h create mode 100644 src/server/game/AI/NpcBots/botmgr.cpp create mode 100644 src/server/game/AI/NpcBots/botmgr.h create mode 100644 src/server/game/AI/NpcBots/botspell.cpp create mode 100644 src/server/game/AI/NpcBots/botspell.h create mode 100644 src/server/game/AI/NpcBots/bottext.h create mode 100644 src/server/game/AI/NpcBots/botwanderful.cpp create mode 100644 src/server/game/AI/NpcBots/botwanderful.h create mode 100644 src/server/game/AI/NpcBots/bpet_ai.cpp create mode 100644 src/server/game/AI/NpcBots/bpet_ai.h create mode 100644 src/server/game/AI/NpcBots/bpet_archmage.cpp create mode 100644 src/server/game/AI/NpcBots/bpet_crypt_lord.cpp create mode 100644 src/server/game/AI/NpcBots/bpet_dark_ranger.cpp create mode 100644 src/server/game/AI/NpcBots/bpet_death_knight.cpp create mode 100644 src/server/game/AI/NpcBots/bpet_dreadlord.cpp create mode 100644 src/server/game/AI/NpcBots/bpet_druid.cpp create mode 100644 src/server/game/AI/NpcBots/bpet_hunter.cpp create mode 100644 src/server/game/AI/NpcBots/bpet_mage.cpp create mode 100644 src/server/game/AI/NpcBots/bpet_necromancer.cpp create mode 100644 src/server/game/AI/NpcBots/bpet_priest.cpp create mode 100644 src/server/game/AI/NpcBots/bpet_sea_witch.cpp create mode 100644 src/server/game/AI/NpcBots/bpet_shaman.cpp create mode 100644 src/server/game/AI/NpcBots/bpet_warlock.cpp create mode 100644 src/server/game/AI/NpcBots/lib/botlogtraits.h create mode 100644 src/server/game/AI/NpcBots/lib/bottraits.h diff --git a/.circleci/config.yml b/.circleci/config.yml index ba871d0e0..64ace6d46 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -52,11 +52,7 @@ jobs: command: | mkdir bin cd bin - if [ "$DOCKERHUB_PUSH_IMAGES" == "TRUE" ]; then - cmake ../ -DWITH_WARNINGS=1 -DWITH_COREDEBUG=0 -DUSE_COREPCH=1 -DUSE_SCRIPTPCH=1 -DTOOLS=1 -DSCRIPTS=static -DSERVERS=1 -DNOJEM=0 -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS="-Werror" -DCMAKE_CXX_FLAGS="-Werror" -DCMAKE_INSTALL_PREFIX=check_install -DCMAKE_INSTALL_RPATH=\$ORIGIN/../lib -DBUILD_TESTING=1 - else - cmake ../ -DWITH_WARNINGS=1 -DWITH_COREDEBUG=0 -DUSE_COREPCH=1 -DUSE_SCRIPTPCH=1 -DTOOLS=1 -DSCRIPTS=static -DSERVERS=1 -DNOJEM=0 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-Werror" -DCMAKE_CXX_FLAGS="-Werror" -DCMAKE_C_FLAGS_DEBUG="-DNDEBUG" -DCMAKE_CXX_FLAGS_DEBUG="-DNDEBUG" -DCMAKE_INSTALL_PREFIX=check_install -DCMAKE_INSTALL_RPATH=\$ORIGIN/../lib -DBUILD_TESTING=1 - fi + cmake ../ -DWITH_WARNINGS=1 -DWITH_COREDEBUG=0 -DUSE_COREPCH=1 -DUSE_SCRIPTPCH=1 -DTOOLS=1 -DSCRIPTS=static -DSERVERS=1 -DNOJEM=0 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-Werror" -DCMAKE_CXX_FLAGS="-Werror" -DCMAKE_C_FLAGS_DEBUG="-DNDEBUG" -DCMAKE_CXX_FLAGS_DEBUG="-DNDEBUG" -DCMAKE_INSTALL_PREFIX=check_install -DCMAKE_INSTALL_RPATH=\$ORIGIN/../lib -DBUILD_TESTING=1 cd .. - run: name: Build @@ -74,27 +70,6 @@ jobs: cd bin/check_install/bin ./authserver --version ./worldserver --version - - setup_remote_docker: - version: default - - run: - name: Create docker images - command: | - cd bin/check_install - cp -r ../../contrib/Docker/* . - cp -r ../../sql ./sql - image_prefix=$(echo $CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME | tr '[:upper:]' '[:lower:]') - echo $image_prefix - docker build --file Dockerfile --force-rm --tag $image_prefix:$CIRCLE_SHA1 --tag $image_prefix:$(echo $CIRCLE_BRANCH | tr '/' '-' | tr '[:upper:]' '[:lower:]') . - docker save $image_prefix | gzip > ../../docker.tar.gz - if [ "$DOCKERHUB_PUSH_IMAGES" == "TRUE" ]; then - if [ "$CIRCLE_BRANCH" == "3.3.5" ] || [ "$CIRCLE_BRANCH" == "master" ] || [ "$CIRCLE_BRANCH" == "wotlk_classic" ]; then - docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_PASSWORD - echo "Pushing docker image to dockerhub" - docker push --all-tags $image_prefix - fi - fi - - store_artifacts: - path: docker.tar.gz nopch: docker: - image: trinitycore/circle-ci:3.3.5-base-22.04 diff --git a/.github/workflows/gcc-build.yml b/.github/workflows/gcc-build.yml index 1f6ade700..2becfb5fa 100644 --- a/.github/workflows/gcc-build.yml +++ b/.github/workflows/gcc-build.yml @@ -2,8 +2,15 @@ name: GCC on: push: + branches: + - 'npcbots_3.3.5' + - 'CI' pull_request: +concurrency: + group: ${{ github.head_ref }} || concat(${{ github.ref }}, ${{ github.workflow }}) + cancel-in-progress: true + jobs: build: runs-on: ubuntu-22.04 diff --git a/README.md b/README.md index 84675eb04..0bebaba9e 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ # ![logo](https://community.trinitycore.org/public/style_images/1_trinitycore.png) TrinityCore (3.3.5) -[![Average time to resolve an issue](https://isitmaintained.com/badge/resolution/TrinityCore/TrinityCore.svg)](https://isitmaintained.com/project/TrinityCore/TrinityCore "Average time to resolve an issue") [![Percentage of issues still open](https://isitmaintained.com/badge/open/TrinityCore/TrinityCore.svg)](https://isitmaintained.com/project/TrinityCore/TrinityCore "Percentage of issues still open") - -------------- @@ -19,12 +17,12 @@ ## Build Status -master | 3.3.5 | wotlk_classic -:------------: | :------------: | :------------: -[![master Build Status](https://circleci.com/gh/TrinityCore/TrinityCore/tree/master.svg?style=shield)](https://circleci.com/gh/TrinityCore/TrinityCore/tree/master) | [![3.3.5 Build Status](https://circleci.com/gh/TrinityCore/TrinityCore/tree/3.3.5.svg?style=shield)](https://circleci.com/gh/TrinityCore/TrinityCore/tree/3.3.5) | [![wotlk_classic Build Status](https://circleci.com/gh/TrinityCore/TrinityCore/tree/wotlk_classic.svg?style=shield)](https://circleci.com/gh/TrinityCore/TrinityCore/tree/wotlk_classic) -[![master Build status](https://ci.appveyor.com/api/projects/status/54d0u1fxe50ad80o/branch/master?svg=true)](https://ci.appveyor.com/project/DDuarte/trinitycore/branch/master) | [![Build status](https://ci.appveyor.com/api/projects/status/54d0u1fxe50ad80o/branch/3.3.5?svg=true)](https://ci.appveyor.com/project/DDuarte/trinitycore/branch/3.3.5) | [![Build status](https://ci.appveyor.com/api/projects/status/54d0u1fxe50ad80o/branch/wotlk_classic?svg=true)](https://ci.appveyor.com/project/DDuarte/trinitycore/branch/wotlk_classic) -[![master GCC Build status](https://github.com/TrinityCore/TrinityCore/actions/workflows/gcc-build.yml/badge.svg?branch=master&event=push)](https://github.com/TrinityCore/TrinityCore/actions?query=workflow%3AGCC+branch%3Amaster+event%3Apush) | [![3.3.5 GCC Build status](https://github.com/TrinityCore/TrinityCore/actions/workflows/gcc-build.yml/badge.svg?branch=3.3.5&event=push)](https://github.com/TrinityCore/TrinityCore/actions?query=workflow%3AGCC+branch%3A3.3.5+event%3Apush) | [![wotlk_classic GCC Build status](https://github.com/TrinityCore/TrinityCore/actions/workflows/gcc-build.yml/badge.svg?branch=wotlk_classic&event=push)](https://github.com/TrinityCore/TrinityCore/actions?query=workflow%3AGCC+branch%3Awotlk_classic+event%3Apush) -[![Coverity Scan Build Status](https://scan.coverity.com/projects/435/badge.svg)](https://scan.coverity.com/projects/435) | [![Coverity Scan Build Status](https://scan.coverity.com/projects/4656/badge.svg)](https://scan.coverity.com/projects/4656) | +[![Fetch status](https://github.com/trickerer/NPCBots-cron/actions/workflows/automerge.yml/badge.svg)](https://github.com/trickerer/NPCBots-cron/actions/workflows/automerge.yml) + +3.3.5 +:------------: +[![GCC](https://github.com/trickerer/TrinityCore-3.3.5-with-NPCBots/actions/workflows/gcc-build.yml/badge.svg)](https://github.com/trickerer/TrinityCore-3.3.5-with-NPCBots/actions/workflows/gcc-build.yml) +[![Build status](https://ci.appveyor.com/api/projects/status/jck8c86eiti50v26/branch/npcbots_3.3.5?svg=true)](https://ci.appveyor.com/project/trickerer/trinitycore-3-3-5-with-npcbots/branch/npcbots_3.3.5) ## Introduction @@ -35,13 +33,9 @@ based on the code of that project with extensive changes over time to optimize, improve and cleanup the codebase at the same time as improving the in-game mechanics and functionality. -It is completely open source; community involvement is highly encouraged. - -If you wish to contribute ideas or code, please visit our site linked below or -make pull requests to our [Github repository](https://github.com/TrinityCore/TrinityCore/pulls). +[NPCBots](https://github.com/trickerer/Trinity-Bots) is TrinityCore mod. -For further information on the TrinityCore project, please visit our project -website at [TrinityCore.org](https://www.trinitycore.org). +It is completely open source; community involvement is highly encouraged. ## Requirements @@ -52,27 +46,22 @@ Windows, Linux and macOS. ## Install -Detailed installation guides are available in the [wiki](https://trinitycore.info/en/home) for +Detailed TrinityCore installation guides are available in the [wiki](https://trinitycore.info/en/home) for Windows, Linux and macOS. +NPCBots installation guide is available in the [NPCBots Readme](https://github.com/trickerer/Trinity-Bots#npcbot-mod-installation). ## Reporting issues -Issues can be reported via the [Github issue tracker](https://github.com/TrinityCore/TrinityCore/labels/Branch-3.3.5a). +NPCBots issues can be reported via the [Github issue tracker](https://github.com/trickerer/Trinity-Bots/issues/). Please take the time to review existing issues before submitting your own to prevent duplicates. -In addition, thoroughly read through the [issue tracker guide](https://community.trinitycore.org/topic/37-the-trinitycore-issuetracker-and-you/) to ensure -your report contains the required information. Incorrect or poorly formed -reports are wasteful and are subject to deletion. - ## Submitting fixes -C++ fixes are submitted as pull requests via Github. For more information on how to -properly submit a pull request, read the [how-to: maintain a remote fork](https://community.trinitycore.org/topic/9002-howto-maintain-a-remote-fork-for-pull-requests-tortoisegit/). -For SQL only fixes, open a ticket; if a bug report exists for the bug, post on an existing ticket. +C++ fixes are submitted as [pull requests](https://github.com/trickerer/TrinityCore-3.3.5-with-NPCBots/pulls). ## Copyright @@ -89,7 +78,8 @@ Read file [AUTHORS](AUTHORS). ## Links -* [Website](https://www.trinitycore.org) -* [Wiki](https://www.trinitycore.info) -* [Forums](https://talk.trinitycore.org/) -* [Discord](https://discord.trinitycore.org/) +* [NPCBots Readme](https://github.com/trickerer/Trinity-Bots/) +* [TrinityCore Website](https://www.trinitycore.org) +* [TrinityCore Wiki](https://www.trinitycore.info) +* [TrinityCore Forums](https://talk.trinitycore.org/) +* [TrinityCore Discord](https://discord.trinitycore.org/) diff --git a/sql/Bots/1_world_bot_appearance.sql b/sql/Bots/1_world_bot_appearance.sql new file mode 100644 index 000000000..82a7bcafe --- /dev/null +++ b/sql/Bots/1_world_bot_appearance.sql @@ -0,0 +1,305 @@ +-- Handmade data, very sensitive +DROP TABLE IF EXISTS `creature_template_npcbot_appearance`; + +CREATE TABLE `creature_template_npcbot_appearance` ( + `entry` mediumint(8) unsigned NOT NULL AUTO_INCREMENT, + `name*` char(16) DEFAULT 'unk' COMMENT 'unused', + `gender` tinyint(3) unsigned NOT NULL DEFAULT '0', + `skin` tinyint(3) unsigned NOT NULL DEFAULT '0', + `face` tinyint(3) unsigned NOT NULL DEFAULT '0', + `hair` tinyint(3) unsigned NOT NULL DEFAULT '0', + `haircolor` tinyint(3) unsigned NOT NULL DEFAULT '0', + `features` tinyint(3) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`entry`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +insert into `creature_template_npcbot_appearance`(`entry`,`name*`,`gender`,`skin`,`face`,`hair`,`haircolor`,`features`) +values +(70001,'Llane',0,3,0,1,1,6), +(70002,'Thran',0,0,0,4,5,4), +(70003,'Lyria',1,3,4,6,4,0), +(70004,'Ander',0,1,11,1,7,6), +(70005,'Malosh',0,7,0,5,5,5), +(70006,'Granis',0,0,2,9,4,4), +(70007,'Kelstrum',0,5,4,2,2,2), +(70008,'Dannal',0,1,0,8,5,0), +(70009,'Austil',0,4,1,8,1,7), +(70010,'Torm',0,7,1,2,0,2), +(70011,'Sark',0,0,3,7,0,5), +(70012,'Ker',1,4,0,0,0,1), +(70013,'Harutt',0,1,3,7,0,4), +(70014,'Krang',0,5,2,6,2,0), +(70015,'Frang',0,0,4,1,1,3), +(70016,'Tarshaw',0,3,3,1,0,7), +(70017,'Grezz',0,3,5,1,0,3), +(70018,'Sorek',0,2,1,3,1,6), +(70019,'Zel\'mak',0,1,0,4,8,1), +(70020,'Alyissia',1,5,1,5,2,0), +(70021,'Kyra',1,6,8,4,6,1), +(70022,'Arias\'ta',1,0,6,3,4,1), +(70023,'Sildanair',1,3,0,2,4,2), +(70024,'Chris',0,3,2,2,2,10), +(70025,'Angela',1,2,7,3,8,6), +(70026,'Baltus',0,0,0,1,8,1), +(70027,'Kelv',0,5,1,7,3,1), +(70028,'Bilban',0,2,2,2,7,1), +(70029,'Wu',0,4,5,1,0,8), +(70030,'Ilsa',1,5,6,7,3,4), +(70031,'Darnath',0,7,8,3,0,0), +(70032,'Evencane',0,5,9,2,4,2), +(70033,'Kore',0,4,0,2,1,6), +(70034,'Ahonan',0,6,1,1,6,5), +(70035,'Behomat',0,4,8,3,6,3), +(70036,'Ruada',1,1,2,1,6,0), +(70037,'Kazi',1,1,2,3,3,0), +(70038,'Kerra',1,2,0,2,2,0), +(70051,'Sammuel',0,9,11,6,2,7), +(70052,'Bromos',0,6,4,2,9,1), +(70053,'Wilhelm',0,4,2,2,1,6), +(70054,'Grayson',0,3,2,2,0,2), +(70055,'Azar',0,2,4,3,1,6), +(70056,'Valgar',0,4,3,0,0,0), +(70057,'Beldruk',0,4,6,2,4,2), +(70058,'Brandur',0,5,4,1,1,3), +(70059,'Arthur',0,1,0,1,5,2), +(70060,'Katherine',1,1,3,2,4,0), +(70061,'Karman',0,3,11,10,5,8), +(70062,'Jesthenis',0,4,0,0,0,0), +(70063,'Noellene',1,0,1,1,1,0), +(70064,'Aurelon',0,8,0,0,0,0), +(70065,'Osselan',0,8,1,1,2,0), +(70066,'Ithelis',0,9,2,2,2,0), +(70067,'Bachi',0,3,0,10,1,0), +(70068,'Baatun',0,6,4,3,0,7), +(70069,'Kavaan',0,2,5,5,6,4), +(70070,'Tullas',1,0,9,9,2,0), +(70071,'Jol',1,0,8,8,6,0), +(70072,'Cyssa',1,4,1,7,6,0), +(70073,'Pyreanor',0,3,3,2,7,1), +(70074,'Rukua',1,5,8,4,4,1), +(70101,'Thorgas',0,4,5,8,4,8), +(70102,'Ogromm',0,4,4,3,1,4), +(70103,'Grif',0,0,1,5,4,4), +(70104,'Kragg',0,7,4,3,2,2), +(70105,'Kary',1,2,1,1,2,1), +(70106,'Holt',0,18,3,4,0,3), +(70107,'Urek',0,4,2,2,1,1), +(70108,'Lanka',0,4,2,2,1,2), +(70109,'Yaw',0,1,1,5,2,2), +(70110,'Jen\'shan',1,3,0,2,6,3), +(70111,'Thotar',0,0,3,3,1,9), +(70112,'Ormak',0,5,8,1,0,5), +(70113,'Xor\'juul',0,2,3,2,0,1), +(70114,'Sian\'dur',1,2,4,1,2,4), +(70115,'Ayanna',1,5,0,6,5,8), +(70116,'Dazalar',0,8,0,6,2,3), +(70117,'Danlaar',0,0,0,3,6,0), +(70118,'Jeen\'ra',1,7,2,1,4,1), +(70119,'Jocaste',1,6,1,1,6,1), +(70120,'Dorion',0,4,6,5,0,5), +(70121,'Daera',1,0,3,12,4,0), +(70122,'Olmin',0,3,1,2,2,4), +(70123,'Regnus',0,3,9,1,4,2), +(70124,'Kaerbrus',0,2,0,0,2,2), +(70125,'Einris',1,1,1,5,4,0), +(70126,'Ulfir',0,6,2,5,8,1), +(70127,'Thorfin',0,2,2,5,4,7), +(70128,'Alenndaar',0,2,6,3,2,2), +(70129,'Dargh',0,3,1,1,4,4), +(70130,'Sallina',1,4,2,1,1,0), +(70131,'Hannovia',1,0,2,2,1,0), +(70132,'Keilnei',1,7,5,2,4,1), +(70133,'Tana',1,8,1,1,2,0), +(70134,'Oninath',0,0,0,0,0,0), +(70135,'Zandine',1,8,0,1,4,0), +(70136,'Deremiis',0,11,0,5,6,7), +(70137,'Acteon',0,2,0,0,0,0), +(70138,'Vord',0,11,0,5,6,7), +(70139,'Killac',0,11,0,5,6,7), +(70151,'Jorik',0,3,0,11,1,2), +(70152,'Solm',0,7,8,4,9,6), +(70153,'Keryn',1,4,4,7,0,0), +(70154,'Osborne',0,4,9,9,0,1), +(70155,'Hogral',0,6,7,10,5,7), +(70156,'Ian',0,5,9,9,1,6), +(70157,'David',0,4,2,1,1,1), +(70158,'Marion',1,5,2,3,5,7), +(70159,'Rwag',0,0,0,0,0,0), +(70160,'Kaplak',0,3,5,0,0,3), +(70161,'Gest',0,4,0,2,0,1), +(70162,'Ormok',0,3,8,4,0,3), +(70163,'Shenthul',0,3,0,5,5,1), +(70164,'Frahun',0,2,1,3,5,4), +(70165,'Jannok',0,3,2,4,5,3), +(70166,'Syurna',1,0,3,5,4,0), +(70167,'Erion',0,5,0,3,4,3), +(70168,'Anishar',0,5,6,4,6,5), +(70169,'Carolyn',1,0,6,3,1,7), +(70170,'Miles',0,0,0,1,9,8), +(70171,'Gregory',0,3,7,8,4,14), +(70172,'Hulfdan',0,8,8,8,5,6), +(70173,'Ormyr',0,0,6,4,4,4), +(70174,'Fenthwick',0,0,2,2,5,0), +(70175,'Fahrad',0,3,0,9,4,2), +(70176,'Tony',0,3,0,1,0,2), +(70177,'Kariel',0,1,0,0,0,0), +(70178,'Tannaria',1,2,2,0,2,0), +(70179,'Zelanis',0,0,1,1,1,0), +(70180,'Elara',1,2,2,1,4,0), +(70181,'Nerisen',0,1,1,2,1,0), +(70201,'Anetta',1,0,9,14,5,0), +(70202,'Laurena',1,0,1,4,5,0), +(70203,'Josetta',1,1,3,17,2,0), +(70204,'Branstock',0,2,7,1,8,0), +(70205,'Maxan',0,2,0,6,1,5), +(70206,'Duesten',0,0,9,5,1,0), +(70207,'Beryl',0,0,0,0,0,12), +(70208,'Miles',0,0,2,7,9,2), +(70209,'Malakai',0,4,3,2,6,8), +(70210,'Cobb',0,5,6,6,4,14), +(70211,'Shanda',1,1,0,2,7,4), +(70212,'Laurna',1,0,7,0,2,4), +(70213,'Tai\'jin',1,0,3,1,6,5), +(70214,'Ken\'jai',0,5,2,4,3,0), +(70215,'Astarii',1,0,0,0,0,0), +(70216,'Jandria',1,1,1,5,3,0), +(70217,'Lariia',1,0,3,3,6,0), +(70218,'Lankester',0,2,0,9,4,16), +(70219,'Lazarus',0,0,0,6,6,14), +(70220,'Theodrus',0,2,6,5,7,4), +(70221,'Braenna',1,0,1,1,0,0), +(70222,'Toldren',0,4,1,3,3,10), +(70223,'Benjamin',0,9,11,0,0,8), +(70224,'Joshua',0,3,0,1,1,8), +(70225,'Zayus',0,2,0,3,1,4), +(70226,'X\'yera',0,2,1,2,4,4), +(70227,'Ur\'kyo',0,0,2,2,3,2), +(70228,'Nara',1,0,7,4,1,3), +(70229,'Alathea',1,4,3,0,4,5), +(70230,'Rohan',0,0,9,4,7,5), +(70231,'Arena',1,0,2,2,1,0), +(70232,'Ponaris',0,1,0,1,2,0), +(70233,'Zalduun',0,9,0,0,0,0), +(70234,'Aldrae',0,3,1,1,1,0), +(70235,'Lotheolan',0,8,8,1,2,0), +(70236,'Belestra',1,4,0,1,3,0), +(70237,'Caedmos',0,4,0,0,4,3), +(70238,'Guvan',0,12,0,5,6,4), +(70239,'Izmir',0,7,0,4,6,6), +(70240,'Fallat',0,7,1,4,6,6), +(70251,'Haromm',0,0,3,5,0,6), +(70252,'Siln',1,10,1,3,2,1), +(70253,'Tigor',0,13,2,2,2,1), +(70254,'Beram',0,10,0,2,0,5), +(70255,'Meela',1,8,1,3,0,0), +(70256,'Narm',0,17,0,6,1,4), +(70257,'Shikrik',1,3,4,5,1,1), +(70258,'Swart',0,0,4,5,0,7), +(70259,'Kardris',1,0,4,7,0,6), +(70260,'Sian\'tsu',1,3,3,3,1,3), +(70261,'Sagorne',0,6,1,1,1,2), +-- (70262,'Firmanvaar',0,0,0,1,0,0), +-- (70263,'Nobundo',0,0,0,0,0,0), +-- (70264,'Tuluun',0,0,0,0,0,0), +(70265,'Sulaa',1,7,2,2,4,6), +(70266,'Hobahken',0,0,0,0,0,0), +(70267,'Umbrua',1,1,7,5,5,1), +(70268,'Javad',0,4,1,6,0,1), +(70301,'Khelden',0,7,5,7,0,5), +(70302,'Zaldimar',0,2,10,11,9,3), +(70303,'Maginor',0,1,9,8,7,4), +(70304,'Marryk',0,0,4,6,7,1), +(70305,'Magis',0,1,5,4,6,4), +(70306,'Isabella',1,2,0,7,1,5), +(70307,'Cain',0,0,0,7,1,10), +(70308,'Shymm',0,5,6,6,5,12), +(70309,'Ursyn',1,0,5,9,1,7), +(70310,'Thurston',0,4,1,5,5,1), +(70311,'Pierce',0,0,2,5,8,2), +(70312,'Anastasia',1,2,7,3,8,5), +(70313,'Bink',1,2,1,1,6,0), +(70314,'Juli',1,1,5,5,3,0), +(70315,'Nittlebur',0,0,4,0,7,6), +(70316,'Jennea',1,5,4,5,4,4), +(70317,'Un\'Thuwa',0,1,2,1,3,4), +(70318,'Pephredo',1,0,4,1,2,5), +(70319,'Enyo',1,3,4,4,0,0), +(70320,'Mai\'ah',1,1,2,1,1,5), +(70321,'Deino',1,2,0,3,3,2), +(70322,'Uthel\'nay',0,3,4,2,3,0), +(70323,'Dink',0,1,3,2,6,1), +(70324,'Julia',1,2,6,2,0,0), +(70325,'Garridel',1,3,1,2,2,0), +(70326,'Valaatu',1,9,6,7,6,5), +(70327,'Zaedana',1,3,2,2,1,0), +(70328,'Quithas',0,2,3,1,1,0), +(70329,'Inethven',0,0,0,0,3,0), +(70330,'Narinth',1,0,2,2,0,0), +(70331,'Edirah',1,5,0,5,3,0), +(70332,'Valustraa',1,5,5,9,6,5), +(70333,'Semid',0,8,9,8,0,7), +(70334,'Harnan',0,8,8,7,1,3), +(70335,'Bati',1,8,1,2,1,0), +(70336,'Derek',0,5,3,3,1,5), +(70351,'Drusilla',1,0,10,4,0,0), +(70352,'Alamar',0,2,2,3,2,5), +(70353,'Demisette',1,0,9,2,0,0), +(70354,'Maximillian',0,1,10,6,0,6), +(70355,'Kartosh',0,0,7,0,1,6), +(70356,'Maximillion',0,5,0,9,5,5), +(70357,'Rupert',0,0,0,0,0,8), +(70358,'Nartok',0,0,6,4,0,1), +(70359,'Dhugru',0,5,6,4,3,9), +(70360,'Grol\'dar',0,3,2,3,1,2), +(70361,'Mirket',1,5,3,7,0,5), +(70362,'Zevrost',0,3,7,5,5,9), +(70363,'Kaal',0,3,1,4,2,3), +(70364,'Luther',0,4,9,0,0,0), +(70365,'Richard',0,5,5,0,0,0), +(70366,'Thistleheart',0,3,2,6,3,0), +(70367,'Briarthorn',0,0,6,1,0,3), +(70368,'Alexander',0,0,10,11,0,6), +(70369,'Ursula',1,1,4,18,3,6), +(70370,'Sandahl',0,4,2,11,1,6), +(70371,'Gimrizz',0,2,5,2,0,3), +(70372,'Teli\'Larien',0,0,0,0,0,0), +(70373,'Celoenus',0,0,0,2,0,0), +(70374,'Alamma',0,0,1,0,0,0), +(70375,'Talionia',1,0,2,1,2,0), +(70376,'Zanien',0,0,0,0,3,0), +(70377,'Babagaya',1,0,6,0,3,0), +(70401,'Turak',0,4,3,5,0,5), +(70402,'Sheal',1,7,0,3,1,1), +(70403,'Kym',1,9,2,3,0,0), +(70404,'Gart',0,13,1,3,2,2), +(70405,'Gennia',1,9,3,0,1,3), +(70406,'Mardant',0,1,1,2,0,4), +(70407,'Kal',0,2,2,4,4,2), +(70408,'Mathrengyl',0,5,0,2,6,3), +(70409,'Denatharion',0,4,8,3,2,5), +(70410,'Fylerian',0,2,1,4,5,0), +(70411,'Sheldras',0,0,3,2,2,4), +(70412,'Theridran',0,2,1,3,7,3), +(70413,'Maldryn',0,2,8,5,4,0), +(70414,'Jannos',0,5,3,2,1,5), +(70415,'Golhine',0,0,2,6,0,4), +(70416,'Loganaar',0,2,1,1,0,1), +(70417,'Harene',1,6,0,3,2,4), +(70418,'Shalannius',0,4,3,0,4,5), +(70451,'Siouxsie',1,9,9,3,4,9), +(70452,'Imhadria',1,10,8,6,1,0), +(70453,'Vaelen',0,16,9,11,9,8), +(70454,'Mynx',1,10,0,17,11,0), +(70455,'Lankral',0,6,13,16,12,4), +(70456,'Sliver',0,10,11,11,6,0), +(70457,'Vereth',0,4,0,8,9,10), +(70458,'Arly',1,7,9,2,8,4), +(70459,'Setaal',1,14,12,15,9,2), +(70460,'Uzo',0,8,7,8,9,18), +(70461,'Illyrie',1,9,11,1,12,0), +(70462,'Crok',0,11,9,10,7,3), +(70463,'Zor\'be',0,8,7,6,9,18), +(70464,'Datura',1,10,11,11,18,0), +(70465,'Stefan',0,10,12,6,12,2), +-- (70551,'Gorkramato',0,0,14,9,7,5); +(70555,'Detrae',0,3,12,11,19,4); diff --git a/sql/Bots/2_world_bot_extras.sql b/sql/Bots/2_world_bot_extras.sql new file mode 100644 index 000000000..2a17a85a5 --- /dev/null +++ b/sql/Bots/2_world_bot_extras.sql @@ -0,0 +1,358 @@ +DROP TABLE IF EXISTS `creature_template_npcbot_extras`; + +CREATE TABLE `creature_template_npcbot_extras` ( + `entry` mediumint(8) unsigned NOT NULL, + `class` tinyint(3) unsigned NOT NULL DEFAULT '1', + `race` tinyint(3) unsigned NOT NULL DEFAULT '1', + PRIMARY KEY (`entry`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +INSERT INTO `creature_template_npcbot_extras` (`entry`,`class`,`race`) VALUES +('70001', '1', '1'), +('70002', '1', '3'), +('70003', '1', '1'), +('70004', '1', '3'), +('70005', '1', '2'), +('70006', '1', '3'), +('70007', '1', '3'), +('70008', '1', '5'), +('70009', '1', '5'), +('70010', '1', '6'), +('70011', '1', '6'), +('70012', '1', '6'), +('70013', '1', '6'), +('70014', '1', '6'), +('70015', '1', '2'), +('70016', '1', '2'), +('70017', '1', '2'), +('70018', '1', '2'), +('70019', '1', '8'), +('70020', '1', '4'), +('70021', '1', '4'), +('70022', '1', '4'), +('70023', '1', '4'), +('70024', '1', '5'), +('70025', '1', '5'), +('70026', '1', '5'), +('70027', '1', '3'), +('70028', '1', '7'), +('70029', '1', '1'), +('70030', '1', '1'), +('70031', '1', '4'), +('70032', '1', '1'), +('70033', '1', '11'), +('70034', '1', '11'), +('70035', '1', '11'), +('70036', '1', '11'), +('70037', '1', '11'), +('70038', '1', '4'), +('70051', '2', '1'), +('70052', '2', '3'), +('70053', '2', '1'), +('70054', '2', '1'), +('70055', '2', '3'), +('70056', '2', '3'), +('70057', '2', '3'), +('70058', '2', '3'), +('70059', '2', '1'), +('70060', '2', '1'), +('70061', '2', '1'), +('70062', '2', '10'), +('70063', '2', '10'), +('70064', '2', '11'), +('70065', '2', '10'), +('70066', '2', '10'), +('70067', '2', '10'), +('70068', '2', '11'), +('70069', '2', '11'), +('70070', '2', '11'), +('70071', '2', '11'), +('70072', '2', '10'), +('70073', '2', '10'), +('70074', '2', '11'), +('70101', '3', '3'), +('70102', '3', '2'), +('70103', '3', '3'), +('70104', '3', '2'), +('70105', '3', '6'), +('70106', '3', '6'), +('70107', '3', '6'), +('70108', '3', '6'), +('70109', '3', '6'), +('70110', '3', '8'), +('70111', '3', '2'), +('70112', '3', '2'), +('70113', '3', '8'), +('70114', '3', '8'), +('70115', '3', '4'), +('70116', '3', '4'), +('70117', '3', '4'), +('70118', '3', '4'), +('70119', '3', '4'), +('70120', '3', '4'), +('70121', '3', '3'), +('70122', '3', '3'), +('70123', '3', '3'), +('70124', '3', '4'), +('70125', '3', '3'), +('70126', '3', '3'), +('70127', '3', '3'), +('70128', '3', '4'), +('70129', '3', '3'), +('70130', '3', '10'), +('70131', '3', '10'), +('70132', '3', '11'), +('70133', '3', '10'), +('70134', '3', '10'), +('70135', '3', '11'), +('70136', '3', '11'), +('70137', '3', '11'), +('70138', '3', '11'), +('70139', '3', '11'), +('70151', '4', '1'), +('70152', '4', '3'), +('70153', '4', '1'), +('70154', '4', '1'), +('70155', '4', '3'), +('70156', '4', '1'), +('70157', '4', '5'), +('70158', '4', '5'), +('70159', '4', '2'), +('70160', '4', '2'), +('70161', '4', '2'), +('70162', '4', '2'), +('70163', '4', '8'), +('70164', '4', '4'), +('70165', '4', '4'), +('70166', '4', '4'), +('70167', '4', '4'), +('70168', '4', '4'), +('70169', '4', '5'), +('70170', '4', '5'), +('70171', '4', '5'), +('70172', '4', '3'), +('70173', '4', '3'), +('70174', '4', '7'), +('70175', '4', '1'), +('70176', '4', '1'), +('70177', '4', '10'), +('70178', '4', '10'), +('70179', '4', '10'), +('70180', '4', '10'), +('70181', '4', '10'), +('70201', '5', '1'), +('70202', '5', '1'), +('70203', '5', '1'), +('70204', '5', '3'), +('70205', '5', '3'), +('70206', '5', '5'), +('70207', '5', '5'), +('70208', '5', '5'), +('70209', '5', '5'), +('70210', '5', '5'), +('70211', '5', '4'), +('70212', '5', '4'), +('70213', '5', '8'), +('70214', '5', '8'), +('70215', '5', '4'), +('70216', '5', '4'), +('70217', '5', '4'), +('70218', '5', '5'), +('70219', '5', '5'), +('70220', '5', '3'), +('70221', '5', '3'), +('70222', '5', '3'), +('70223', '5', '1'), +('70224', '5', '1'), +('70225', '5', '8'), +('70226', '5', '8'), +('70227', '5', '8'), +('70228', '5', '4'), +('70229', '5', '4'), +('70230', '5', '3'), +('70231', '5', '10'), +('70232', '5', '10'), +('70233', '5', '11'), +('70234', '5', '10'), +('70235', '5', '10'), +('70236', '5', '10'), +('70237', '5', '11'), +('70238', '5', '11'), +('70239', '5', '11'), +('70240', '5', '11'), +('70251', '7', '2'), +('70252', '7', '6'), +('70253', '7', '6'), +('70254', '7', '6'), +('70255', '7', '6'), +('70256', '7', '6'), +('70257', '7', '2'), +('70258', '7', '2'), +('70259', '7', '2'), +('70260', '7', '8'), +('70261', '7', '6'), +('70265', '7', '11'), +('70267', '7', '11'), +('70268', '7', '11'), +('70301', '8', '1'), +('70302', '8', '1'), +('70303', '8', '1'), +('70304', '8', '7'), +('70305', '8', '7'), +('70306', '8', '5'), +('70307', '8', '5'), +('70308', '8', '5'), +('70309', '8', '5'), +('70310', '8', '5'), +('70311', '8', '5'), +('70312', '8', '5'), +('70313', '8', '7'), +('70314', '8', '7'), +('70315', '8', '7'), +('70316', '8', '1'), +('70317', '8', '8'), +('70318', '8', '8'), +('70319', '8', '8'), +('70320', '8', '8'), +('70321', '8', '8'), +('70322', '8', '8'), +('70323', '8', '7'), +('70324', '8', '10'), +('70325', '8', '10'), +('70326', '8', '11'), +('70327', '8', '10'), +('70328', '8', '10'), +('70329', '8', '10'), +('70330', '8', '10'), +('70331', '8', '11'), +('70332', '8', '11'), +('70333', '8', '11'), +('70334', '8', '11'), +('70335', '8', '11'), +('70336', '8', '5'), +('70351', '9', '1'), +('70352', '9', '7'), +('70353', '9', '1'), +('70354', '9', '1'), +('70355', '9', '2'), +('70356', '9', '5'), +('70357', '9', '5'), +('70358', '9', '2'), +('70359', '9', '2'), +('70360', '9', '2'), +('70361', '9', '2'), +('70362', '9', '2'), +('70363', '9', '2'), +('70364', '9', '5'), +('70365', '9', '5'), +('70366', '9', '7'), +('70367', '9', '7'), +('70368', '9', '1'), +('70369', '9', '1'), +('70370', '9', '1'), +('70371', '9', '7'), +('70372', '9', '10'), +('70373', '9', '10'), +('70374', '9', '10'), +('70375', '9', '10'), +('70376', '9', '10'), +('70377', '9', '7'), +('70401', '11', '6'), +('70402', '11', '6'), +('70403', '11', '6'), +('70404', '11', '6'), +('70405', '11', '6'), +('70406', '11', '4'), +('70407', '11', '4'), +('70408', '11', '4'), +('70409', '11', '4'), +('70410', '11', '4'), +('70411', '11', '4'), +('70412', '11', '4'), +('70413', '11', '6'), +('70414', '11', '6'), +('70415', '11', '4'), +('70416', '11', '4'), +('70417', '11', '6'), +('70418', '11', '4'), +('70451', '6', '4'), +('70452', '6', '10'), +('70453', '6', '11'), +('70454', '6', '10'), +('70455', '6', '1'), +('70456', '6', '10'), +('70457', '6', '5'), +('70458', '6', '7'), +('70459', '6', '11'), +('70460', '6', '8'), +('70461', '6', '4'), +('70462', '6', '2'), +('70463', '6', '8'), +('70464', '6', '10'), +('70465', '6', '1'), +('70501', '0', '0'), +('70502', '0', '0'), +('70503', '0', '0'), +('70504', '0', '0'), +('70505', '0', '0'), +('70506', '0', '0'), +('70507', '0', '0'), +('70508', '0', '0'), +('70509', '0', '0'), +('70510', '0', '0'), +('70511', '0', '0'), +('70512', '0', '0'), +('70513', '0', '0'), +('70514', '0', '0'), +('70515', '0', '0'), +('70516', '0', '0'), +('70517', '0', '0'), +('70518', '0', '0'), +('70519', '0', '0'), +('70520', '0', '0'), +('70521', '0', '0'), +('70522', '0', '0'), +('70523', '0', '0'), +('70524', '0', '0'), +('70525', '0', '0'), +('70526', '0', '0'), +('70527', '0', '0'), +('70528', '0', '0'), +('70529', '0', '0'), +('70530', '0', '0'), +('70531', '0', '0'), +('70532', '0', '0'), +('70533', '0', '0'), +('70534', '0', '0'), +('70535', '0', '0'), +('70536', '0', '0'), +('70537', '0', '0'), +('70538', '0', '0'), +('70542', '0', '0'), +('70543', '0', '0'), +('70544', '0', '0'), +('70545', '0', '0'), +('70551', '12', '15'), +('70552', '12', '15'), +('70553', '13', '15'), +('70554', '13', '15'), +('70555', '14', '1'), +('70556', '0', '0'), +('70557', '15', '15'), +('70558', '15', '15'), +('70559', '15', '15'), +('70560', '15', '15'), +('70561', '15', '15'), +('70562', '0', '0'), +('70563', '16', '15'), +('70564', '16', '15'), +('70565', '16', '15'), +('70566', '16', '15'), +('70567', '16', '15'), +('70568', '17', '10'), +('70569', '17', '10'), +('70570', '17', '10'), +('70571', '17', '10'), +('70572', '17', '10'), +('70573', '0', '15'), +('70574', '0', '15'); diff --git a/sql/Bots/3_world_bots.sql b/sql/Bots/3_world_bots.sql new file mode 100644 index 000000000..725010f7a --- /dev/null +++ b/sql/Bots/3_world_bots.sql @@ -0,0 +1,547 @@ +-- GENERAL -- + +SET @BOT_START = 70001; +SET @BOT_END = 71000; + +-- move to creature_template_movement InhabitType +-- Resistance +-- basevariance, rangevariance -- spell_school_immune_mask -- damage modifier, expirience modifier + +-- TOTAL BOT ENTRIES: 352 (348) + +delete from `creature_template` where entry between @BOT_START and @BOT_END; + +insert into `creature_template` +(`entry`,`difficulty_entry_1`,`difficulty_entry_2`,`difficulty_entry_3`,`KillCredit1`,`KillCredit2`, +`modelid1`,`modelid2`,`modelid3`,`modelid4`,`name`,`subname`,`IconName`,`gossip_menu_id`,`minlevel`,`maxlevel`,`exp`, +`faction`,`npcflag`,`speed_walk`,`speed_run`,`scale`,`rank`,`dmgschool`,`BaseAttackTime`,`RangeAttackTime`, +`unit_class`,`unit_flags`,`unit_flags2`,`dynamicflags`,`family`,`type`,`type_flags`,`lootid`, +`pickpocketloot`,`skinloot`,`PetSpellDataId`,`VehicleId`,`mingold`,`maxgold`,`AIName`,`MovementType`, +`HoverHeight`,`HealthModifier`,`ManaModifier`,`ArmorModifier`,`RacialLeader`,`movementId`,`RegenHealth`, +`mechanic_immune_mask`,`flags_extra`,`ScriptName`,`VerifiedBuild`) +values +('70001','0','0','0','0','0','3343','0','0','0','Llane','Warrior Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3400','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warrior_bot','-1'), +('70002','0','0','0','0','0','3399','0','0','0','Thran','Warrior Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3400','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warrior_bot','-1'), +('70003','0','0','0','0','0','1300','0','0','0','Lyria','Warrior Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3400','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warrior_bot','-1'), +('70004','0','0','0','0','0','3431','0','0','0','Ander','Warrior Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3400','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warrior_bot','-1'), +('70005','0','0','0','0','0','4556','0','0','0','Malosh','Warrior Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3400','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warrior_bot','-1'), +('70006','0','0','0','0','0','3431','0','0','0','Granis','Warrior Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3400','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warrior_bot','-1'), +('70007','0','0','0','0','0','3053','0','0','0','Kelstrum','Warrior Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3400','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warrior_bot','-1'), +('70008','0','0','0','0','0','1578','0','0','0','Dannal','Warrior Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3400','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warrior_bot','-1'), +('70009','0','0','0','0','0','1599','0','0','0','Austil','Warrior Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3400','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warrior_bot','-1'), +('70010','0','0','0','0','0','2103','0','0','0','Torm','Warrior Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3400','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warrior_bot','-1'), +('70011','0','0','0','0','0','2096','0','0','0','Sark','Warrior Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3400','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warrior_bot','-1'), +('70012','0','0','0','0','0','2113','0','0','0','Ker','Warrior Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3400','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warrior_bot','-1'), +('70013','0','0','0','0','0','3793','0','0','0','Harutt','Warrior Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3400','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warrior_bot','-1'), +('70014','0','0','0','0','0','3794','0','0','0','Krang','Warrior Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3400','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warrior_bot','-1'), +('70015','0','0','0','0','0','1880','0','0','0','Frang','Warrior Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3400','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warrior_bot','-1'), +('70016','0','0','0','0','0','3743','0','0','0','Tarshaw','Warrior Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3400','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warrior_bot','-1'), +('70017','0','0','0','0','0','1374','0','0','0','Grezz','Warrior Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3400','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warrior_bot','-1'), +('70018','0','0','0','0','0','1375','0','0','0','Sorek','Warrior Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3400','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warrior_bot','-1'), +('70019','0','0','0','0','0','4242','0','0','0','Zel\'mak','Warrior Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3400','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warrior_bot','-1'), +('70020','0','0','0','0','0','1721','0','0','0','Alyissia','Warrior Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3400','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warrior_bot','-1'), +('70021','0','0','0','0','0','1707','0','0','0','Kyra','Warrior Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3400','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warrior_bot','-1'), +('70022','0','0','0','0','0','2196','0','0','0','Arias\'ta','Warrior Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3400','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warrior_bot','-1'), +('70023','0','0','0','0','0','2198','0','0','0','Sildanair','Warrior Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3400','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warrior_bot','-1'), +('70024','0','0','0','0','0','2620','0','0','0','Chris','Warrior Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3400','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warrior_bot','-1'), +('70025','0','0','0','0','0','2658','0','0','0','Angela','Warrior Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3400','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warrior_bot','-1'), +('70026','0','0','0','0','0','2614','0','0','0','Baltus','Warrior Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3400','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warrior_bot','-1'), +('70027','0','0','0','0','0','3054','0','0','0','Kelv','Warrior Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3400','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warrior_bot','-1'), +('70028','0','0','0','0','0','3055','0','0','0','Bilban','Warrior Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3400','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warrior_bot','-1'), +('70029','0','0','0','0','0','3280','0','0','0','Wu','Warrior Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3400','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warrior_bot','-1'), +('70030','0','0','0','0','0','3287','0','0','0','Ilsa','Warrior Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3400','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warrior_bot','-1'), +('70031','0','0','0','0','0','6071','0','0','0','Darnath','Warrior Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3400','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warrior_bot','-1'), +('70032','0','0','0','0','0','11037','0','0','0','Evencane','Warrior Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3400','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warrior_bot','-1'), +('70033','0','0','0','0','0','16226','0','0','0','Kore','Warrior Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3400','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warrior_bot','-1'), +('70034','0','0','0','0','0','17212','0','0','0','Ahonan','Warrior Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3400','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warrior_bot','-1'), +('70035','0','0','0','0','0','17213','0','0','0','Behomat','Warrior Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3400','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warrior_bot','-1'), +('70036','0','0','0','0','0','17215','0','0','0','Ruada','Warrior Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3400','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warrior_bot','-1'), +('70037','0','0','0','0','0','17214','0','0','0','Kazi','Warrior Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3400','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warrior_bot','-1'), +('70038','0','0','0','0','0','17211','0','0','0','Kerra','Warrior Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3400','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warrior_bot','-1'), +-- 70039 - 70050 reserved for warriors +('70051','0','0','0','0','0','3346','0','0','0','Sammuel','Paladin Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2300','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','paladin_bot','-1'), +('70052','0','0','0','0','0','3393','0','0','0','Bromos','Paladin Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2300','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','paladin_bot','-1'), +('70053','0','0','0','0','0','1299','0','0','0','Wilhelm','Paladin Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2300','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','paladin_bot','-1'), +('70054','0','0','0','0','0','1499','0','0','0','Grayson','Paladin Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2300','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','paladin_bot','-1'), +('70055','0','0','0','0','0','1622','0','0','0','Azar','Paladin Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2300','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','paladin_bot','-1'), +('70056','0','0','0','0','0','3089','0','0','0','Valgar','Paladin Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2300','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','paladin_bot','-1'), +('70057','0','0','0','0','0','3088','0','0','0','Beldruk','Paladin Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2300','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','paladin_bot','-1'), +('70058','0','0','0','0','0','3087','0','0','0','Brandur','Paladin Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2300','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','paladin_bot','-1'), +('70059','0','0','0','0','0','3284','0','0','0','Arthur','Paladin Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2300','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','paladin_bot','-1'), +('70060','0','0','0','0','0','3289','0','0','0','Katherine','Paladin Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2300','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','paladin_bot','-1'), +('70061','0','0','0','0','0','7356','0','0','0','Karman','Paladin Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2300','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','paladin_bot','-1'), +('70062','0','0','0','0','0','15521','0','0','0','Jesthenis','Paladin Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2300','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','paladin_bot','-1'), +('70063','0','0','0','0','0','16685','0','0','0','Noellene','Paladin Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2300','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','paladin_bot','-1'), +('70064','0','0','0','0','0','16224','0','0','0','Aurelon','Paladin Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2300','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','paladin_bot','-1'), +('70065','0','0','0','0','0','16815','0','0','0','Osselan','Paladin Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2300','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','paladin_bot','-1'), +('70066','0','0','0','0','0','16811','0','0','0','Ithelis','Paladin Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2300','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','paladin_bot','-1'), +('70067','0','0','0','0','0','16829','0','0','0','Bachi','Paladin Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2300','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','paladin_bot','-1'), +('70068','0','0','0','0','0','17225','0','0','0','Baatun','Paladin Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2300','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','paladin_bot','-1'), +('70069','0','0','0','0','0','17227','0','0','0','Kavaan','Paladin Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2300','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','paladin_bot','-1'), +('70070','0','0','0','0','0','17234','0','0','0','Tullas','Paladin Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2300','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','paladin_bot','-1'), +('70071','0','0','0','0','0','17226','0','0','0','Jol','Paladin Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2300','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','paladin_bot','-1'), +('70072','0','0','0','0','0','19596','0','0','0','Cyssa','Paladin Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2300','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','paladin_bot','-1'), +('70073','0','0','0','0','0','21264','0','0','0','Pyreanor','Paladin Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2300','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','paladin_bot','-1'), +('70074','0','0','0','0','0','29735','0','0','0','Rukua','Paladin Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2300','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','paladin_bot','-1'), +-- 70075 - 70100 reserved for paldins +('70101','0','0','0','0','0','3395','0','0','0','Thorgas','Hunter Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2800','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','hunter_bot','-1'), +('70102','0','0','0','0','0','4560','0','0','0','Ogromm','Hunter Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2800','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','hunter_bot','-1'), +('70103','0','0','0','0','0','3558','0','0','0','Grif','Hunter Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2800','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','hunter_bot','-1'), +('70104','0','0','0','0','0','4372','0','0','0','Kragg','Hunter Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2800','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','hunter_bot','-1'), +('70105','0','0','0','0','0','2112','0','0','0','Kary','Hunter Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2800','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','hunter_bot','-1'), +('70106','0','0','0','0','0','2087','0','0','0','Holt','Hunter Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2800','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','hunter_bot','-1'), +('70107','0','0','0','0','0','2105','0','0','0','Urek','Hunter Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2800','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','hunter_bot','-1'), +('70108','0','0','0','0','0','3810','0','0','0','Lanka','Hunter Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2800','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','hunter_bot','-1'), +('70109','0','0','0','0','0','3811','0','0','0','Yaw','Hunter Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2800','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','hunter_bot','-1'), +('70110','0','0','0','0','0','1882','0','0','0','Jen\'shan','Hunter Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2800','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','hunter_bot','-1'), +('70111','0','0','0','0','0','3744','0','0','0','Thotar','Hunter Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2800','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','hunter_bot','-1'), +('70112','0','0','0','0','0','1373','0','0','0','Ormak','Hunter Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2800','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','hunter_bot','-1'), +('70113','0','0','0','0','0','4239','0','0','0','Xor\'juul','Hunter Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2800','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','hunter_bot','-1'), +('70114','0','0','0','0','0','4241','0','0','0','Sian\'dur','Hunter Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2800','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','hunter_bot','-1'), +('70115','0','0','0','0','0','1723','0','0','0','Ayanna','Hunter Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2800','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','hunter_bot','-1'), +('70116','0','0','0','0','0','1703','0','0','0','Dazalar','Hunter Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2800','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','hunter_bot','-1'), +('70117','0','0','0','0','0','2066','0','0','0','Danlaar','Hunter Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2800','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','hunter_bot','-1'), +('70118','0','0','0','0','0','2205','0','0','0','Jeen\'ra','Hunter Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2800','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','hunter_bot','-1'), +('70119','0','0','0','0','0','2206','0','0','0','Jocaste','Hunter Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2800','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','hunter_bot','-1'), +('70120','0','0','0','0','0','2251','0','0','0','Dorion','Hunter Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2800','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','hunter_bot','-1'), +('70121','0','0','0','0','0','3056','0','0','0','Daera','Hunter Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2800','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','hunter_bot','-1'), +('70122','0','0','0','0','0','3072','0','0','0','Olmin','Hunter Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2800','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','hunter_bot','-1'), +('70123','0','0','0','0','0','3073','0','0','0','Regnus','Hunter Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2800','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','hunter_bot','-1'), +('70124','0','0','0','0','0','3299','0','0','0','Kaerbrus','Hunter Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2800','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','hunter_bot','-1'), +('70125','0','0','0','0','0','3312','0','0','0','Einris','Hunter Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2800','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','hunter_bot','-1'), +('70126','0','0','0','0','0','3309','0','0','0','Ulfir','Hunter Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2800','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','hunter_bot','-1'), +('70127','0','0','0','0','0','3310','0','0','0','Thorfin','Hunter Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2800','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','hunter_bot','-1'), +('70128','0','0','0','0','0','7538','0','0','0','Alenndaar','Hunter Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2800','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','hunter_bot','-1'), +('70129','0','0','0','0','0','10245','0','0','0','Dargh','Hunter Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2800','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','hunter_bot','-1'), +('70130','0','0','0','0','0','15520','0','0','0','Sallina','Hunter Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2800','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','hunter_bot','-1'), +('70131','0','0','0','0','0','16681','0','0','0','Hannovia','Hunter Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2800','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','hunter_bot','-1'), +('70132','0','0','0','0','0','16222','0','0','0','Keilnei','Hunter Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2800','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','hunter_bot','-1'), +('70133','0','0','0','0','0','16778','0','0','0','Tana','Hunter Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2800','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','hunter_bot','-1'), +('70134','0','0','0','0','0','16816','0','0','0','Oninath','Hunter Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2800','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','hunter_bot','-1'), +('70135','0','0','0','0','0','16802','0','0','0','Zandine','Hunter Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2800','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','hunter_bot','-1'), +('70136','0','0','0','0','0','17434','0','0','0','Deremiis','Hunter Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2800','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','hunter_bot','-1'), +('70137','0','0','0','0','0','16860','0','0','0','Acteon','Hunter Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2800','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','hunter_bot','-1'), +('70138','0','0','0','0','0','17511','0','0','0','Vord','Hunter Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2800','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','hunter_bot','-1'), +('70139','0','0','0','0','0','17488','0','0','0','Killac','Hunter bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2800','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','hunter_bot','-1'), +-- 70140 - 70150 reserved for hunters +('70151','0','0','0','0','0','3351','0','0','0','Jorik','Rogue Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','1600','2000','4','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','rogue_bot','-1'), +('70152','0','0','0','0','0','3407','0','0','0','Solm','Rogue Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','1600','2000','4','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','rogue_bot','-1'), +('70153','0','0','0','0','0','1297','0','0','0','Keryn','Rogue Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','1600','2000','4','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','rogue_bot','-1'), +('70154','0','0','0','0','0','1507','0','0','0','Osborne','Rogue Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','1600','2000','4','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','rogue_bot','-1'), +('70155','0','0','0','0','0','3436','0','0','0','Hogral','Rogue Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','1600','2000','4','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','rogue_bot','-1'), +('70156','0','0','0','0','0','5146','0','0','0','Ian','Rogue Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','1600','2000','4','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','rogue_bot','-1'), +('70157','0','0','0','0','0','1580','0','0','0','David','Rogue Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','1600','2000','4','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','rogue_bot','-1'), +('70158','0','0','0','0','0','2130','0','0','0','Marion','Rogue Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','1600','2000','4','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','rogue_bot','-1'), +('70159','0','0','0','0','0','1886','0','0','0','Rwag','Rogue Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','1600','2000','4','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','rogue_bot','-1'), +('70160','0','0','0','0','0','3749','0','0','0','Kaplak','Rogue Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','1600','2000','4','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','rogue_bot','-1'), +('70161','0','0','0','0','0','1327','0','0','0','Gest','Rogue Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','1600','2000','4','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','rogue_bot','-1'), +('70162','0','0','0','0','0','1328','0','0','0','Ormok','Rogue Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','1600','2000','4','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','rogue_bot','-1'), +('70163','0','0','0','0','0','4360','0','0','0','Shenthul','Rogue Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','1600','2000','4','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','rogue_bot','-1'), +('70164','0','0','0','0','0','1725','0','0','0','Frahun','Rogue Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','1600','2000','4','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','rogue_bot','-1'), +('70165','0','0','0','0','0','1704','0','0','0','Jannok','Rogue Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','1600','2000','4','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','rogue_bot','-1'), +('70166','0','0','0','0','0','2231','0','0','0','Syurna','Rogue Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','1600','2000','4','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','rogue_bot','-1'), +('70167','0','0','0','0','0','2252','0','0','0','Erion','Rogue Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','1600','2000','4','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','rogue_bot','-1'), +('70168','0','0','0','0','0','2243','0','0','0','Anishar','Rogue Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','1600','2000','4','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','rogue_bot','-1'), +('70169','0','0','0','0','0','2659','0','0','0','Carolyn','Rogue Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','1600','2000','4','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','rogue_bot','-1'), +('70170','0','0','0','0','0','2639','0','0','0','Miles','Rogue Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','1600','2000','4','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','rogue_bot','-1'), +('70171','0','0','0','0','0','2631','0','0','0','Gregory','Rogue Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','1600','2000','4','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','rogue_bot','-1'), +('70172','0','0','0','0','0','3101','0','0','0','Hulfdan','Rogue Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','1600','2000','4','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','rogue_bot','-1'), +('70173','0','0','0','0','0','3100','0','0','0','Ormyr','Rogue Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','1600','2000','4','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','rogue_bot','-1'), +('70174','0','0','0','0','0','3113','0','0','0','Fenthwick','Rogue Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','1600','2000','4','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','rogue_bot','-1'), +('70175','0','0','0','0','0','5528','0','0','0','Fahrad','Rogue Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','1600','2000','4','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','rogue_bot','-1'), +('70176','0','0','0','0','0','13171','0','0','0','Tony','Rogue Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','1600','2000','4','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','rogue_bot','-1'), +('70177','0','0','0','0','0','15519','0','0','0','Kariel','Rogue Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','1600','2000','4','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','rogue_bot','-1'), +('70178','0','0','0','0','0','16689','0','0','0','Tannaria','Rogue Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','1600','2000','4','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','rogue_bot','-1'), +('70179','0','0','0','0','0','16767','0','0','0','Zelanis','Rogue Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','1600','2000','4','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','rogue_bot','-1'), +('70180','0','0','0','0','0','16798','0','0','0','Elara','Rogue Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','1600','2000','4','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','rogue_bot','-1'), +('70181','0','0','0','0','0','16818','0','0','0','Nerisen','Rogue Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','1600','2000','4','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','rogue_bot','-1'), +-- 70182 - 70200 reserved for rogues +('70201','0','0','0','0','0','3344','0','0','0','Anetta','Priest Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3600','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','priest_bot','-1'), +('70202','0','0','0','0','0','1495','0','0','0','Laurena','Priest Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3600','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','priest_bot','-1'), +('70203','0','0','0','0','0','1295','0','0','0','Josetta','Priest Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3600','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','priest_bot','-1'), +('70204','0','0','0','0','0','3401','0','0','0','Branstock','Priest Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3600','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','priest_bot','-1'), +('70205','0','0','0','0','0','3429','0','0','0','Maxan','Priest Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3600','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','priest_bot','-1'), +('70206','0','0','0','0','0','1579','0','0','0','Duesten','Priest Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3600','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','priest_bot','-1'), +('70207','0','0','0','0','0','1602','0','0','0','Beryl','Priest Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3600','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','priest_bot','-1'), +('70208','0','0','0','0','0','2139','0','0','0','Miles','Priest Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3600','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','priest_bot','-1'), +('70209','0','0','0','0','0','2138','0','0','0','Malakai','Priest Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3600','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','priest_bot','-1'), +('70210','0','0','0','0','0','2137','0','0','0','Cobb','Priest Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3600','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','priest_bot','-1'), +('70211','0','0','0','0','0','1733','0','0','0','Shanda','Priest Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3600','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','priest_bot','-1'), +('70212','0','0','0','0','0','1708','0','0','0','Laurna','Priest Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3600','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','priest_bot','-1'), +('70213','0','0','0','0','0','1897','0','0','0','Tai\'jin','Priest Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3600','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','priest_bot','-1'), +('70214','0','0','0','0','0','4068','0','0','0','Ken\'jai','Priest Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3600','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','priest_bot','-1'), +('70215','0','0','0','0','0','2200','0','0','0','Astarii','Priest Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3600','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','priest_bot','-1'), +('70216','0','0','0','0','0','2201','0','0','0','Jandria','Priest Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3600','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','priest_bot','-1'), +('70217','0','0','0','0','0','2202','0','0','0','Lariia','Priest Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3600','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','priest_bot','-1'), +('70218','0','0','0','0','0','2626','0','0','0','Lankester','Priest Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3600','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','priest_bot','-1'), +('70219','0','0','0','0','0','2618','0','0','0','Lazarus','Priest Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3600','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','priest_bot','-1'), +('70220','0','0','0','0','0','3086','0','0','0','Theodrus','Priest Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3600','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','priest_bot','-1'), +('70221','0','0','0','0','0','3066','0','0','0','Braenna','Priest Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3600','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','priest_bot','-1'), +('70222','0','0','0','0','0','3085','0','0','0','Toldren','Priest Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3600','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','priest_bot','-1'), +('70223','0','0','0','0','0','3282','0','0','0','Benjamin','Priest Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3600','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','priest_bot','-1'), +('70224','0','0','0','0','0','3283','0','0','0','Joshua','Priest Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3600','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','priest_bot','-1'), +('70225','0','0','0','0','0','4690','0','0','0','Zayus','Priest Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3600','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','priest_bot','-1'), +('70226','0','0','0','0','0','10473','0','0','0','X\'yera','Priest Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3600','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','priest_bot','-1'), +('70227','0','0','0','0','0','4711','0','0','0','Ur\'kyo','Priest Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3600','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','priest_bot','-1'), +('70228','0','0','0','0','0','11044','0','0','0','Nara','Priest Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3600','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','priest_bot','-1'), +('70229','0','0','0','0','0','11048','0','0','0','Alathea','Priest Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3600','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','priest_bot','-1'), +('70230','0','0','0','0','0','11053','0','0','0','Rohan','Priest Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3600','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','priest_bot','-1'), +('70231','0','0','0','0','0','15518','0','0','0','Arena','Priest Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3600','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','priest_bot','-1'), +('70232','0','0','0','0','0','16707','0','0','0','Ponaris','Priest Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3600','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','priest_bot','-1'), +('70233','0','0','0','0','0','16225','0','0','0','Zalduun','Priest Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3600','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','priest_bot','-1'), +('70234','0','0','0','0','0','16788','0','0','0','Aldrae','Priest Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3600','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','priest_bot','-1'), +('70235','0','0','0','0','0','16817','0','0','0','Lotheolan','Priest Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3600','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','priest_bot','-1'), +('70236','0','0','0','0','0','16795','0','0','0','Belestra','Priest Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3600','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','priest_bot','-1'), +('70237','0','0','0','0','0','17247','0','0','0','Caedmos','Priest Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3600','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','priest_bot','-1'), +('70238','0','0','0','0','0','17232','0','0','0','Guvan','Priest Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3600','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','priest_bot','-1'), +('70239','0','0','0','0','0','17249','0','0','0','Izmir','Priest Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3600','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','priest_bot','-1'), +('70240','0','0','0','0','0','17248','0','0','0','Fallat','Priest Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3600','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','priest_bot','-1'), +-- 70241 - 70250 reserved for priests +('70251','0','0','0','0','0','4552','0','0','0','Haromm','Shaman Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2600','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','shaman_bot','-1'), +('70252','0','0','0','0','0','2123','0','0','0','Siln','Shaman Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2600','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','shaman_bot','-1'), +('70253','0','0','0','0','0','2102','0','0','0','Tigor','Shaman Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2600','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','shaman_bot','-1'), +('70254','0','0','0','0','0','2082','0','0','0','Beram','Shaman Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2600','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','shaman_bot','-1'), +('70255','0','0','0','0','0','10180','0','0','0','Meela','Shaman Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2600','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','shaman_bot','-1'), +('70256','0','0','0','0','0','3816','0','0','0','Narm','Shaman Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2600','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','shaman_bot','-1'), +('70257','0','0','0','0','0','1878','0','0','0','Shikrik','Shaman Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2600','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','shaman_bot','-1'), +('70258','0','0','0','0','0','3746','0','0','0','Swart','Shaman Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2600','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','shaman_bot','-1'), +('70259','0','0','0','0','0','1360','0','0','0','Kardris','Shaman Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2600','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','shaman_bot','-1'), +('70260','0','0','0','0','0','4231','0','0','0','Sian\'tsu','Shaman Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2600','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','shaman_bot','-1'), +('70261','0','0','0','0','0','13341','0','0','0','Sagorne','Shaman Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2600','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','shaman_bot','-1'), +-- ('70262','0','0','0','0','0','17598','0','0','0','Firmanvaar','Shaman Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2600','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','shaman_bot','-1'), +-- ('70263','0','0','0','0','0','17600','0','0','0','Nobundo','Shaman Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2600','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','shaman_bot','-1'), +-- ('70264','0','0','0','0','0','17599','0','0','0','Tuluun','Shaman Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2600','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','shaman_bot','-1'), +('70265','0','0','0','0','0','16914','0','0','0','Sulaa','Shaman Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2600','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','shaman_bot','-1'), +-- ('70266','0','0','0','0','0','17792','0','0','0','Hobahken','Shaman Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2600','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','shaman_bot','-1'), +('70267','0','0','0','0','0','19598','0','0','0','Umbrua','Shaman Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2600','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','shaman_bot','-1'), +('70268','0','0','0','0','0','21265','0','0','0','Javad','Shaman Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2600','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','shaman_bot','-1'), +-- 70269 - 70300 reserved for shamans +('70301','0','0','0','0','0','5001','0','0','0','Khelden','Mage Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3800','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','mage_bot','-1'), +('70302','0','0','0','0','0','1294','0','0','0','Zaldimar','Mage Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3800','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','mage_bot','-1'), +('70303','0','0','0','0','0','1484','0','0','0','Maginor','Mage Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3800','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','mage_bot','-1'), +('70304','0','0','0','0','0','10216','0','0','0','Marryk','Mage Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3800','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','mage_bot','-1'), +('70305','0','0','0','0','0','10215','0','0','0','Magis','Mage Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3800','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','mage_bot','-1'), +('70306','0','0','0','0','0','1592','0','0','0','Isabella','Mage Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3800','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','mage_bot','-1'), +('70307','0','0','0','0','0','1600','0','0','0','Cain','Mage Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3800','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','mage_bot','-1'), +('70308','0','0','0','0','0','2134','0','0','0','Shymm','Mage Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3800','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','mage_bot','-1'), +('70309','0','0','0','0','0','6058','0','0','0','Ursyn','Mage Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3800','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','mage_bot','-1'), +('70310','0','0','0','0','0','2135','0','0','0','Thurston','Mage Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3800','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','mage_bot','-1'), +('70311','0','0','0','0','0','2644','0','0','0','Pierce','Mage Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3800','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','mage_bot','-1'), +('70312','0','0','0','0','0','2657','0','0','0','Anastasia','Mage Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3800','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','mage_bot','-1'), +('70313','0','0','0','0','0','3108','0','0','0','Bink','Mage Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3800','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','mage_bot','-1'), +('70314','0','0','0','0','0','10214','0','0','0','Juli','Mage Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3800','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','mage_bot','-1'), +('70315','0','0','0','0','0','3109','0','0','0','Nittlebur','Mage Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3800','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','mage_bot','-1'), +('70316','0','0','0','0','0','3292','0','0','0','Jennea','Mage Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3800','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','mage_bot','-1'), +('70317','0','0','0','0','0','10171','0','0','0','Un\'Thuwa','Mage Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3800','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','mage_bot','-1'), +('70318','0','0','0','0','0','4524','0','0','0','Pephredo','Mage Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3800','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','mage_bot','-1'), +('70319','0','0','0','0','0','4522','0','0','0','Enyo','Mage Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3800','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','mage_bot','-1'), +('70320','0','0','0','0','0','4526','0','0','0','Mai\'ah','Mage Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3800','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','mage_bot','-1'), +('70321','0','0','0','0','0','4523','0','0','0','Deino','Mage Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3800','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','mage_bot','-1'), +('70322','0','0','0','0','0','6060','0','0','0','Uthel\'nay','Mage Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3800','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','mage_bot','-1'), +('70323','0','0','0','0','0','6072','0','0','0','Dink','Mage Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3800','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','mage_bot','-1'), +('70324','0','0','0','0','0','15522','0','0','0','Julia','Mage Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3800','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','mage_bot','-1'), +('70325','0','0','0','0','0','16680','0','0','0','Garridel','Mage Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3800','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','mage_bot','-1'), +('70326','0','0','0','0','0','16223','0','0','0','Valaatu','Mage Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3800','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','mage_bot','-1'), +('70327','0','0','0','0','0','16781','0','0','0','Zaedana','Mage Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3800','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','mage_bot','-1'), +('70328','0','0','0','0','0','16824','0','0','0','Quithas','Mage Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3800','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','mage_bot','-1'), +('70329','0','0','0','0','0','16809','0','0','0','Inethven','Mage Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3800','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','mage_bot','-1'), +('70330','0','0','0','0','0','16777','0','0','0','Narinth','Mage Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3800','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','mage_bot','-1'), +('70331','0','0','0','0','0','17242','0','0','0','Edirah','Mage Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3800','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','mage_bot','-1'), +('70332','0','0','0','0','0','16856','0','0','0','Valustraa','Mage Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3800','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','mage_bot','-1'), +('70333','0','0','0','0','0','17233','0','0','0','Semid','Mage Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3800','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','mage_bot','-1'), +('70334','0','0','0','0','0','17243','0','0','0','Harnan','Mage Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3800','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','mage_bot','-1'), +('70335','0','0','0','0','0','17241','0','0','0','Bati','Mage Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3800','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','mage_bot','-1'), +('70336','0','0','0','0','0','11466','0','0','0','Derek','Mage Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3800','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','mage_bot','-1'), +-- 70337 - 70350 reserved for mages +('70351','0','0','0','0','0','3345','0','0','0','Drusilla','Warlock Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3500','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warlock_bot','-1'), +('70352','0','0','0','0','0','1930','0','0','0','Alamar','Warlock Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3500','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warlock_bot','-1'), +('70353','0','0','0','0','0','1469','0','0','0','Demisette','Warlock Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3500','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warlock_bot','-1'), +('70354','0','0','0','0','0','3271','0','0','0','Maximillian','Warlock Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3500','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warlock_bot','-1'), +('70355','0','0','0','0','0','4567','0','0','0','Kartosh','Warlock Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3500','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warlock_bot','-1'), +('70356','0','0','0','0','0','1581','0','0','0','Maximillion','Warlock Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3500','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warlock_bot','-1'), +('70357','0','0','0','0','0','1604','0','0','0','Rupert','Warlock Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3500','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warlock_bot','-1'), +('70358','0','0','0','0','0','1884','0','0','0','Nartok','Warlock Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3500','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warlock_bot','-1'), +('70359','0','0','0','0','0','3745','0','0','0','Dhugru','Warlock Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3500','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warlock_bot','-1'), +('70360','0','0','0','0','0','1324','0','0','0','Grol\'dar','Warlock Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3500','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warlock_bot','-1'), +('70361','0','0','0','0','0','1325','0','0','0','Mirket','Warlock Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3500','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warlock_bot','-1'), +('70362','0','0','0','0','0','1326','0','0','0','Zevrost','Warlock Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3500','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warlock_bot','-1'), +('70363','0','0','0','0','0','2675','0','0','0','Kaal','Warlock Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3500','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warlock_bot','-1'), +('70364','0','0','0','0','0','2637','0','0','0','Luther','Warlock Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3500','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warlock_bot','-1'), +('70365','0','0','0','0','0','2646','0','0','0','Richard','Warlock Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3500','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warlock_bot','-1'), +('70366','0','0','0','0','0','3115','0','0','0','Thistleheart','Warlock Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3500','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warlock_bot','-1'), +('70367','0','0','0','0','0','3116','0','0','0','Briarthorn','Warlock Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3500','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warlock_bot','-1'), +('70368','0','0','0','0','0','3122','0','0','0','Alexander','Warlock Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3500','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warlock_bot','-1'), +('70369','0','0','0','0','0','3291','0','0','0','Ursula','Warlock Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3500','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warlock_bot','-1'), +('70370','0','0','0','0','0','3286','0','0','0','Sandahl','Warlock Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3500','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warlock_bot','-1'), +('70371','0','0','0','0','0','3607','0','0','0','Gimrizz','Warlock Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3500','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warlock_bot','-1'), +('70372','0','0','0','0','0','15524','0','0','0','Teli\'Larien','Warlock Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3500','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warlock_bot','-1'), +('70373','0','0','0','0','0','16700','0','0','0','Celoenus','Warlock Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3500','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warlock_bot','-1'), +('70374','0','0','0','0','0','16787','0','0','0','Alamma','Warlock Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3500','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warlock_bot','-1'), +('70375','0','0','0','0','0','16800','0','0','0','Talionia','Warlock Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3500','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warlock_bot','-1'), +('70376','0','0','0','0','0','16831','0','0','0','Zanien','Warlock Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3500','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warlock_bot','-1'), +('70377','0','0','0','0','0','21604','0','0','0','Babagaya','Warlock Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3500','2000','8','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','warlock_bot','-1'), +-- 70378 - 70400 reserved for warlocks +('70401','0','0','0','0','0','2106','0','0','0','Turak','Druid Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2200','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','druid_bot','-1'), +('70402','0','0','0','0','0','2121','0','0','0','Sheal','Druid Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2200','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','druid_bot','-1'), +('70403','0','0','0','0','0','2115','0','0','0','Kym','Druid Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2200','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','druid_bot','-1'), +('70404','0','0','0','0','0','3819','0','0','0','Gart','Druid Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2200','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','druid_bot','-1'), +('70405','0','0','0','0','0','10734','0','0','0','Gennia','Druid Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2200','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','druid_bot','-1'), +('70406','0','0','0','0','0','1732','0','0','0','Mardant','Druid Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2200','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','druid_bot','-1'), +('70407','0','0','0','0','0','1706','0','0','0','Kal','Druid Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2200','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','druid_bot','-1'), +('70408','0','0','0','0','0','2261','0','0','0','Mathrengyl','Druid Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2200','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','druid_bot','-1'), +('70409','0','0','0','0','0','2250','0','0','0','Denatharion','Druid Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2200','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','druid_bot','-1'), +('70410','0','0','0','0','0','2255','0','0','0','Fylerian','Druid Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2200','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','druid_bot','-1'), +('70411','0','0','0','0','0','3300','0','0','0','Sheldras','Druid Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2200','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','druid_bot','-1'), +('70412','0','0','0','0','0','3301','0','0','0','Theridran','Druid Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2200','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','druid_bot','-1'), +('70413','0','0','0','0','0','3302','0','0','0','Maldryn','Druid Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2200','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','druid_bot','-1'), +('70414','0','0','0','0','0','7357','0','0','0','Jannos','Druid Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2200','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','druid_bot','-1'), +('70415','0','0','0','0','0','10738','0','0','0','Golhine','Druid Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2200','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','druid_bot','-1'), +('70416','0','0','0','0','0','12053','0','0','0','Loganaar','Druid Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2200','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','druid_bot','-1'), +('70417','0','0','0','0','0','16739','0','0','0','Harene','Druid Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2200','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','druid_bot','-1'), +('70418','0','0','0','0','0','16858','0','0','0','Shalannius','Druid Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','2200','2000','2','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','druid_bot','-1'), +-- 70419 - 70450 reserved for druids +('70451','0','0','0','0','0','24935','0','0','0','Siouxsie','Death Knight Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3300','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','death_knight_bot','-1'), +('70452','0','0','0','0','0','26939','0','0','0','Imhadria','Death Knight Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3300','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','death_knight_bot','-1'), +('70453','0','0','0','0','0','26854','0','0','0','Vaelen','Death Knight Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3300','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','death_knight_bot','-1'), +('70454','0','0','0','0','0','28039','0','0','0','Mynx','Death Knight Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3300','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','death_knight_bot','-1'), +('70455','0','0','0','0','0','26688','0','0','0','Lankral','Death Knight Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3300','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','death_knight_bot','-1'), +('70456','0','0','0','0','0','26195','0','0','0','Sliver','Death Knight Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3300','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','death_knight_bot','-1'), +('70457','0','0','0','0','0','27402','0','0','0','Vereth','Death Knight Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3300','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','death_knight_bot','-1'), +('70458','0','0','0','0','0','27189','0','0','0','Arly','Death Knight Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3300','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','death_knight_bot','-1'), +('70459','0','0','0','0','0','26217','0','0','0','Setaal','Death Knight Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3300','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','death_knight_bot','-1'), +('70460','0','0','0','0','0','26222','0','0','0','Uzo','Death Knight Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3300','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','death_knight_bot','-1'), +('70461','0','0','0','0','0','28842','0','0','0','Illyrie','Death Knight Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3300','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','death_knight_bot','-1'), +('70462','0','0','0','0','0','28837','0','0','0','Crok','Death Knight Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3300','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','death_knight_bot','-1'), +('70463','0','0','0','0','0','28840','0','0','0','Zor\'be','Death Knight Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3300','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','death_knight_bot','-1'), +('70464','0','0','0','0','0','25512','0','0','0','Datura','Death Knight Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3300','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','death_knight_bot','-1'), +('70465','0','0','0','0','0','25500','0','0','0','Stefan','Death Knight Bot','','0','80','80','2','35','1','1.1','1.1','1','0','0','3300','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','death_knight_bot','-1'), +-- 70466 - 70500 reserved for deathknights +('70501','0','0','0','0','0','4449','0','0','0','Imp',NULL,'','0','80','80','2','35','0','1.2','1.3','1','0','0','2000','2000','8','0','0','0','0','3','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','1','135266416','warlock_pet_bot','-1'), +('70502','0','0','0','0','0','1132','0','0','0','Voidwalker',NULL,'','0','80','80','2','35','0','1.2','1.3','1','0','0','2000','2000','8','0','0','0','0','3','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','1','135266416','warlock_pet_bot','-1'), +('70503','0','0','0','0','0','4162','0','0','0','Succubus',NULL,'','0','80','80','2','35','0','1.2','1.3','1','0','0','2000','2000','8','0','0','0','0','3','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','1','135266416','warlock_pet_bot','-1'), +('70504','0','0','0','0','0','850','0','0','0','Felhunter',NULL,'','0','80','80','2','35','0','1.2','1.3','1','0','0','2000','2000','8','0','0','0','0','3','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','1','135266416','warlock_pet_bot','-1'), +('70505','0','0','0','0','0','14255','0','0','0','Felguard',NULL,'','0','80','80','2','35','0','1.2','1.3','1','0','0','2000','2000','8','0','0','0','0','3','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','1','135266416','warlock_pet_bot','-1'), +('70506','0','0','0','0','0','368','0','0','0','Spider',NULL,'','0','80','80','2','35','0','1.2','1.3','1','0','0','2000','2000','1','0','0','0','0','1','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','135266416','hunter_pet_bot','-1'), +('70507','0','0','0','0','0','4312','0','0','0','Serpent',NULL,'','0','80','80','2','35','0','1.2','1.3','1','0','0','2000','2000','1','0','0','0','0','1','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','135266416','hunter_pet_bot','-1'), +('70508','0','0','0','0','0','16724','0','0','0','Bird of Prey',NULL,'','0','80','80','2','35','0','1.2','1.3','1','0','0','2000','2000','1','0','0','0','0','1','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','135266416','hunter_pet_bot','-1'), +('70509','0','0','0','0','0','8808','0','0','0','Bat',NULL,'','0','80','80','2','35','0','1.2','1.3','1','0','0','2000','2000','1','0','0','0','0','1','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','135266416','hunter_pet_bot','-1'), +('70510','0','0','0','0','0','17562','0','0','0','Wind Serpent',NULL,'','0','80','80','2','35','0','1.2','1.3','1','0','0','2000','2000','1','0','0','0','0','1','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','135266416','hunter_pet_bot','-1'), +('70511','0','0','0','0','0','20308','0','0','0','Ravager',NULL,'','0','80','80','2','35','0','1.2','1.3','1','0','0','2000','2000','1','0','0','0','0','1','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','135266416','hunter_pet_bot','-1'), +('70512','0','0','0','0','0','17545','0','0','0','Dragonhawk',NULL,'','0','80','80','2','35','0','1.2','1.3','1','0','0','2000','2000','1','0','0','0','0','1','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','135266416','hunter_pet_bot','-1'), +('70513','0','0','0','0','0','19405','0','0','0','Nether Ray',NULL,'','0','80','80','2','35','0','1.2','1.3','1','0','0','2000','2000','1','0','0','0','0','1','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','135266416','hunter_pet_bot','-1'), +('70514','0','0','0','0','0','17753','0','0','0','Sporebat',NULL,'','0','80','80','2','35','0','1.2','1.3','1','0','0','2000','2000','1','0','0','0','0','1','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','135266416','hunter_pet_bot','-1'), +('70515','0','0','0','0','0','15590','0','0','0','Carrion Bird',NULL,'','0','80','80','2','35','0','1.2','1.3','1','0','0','2000','2000','1','0','0','0','0','1','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','135266416','hunter_pet_bot','-1'), +('70516','0','0','0','0','0','11319','0','0','0','Raptor',NULL,'','0','80','80','2','35','0','1.2','1.3','1','0','0','2000','2000','1','0','0','0','0','1','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','135266416','hunter_pet_bot','-1'), +('70517','0','0','0','0','0','741','0','0','0','Wolf',NULL,'','0','80','80','2','35','0','1.2','1.3','1','0','0','2000','2000','1','0','0','0','0','1','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','135266416','hunter_pet_bot','-1'), +('70518','0','0','0','0','0','1220','0','0','0','Tallstrider',NULL,'','0','80','80','2','35','0','1.2','1.3','1','0','0','2000','2000','1','0','0','0','0','1','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','135266416','hunter_pet_bot','-1'), +('70519','0','0','0','0','0','321','0','0','0','Cat',NULL,'','0','80','80','2','35','0','1.2','1.3','1','0','0','2000','2000','1','0','0','0','0','1','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','135266416','hunter_pet_bot','-1'), +('70520','0','0','0','0','0','2714','0','0','0','Hyena',NULL,'','0','80','80','2','35','0','1.2','1.3','1','0','0','2000','2000','1','0','0','0','0','1','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','135266416','hunter_pet_bot','-1'), +('70521','0','0','0','0','0','29113','0','0','0','Wasp',NULL,'','0','80','80','2','35','0','1.2','1.3','1','0','0','2000','2000','1','0','0','0','0','1','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','135266416','hunter_pet_bot','-1'), +('70522','0','0','0','0','0','17798','0','0','0','Teromoth',NULL,'','0','80','80','2','35','0','1.2','1.3','1','0','0','2000','2000','1','0','0','0','0','1','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','135266416','hunter_pet_bot','-1'), +('70523','0','0','0','0','0','20790','0','0','0','Scorpid',NULL,'','0','80','80','2','35','0','1.2','1.3','1','0','0','2000','2000','1','0','0','0','0','1','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','135266416','hunter_pet_bot','-1'), +('70524','0','0','0','0','0','1244','0','0','0','Turtle',NULL,'','0','80','80','2','35','0','1.2','1.3','1','0','0','2000','2000','1','0','0','0','0','1','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','135266416','hunter_pet_bot','-1'), +('70525','0','0','0','0','0','809','0','0','0','Gorilla',NULL,'','0','80','80','2','35','0','1.2','1.3','1','0','0','2000','2000','1','0','0','0','0','1','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','135266416','hunter_pet_bot','-1'), +('70526','0','0','0','0','0','706','0','0','0','Bear',NULL,'','0','80','80','2','35','0','1.2','1.3','1','0','0','2000','2000','1','0','0','0','0','1','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','135266416','hunter_pet_bot','-1'), +('70527','0','0','0','0','0','381','0','0','0','Boar',NULL,'','0','80','80','2','35','0','1.2','1.3','1','0','0','2000','2000','1','0','0','0','0','1','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','135266416','hunter_pet_bot','-1'), +('70528','0','0','0','0','0','1938','0','0','0','Crab',NULL,'','0','80','80','2','35','0','1.2','1.3','1','0','0','2000','2000','1','0','0','0','0','1','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','135266416','hunter_pet_bot','-1'), +('70529','0','0','0','0','0','1250','0','0','0','Crocolisk',NULL,'','0','80','80','2','35','0','1.2','1.3','1','0','0','2000','2000','1','0','0','0','0','1','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','135266416','hunter_pet_bot','-1'), +('70530','0','0','0','0','0','19998','0','0','0','Warp Stalker',NULL,'','0','80','80','2','35','0','1.2','1.3','1','0','0','2000','2000','1','0','0','0','0','1','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','135266416','hunter_pet_bot','-1'), +('70531','0','0','0','0','0','11087','0','0','0','Silithid',NULL,'','0','80','80','2','35','0','1.2','1.3','1','0','0','2000','2000','1','0','0','0','0','1','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','135266416','hunter_pet_bot','-1'), +('70532','0','0','0','0','0','10810','0','0','0','Chimaera',NULL,'','0','80','80','2','35','0','1.2','1.3','1','0','0','2000','2000','1','0','0','0','0','1','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','135266416','hunter_pet_bot','-1'), +('70533','0','0','0','0','0','29673','0','0','0','Spirit Beast',NULL,'','0','80','80','2','35','0','1.2','1.3','1','0','0','2000','2000','1','0','0','0','0','1','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','135266416','hunter_pet_bot','-1'), +('70534','0','0','0','0','0','12168','0','0','0','Core Hound',NULL,'','0','80','80','2','35','0','1.2','1.3','1','0','0','2000','2000','1','0','0','0','0','1','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','135266416','hunter_pet_bot','-1'), +('70535','0','0','0','0','0','5239','0','0','0','Devilsaur',NULL,'','0','80','80','2','35','0','1.2','1.3','1','0','0','2000','2000','1','0','0','0','0','1','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','135266416','hunter_pet_bot','-1'), +('70536','0','0','0','0','0','26279','0','0','0','Rhino',NULL,'','0','80','80','2','35','0','1.2','1.3','1','0','0','2000','2000','1','0','0','0','0','1','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','135266416','hunter_pet_bot','-1'), +('70537','0','0','0','0','0','13212','0','0','0','Worm',NULL,'','0','80','80','2','35','0','1.2','1.3','1','0','0','2000','2000','1','0','0','0','0','1','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','135266416','hunter_pet_bot','-1'), +('70538','0','0','0','0','0','24994','24993','24992','24995','Risen Ghoul',NULL,'','0','80','80','2','35','0','1.2','1.3','1','0','0','2000','2000','8','0','0','0','0','6','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','135266416','deathknight_pet_bot','-1'), +-- 70539 - 70541 reserved for dk pets +('70542','0','0','0','0','0','19110','0','0','0','Shadowfiend',NULL,'','0','80','80','2','35','0','1.2','1.3','1','0','5','1500','1500','2','0','0','0','0','10','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','135266416','priest_pet_bot','-1'), +('70543','0','0','0','0','0','21114','0','0','0','Spirit Wolf',NULL,'','0','80','80','2','35','0','1.2','1.3','1','0','0','1500','1500','1','0','0','0','0','1','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','135266416','shaman_pet_bot','-1'), +('70544','0','0','0','0','0','525','0','0','0','Water Elemental',NULL,'','0','80','80','2','35','0','1.2','1.3','1','0','0','2000','2000','8','0','0','0','0','4','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','135266416','mage_pet_bot','-1'), +('70545','0','0','0','0','0','18922','0','0','0','Treant',NULL,'','0','80','80','2','35','0','1.2','1.3','1','0','0','2000','2000','1','0','0','0','0','4','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','135266416','druid_pet_bot','-1'), +-- 70546 - 70550 reserved for pets +('70551','0','0','0','0','0','17659','0','0','0','Gorkramato','Blademaster Bot','','0','81','81','2','35','1','1.1','1.1','1','4','0','2200','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157560','blademaster_bot','-1'), +('70552','0','0','0','0','0','17659','0','0','0','Mirror Image (Blademaster)','Blademaster bot','','0','81','81','2','35','1','1.1','1.1','1','4','0','2200','2000','1','0','16432','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157560','blademaster_bot','-1'), +('70553','0','0','0','0','0','15343','0','0','0','Osis','Obsidian Destroyer Bot','','0','83','83','2','35','1','1.1','1.1','0.7','2','0','1200','1200','8','0','0','0','0','10','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','0','sphynx_bot','-1'), +('70554','0','0','0','0','0','15343','0','0','0','Amot','Obsidian Destroyer Bot','','0','83','83','2','35','1','1.1','1.1','0.7','2','0','1200','1200','8','0','0','0','0','10','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','0','sphynx_bot','-1'), +('70555','0','0','0','0','0','6769','0','0','0','Detrae','Archmage Bot','','0','81','81','2','35','1','1.1','1.1','1','4','0','1420','1420','8','0','16432','0','0','7','2048','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','archmage_bot','-1'), +('70556','0','0','0','0','0','5561','0','0','0','Water Elemental',NULL,'','0','81','81','2','35','0','1.2','1.3','1','0','0','1500','1500','1','0','0','0','0','4','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','135266416','archmage_pet_bot','-1'), +('70557','0','0','0','0','0','348','0','0','0','Neroth','Dreadlord Bot','','0','83','83','2','35','1','1.1','1.1','1.5','2','0','1300','1300','2','0','0','0','0','3','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','0','dreadlord_bot','-1'), +('70558','0','0','0','0','0','348','0','0','0','Fearoth','Dreadlord Bot','','0','83','83','2','35','1','1.1','1.1','1.5','2','0','1300','1300','2','0','0','0','0','3','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','0','dreadlord_bot','-1'), +('70559','0','0','0','0','0','348','0','0','0','Zalamon','Dreadlord Bot','','0','83','83','2','35','1','1.1','1.1','1.5','2','0','1300','1300','2','0','0','0','0','3','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','0','dreadlord_bot','-1'), +('70560','0','0','0','0','0','348','0','0','0','Lotthicus','Dreadlord Bot','','0','83','83','2','35','1','1.1','1.1','1.5','2','0','1300','1300','2','0','0','0','0','3','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','0','dreadlord_bot','-1'), +('70561','0','0','0','0','0','348','0','0','0','Ramarot','Dreadlord Bot','','0','83','83','2','35','1','1.1','1.1','1.5','2','0','1300','1300','2','0','0','0','0','3','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','0','dreadlord_bot','-1'), +('70562','0','0','0','0','0','169','0','0','0','Infernal',NULL,'','0','83','83','2','35','0','1.2','1.3','1.5','0','2','1350','1350','1','0','0','0','0','3','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','135266416','dreadlord_pet_bot','-1'), +('70563','0','0','0','0','0','15511','0','0','0','Eanor','Spell Breaker Bot','','0','81','81','2','35','1','1.1','1.1','1.5','4','0','1900','1900','2','0','0','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','0','spellbreaker_bot','-1'), +('70564','0','0','0','0','0','15511','0','0','0','Narsen','Spell Breaker Bot','','0','81','81','2','35','1','1.1','1.1','1.5','4','0','1900','1900','2','0','0','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','0','spellbreaker_bot','-1'), +('70565','0','0','0','0','0','15511','0','0','0','Caelnor','Spell Breaker Bot','','0','81','81','2','35','1','1.1','1.1','1.5','4','0','1900','1900','2','0','0','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','0','spellbreaker_bot','-1'), +('70566','0','0','0','0','0','15511','0','0','0','Daenste','Spell Breaker Bot','','0','81','81','2','35','1','1.1','1.1','1.5','4','0','1900','1900','2','0','0','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','0','spellbreaker_bot','-1'), +('70567','0','0','0','0','0','15511','0','0','0','Neshdar','Spell Breaker Bot','','0','81','81','2','35','1','1.1','1.1','1.5','4','0','1900','1900','2','0','0','0','0','7','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','0','spellbreaker_bot','-1'), +('70568','0','0','0','0','0','30072','0','0','0','Mara','Dark Ranger Bot','','0','83','83','2','35','1','1.1','1.1','1','2','0','2000','2000','8','0','0','0','0','6','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','0','dark_ranger_bot','-1'), +('70569','0','0','0','0','0','30072','0','0','0','Tani','Dark Ranger Bot','','0','83','83','2','35','1','1.1','1.1','1','2','0','2000','2000','8','0','0','0','0','6','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','0','dark_ranger_bot','-1'), +('70570','0','0','0','0','0','30072','0','0','0','Eva','Dark Ranger Bot','','0','83','83','2','35','1','1.1','1.1','1','2','0','2000','2000','8','0','0','0','0','6','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','0','dark_ranger_bot','-1'), +('70571','0','0','0','0','0','30072','0','0','0','Darise','Dark Ranger Bot','','0','83','83','2','35','1','1.1','1.1','1','2','0','2000','2000','8','0','0','0','0','6','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','0','dark_ranger_bot','-1'), +('70572','0','0','0','0','0','30072','0','0','0','Lyra','Dark Ranger Bot','','0','83','83','2','35','1','1.1','1.1','1','2','0','2000','2000','8','0','0','0','0','6','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','0','dark_ranger_bot','-1'), +('70573','0','0','0','0','0','30363','0','0','0','Dark Minion',NULL,'','0','83','83','2','35','0','1.1','1.1','1','0','0','2000','2000','1','0','0','0','0','6','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','0','dark_ranger_pet_bot','-1'), +('70574','0','0','0','0','0','30363','0','0','0','Dark Minion',NULL,'','0','83','83','2','35','0','1.1','1.1','1','1','0','2000','2000','1','0','0','0','0','6','0','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','0','dark_ranger_pet_bot','-1'); +-- 70575 - 71000 reserved for custom stuff + +-- GOSSIPS -- +delete from `npc_text` where ID between @BOT_START and @BOT_END; +insert into `npc_text` (`ID`,`text0_0`,`VerifiedBuild`) +values +('70001','I live only to serve the master.','-1'), +('70002','You need something?','-1'), +('70003','Mortals... usually I kill wretches like you at sight.','-1'), +('70004','','-1'), +('70005','','-1'), +('70006','Are you surprised, mortal? As a lesser nathrezim I have to resort to seeking out allies. You look like you could amuse me at least.','-1'), +('70007','What is it now, mortal?','-1'), +('70008','Can you just leave me be? ','-1'), +('70009','Now what?','-1'), +-- 70010 - 70100 reserved for standard/greet gossips +('70101','|cffff3300Blademaster|r$b|cffdd6600-=Warcraft III tribute=-|r$B$B\"An elite swordsman, former member of Burning Blade clan, now an elite fighter within the Horde\".$B$BMain attribute: Agility.$B$BNetherwalk (Windwalk). Allows Blademaster to become invisible, and move faster for a set amount of time. When the Blademaster attacks a unit to break invisibility, he will deal bonus damage.$B$BMirror Image. Confuses the enemy by creating illusions of the Blademaster and dispelling all magic from the Blademaster.$B$BCritical Strike (passive). Gives a 15% chance to deal critical x2(x3,x4) times normal damage on his attacks.$B$BBladestorm (NIY). Grants immunity to magic and deals damage to all surrounding enemies.$B$B','-1'), +('70102','|cff9900ccObsidian Destroyer|r$b|cffdd6600-=Warcraft III tribute=-|r$B$B\"An obsidian winged monstrocity with insatiable hunger for magic\".$B$BHigh armor, very high resistances, partially immune to magic, loses mana over time and doesn\'t benefit from passive mana regeneration effects, mail/plate armor, dual-wielding wands, deals spellshadow damage, no physical attack, cannot attack enemies not in front while moving, spell power bonus: 50% attack power + 200% intellect + wands damage.$B$BDevour Magic. Dispels up to 2 magic effects from enemies, up to 2 magic effects and up to 2 curses from allies and damaging summoned units in 20 yards area. Every dispelled effect restores 20% mana and 5% health, 7 seconds cooldown.$B$BShadow Blast. Empowered attack that deals increased splash damage.$B$BDrain Mana. Drains all mana (limited by caster\'s mana pool) from a random friendly unit.$B$BReplenish Mana. Energizes surrounding party and raid members within 25 yards for 2% of their maximum mana nullifying caster\'s mana, affects up to 10 targets, 3 seconds cooldown.$B$BRegenerating Aura. Heals surrounding party and raid members within 25 yards for 3% of their maximum health nullifying caster\'s mana, affects up to 10 targets, 3 seconds cooldown.$B$BShadow Armor (passive). Restores mana equal to a percentage of damage taken.$B$B','-1'), +('70103','|cff0000ddArchmage|r$b|cffdd6600-=Warcraft III tribute=-|r$B$BSpell damage taken reduced by 35%, partially immune to control effects, cloth armor, deals spellsfire/spellfrost damage, no physical attack, spell power bonus: 100% intellect. Main attribute: Intellect.$B$BBlizzard. Your typical blizzard, just a little more powerful, 6 seconds cooldown.$B$BSummon Water Elemental. Summons a water elemental to attack archmage\'s enemies for 1 min, 20 seconds cooldown.$B$BBrilliance Aura. Increases maximum mana by 10% and greatly increases mana regeneration of party and raid members within 40 yards.$B$BMass Teleport. NIY.','-1'), +('70104','|cff9900ccDreadlord|r$b|cffdd6600-=Warcraft III tribute=-|r$B$B\"Incredibly powerful demon who wields power of darkness and mental domination\".$B$BHigh armor, high resistances, partially immune to control effects, damage taken speeds up spells recharge, plate armor, deals melee/spellshadow damage, bonus damage against incapacitated targets, spell power bonus: 200% strength. Main attribute: Strength.$B$BCarrion Swarm. Sends a horde of bats combined with chaotic magic to damage enemies in a very large frontal cone, cannot crit, 10 seconds cooldown.$B$BSleep. Puts the enemy target to sleep for 60 seconds and allows next physical attack on that target to bypass armor, direct damage caused will awaken the target, 6 seconds cooldown.$B$BVampiric Aura. Increases physical critical damage by 5% and heals party and raid members within 40 yards for a percentage (100% for Dreadlord and 25% for everyone else) of damage done by melee physical attacks and Carrion Swarm, no threat.$B$BSummon Infernal Servant. Calls an infernal down from the sky dealing damage and stunning enemy units, infernal is very resistant to magic and lasts 180 seconds, 180 seconds cooldown.','-1'), +('70105','|cff0000ddSpell Breaker|r$b|cffdd6600-=Warcraft III tribute=-|r$B$B\"An elven warrior trained to disrupt and contort magical energies\".$B$BSpell damage taken reduced by 75%, partially immune to control effects, armor penalty -30%, mail/plate armor, deals melee/arcane damage, spell power bonus: 200% strength. Main attribute: Strength.$B$BSteal Magic (Spellsteal). Steals a benefical spell from an enemy and applies it to a nearby ally or removes a negative spell from an ally and applies it to a nearby enemy, affects magic and curse effects, 3 seconds cooldown.$B$BFeedback (passive). Successful melee attacks burn target\'s mana equal to damage caused (increased by spellpower) dealing arcane damage. If target is drained, Spell Breaker\'s melee attacks will do triple damage with increased critical strike chance','-1'), +('70106','|cff9900ccDark Ranger|r$b|cffdd6600-=Warcraft III tribute=-|r$B$B\"A former ranger of Quel\'thalas forcibly raised from the dead\".$B$BSpell damage taken reduced by 35%, undead, partially immune to control effects, leather/cloth armor, deals physical/spellshadow damage, stick to shadows and generates no threat, spell power bonus: 50% intellect. Main attribute: Agility.$B$BSilence. Silences an enemy and up to 4 nearby targets for 8 seconds, 15 seconds cooldown.$B$BBlack Arrow. Fires a cursed arrow dealing 150% weapon damage and additional spellshadow damage over time. If affected target dies from Dark Ranger\'s damage, Dark Minion will spawn from the corpse (maximum 5 Minions, 80 seconds duration, only works on humanoids, beasts and dragonkin). Deals five times more damage if target is under 20% health.$B$BDrain Life. Drains health from an enemy every second for 5 seconds, healing Dark Ranger for 200% of the drained amount.','-1'); +-- 70107 - 70200 reserved for custom class descriptions + +-- OUTFITS -- +-- Npc Dress mod by Rochet2 +CREATE TABLE IF NOT EXISTS `creature_template_outfits` ( + `entry` INT(10) UNSIGNED NOT NULL, + `race` tinyint(3) UNSIGNED NOT NULL DEFAULT '1', + `gender` tinyint(3) UNSIGNED NOT NULL DEFAULT '0' COMMENT '0 for male, 1 for female', + `skin` tinyint(3) UNSIGNED NOT NULL DEFAULT '0', + `face` tinyint(3) UNSIGNED NOT NULL DEFAULT '0', + `hair` tinyint(3) UNSIGNED NOT NULL DEFAULT '0', + `haircolor` tinyint(3) UNSIGNED NOT NULL DEFAULT '0', + `facialhair` tinyint(3) UNSIGNED NOT NULL DEFAULT '0', + `head` INT(10) UNSIGNED NOT NULL DEFAULT '0', + `shoulders` INT(10) UNSIGNED NOT NULL DEFAULT '0', + `body` INT(10) UNSIGNED NOT NULL DEFAULT '0', + `chest` INT(10) UNSIGNED NOT NULL DEFAULT '0', + `waist` INT(10) UNSIGNED NOT NULL DEFAULT '0', + `legs` INT(10) UNSIGNED NOT NULL DEFAULT '0', + `feet` INT(10) UNSIGNED NOT NULL DEFAULT '0', + `wrists` INT(10) UNSIGNED NOT NULL DEFAULT '0', + `hands` INT(10) UNSIGNED NOT NULL DEFAULT '0', + `back` INT(10) UNSIGNED NOT NULL DEFAULT '0', + `tabard` INT(10) UNSIGNED NOT NULL DEFAULT '0', + PRIMARY KEY (`entry`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +-- End Npc Dress mod + +replace into `creature_template_outfits` (`entry`, `race`, `gender`, `skin`, `face`, `hair`, `haircolor`, `facialhair`, `head`, `shoulders`, `body`, `chest`, `waist`, `legs`, `feet`, `wrists`, `hands`, `back`, `tabard`) +values +('70551','2','0','0','14','9','7','5','0','0','0','0','59194','64674','0','36248','0','0','0'), -- Blademaster +('70552','2','0','0','14','9','7','5','0','0','0','0','59194','64674','0','36248','0','0','0'); -- Mirror Image (Blademaster) + +-- Customize section + +SET @CLASS_WARRIOR = 1; +SET @CLASS_PALADIN = 2; +SET @CLASS_HUNTER = 3; +SET @CLASS_ROGUE = 4; +SET @CLASS_PRIEST = 5; +SET @CLASS_DK = 6; +SET @CLASS_SHAMAN = 7; +SET @CLASS_MAGE = 8; +SET @CLASS_WARLOCK = 9; +SET @CLASS_DRUID = 11; +SET @CLASS_BM = 12; +SET @CLASS_SPHYNX = 13; +SET @CLASS_ARCHMAGE = 14; +SET @CLASS_DREADLORD = 15; +SET @CLASS_SPELLBREAKER = 16; +SET @CLASS_DARK_RANGER = 17; + +-- Add flags_extra + +SET @EX_NO_PARRY_HASTEN = 8; -- 0x00000008 - CREATURE_FLAG_EXTRA_NO_PARRY_HASTEN +SET @EX_NO_BLOCK = 16; -- 0x00000010 - CREATURE_FLAG_EXTRA_NO_BLOCK +SET @EX_NO_CRUSH = 32; -- 0x00000020 - CREATURE_FLAG_EXTRA_NO_CRUSH +SET @EX_NO_XP = 64; -- 0x00000040 - CREATURE_FLAG_EXTRA_NO_XP_AT_KILL +SET @EX_DIMINISH = 1048576; -- 0x00100000 - CREATURE_FLAG_EXTRA_ALL_DIMINISH +SET @EX_NPCBOT = 67108864; -- 0x04000000 - CREATURE_FLAG_EXTRA_NPCBOT - custom flag +SET @EX_NPCBOT_PET = 134217728; -- 0x08000000 - CREATURE_FLAG_EXTRA_NPCBOT_PET - custom flag +SET @FLAGS_EX = @EX_NO_BLOCK | @EX_NO_CRUSH | @EX_NO_XP | @EX_DIMINISH | @EX_NPCBOT; +SET @FLAGS_EXN = @EX_NO_BLOCK | @EX_NO_CRUSH | @EX_NO_XP | @EX_DIMINISH | @EX_NPCBOT | @EX_NO_PARRY_HASTEN; +SET @FLAGS_EX_PET = @EX_NO_BLOCK | @EX_NO_CRUSH | @EX_NO_XP | @EX_DIMINISH | @EX_NPCBOT_PET; +SET @FLAGS_EX_PET_BLOCK = @EX_NO_CRUSH | @EX_NO_XP | @EX_DIMINISH | @EX_NPCBOT_PET; + +-- Add unit_flags +-- SET @U_PVP_ATTACKABLE = 8; -- 0x00000008 - UNIT_FLAG_PVP_ATTACKABLE +SET @FLAGS_U = 0; +SET @FLAGS_U_PET = 0; + +-- Add unit_flags2 +SET @U2_MIRROR_IMAGE = 16; -- 0x00000010 - UNIT_FLAG2_MIRROR_IMAGE - 22.06.19 +SET @U2_INSTANT_APPEAR_MODEL= 32; -- 0x00000020 - UNIT_FLAG2_INSTANTLY_APPEAR_MODEL - 29.05.19 +SET @U2_ENEMY_INTERRACT = 16384; -- 0x00004000 - UNIT_FLAG2_ALLOW_ENEMY_INTERACT +-- SET @U2_DISABLE_TURN = 32768; -- 0x00008000 - UNIT_FLAG2_DISABLE_TURN +SET @FLAGS_U2 = @U2_MIRROR_IMAGE | @U2_INSTANT_APPEAR_MODEL | @U2_ENEMY_INTERRACT; +SET @FLAGS_U2_NM = @U2_INSTANT_APPEAR_MODEL | @U2_ENEMY_INTERRACT; +SET @FLAGS_U2_PET = 0; + +-- Add type_flags +SET @CT_CAN_ASSIST = 4096; -- 0x00001000 - CREATURE_TYPE_FLAG_CAN_ASSIST - 26.10.19 +SET @FLAGS_CT = @CT_CAN_ASSIST; + +-- general +UPDATE `creature_template` SET type_flags:=`type_flags`|@FLAGS_CT WHERE `entry` BETWEEN @BOT_START AND @BOT_END; + +-- minions +UPDATE `creature_template` SET exp:=2, faction:=35, minlevel:=80, maxlevel:=80, BaseAttackTime:=3300, RangeAttackTime:=2000, dynamicflags:=0, speed_walk:=1.1, speed_run:=1.1, scale:=1, HealthModifier:=1, ManaModifier:=1, ArmorModifier:=1, RegenHealth:=0, mechanic_immune_mask:=0, flags_extra:=@FLAGS_EX, unit_flags:=`unit_flags`|@FLAGS_U, unit_flags2:=`unit_flags2`|@FLAGS_U2, AIName:='' +WHERE `entry` IN (SELECT `entry` FROM `creature_template_npcbot_extras` WHERE `class`=@CLASS_DK); +UPDATE `creature_template` SET exp:=2, faction:=35, minlevel:=80, maxlevel:=80, BaseAttackTime:=2200, RangeAttackTime:=2000, dynamicflags:=0, speed_walk:=1.1, speed_run:=1.1, scale:=1, HealthModifier:=1, ManaModifier:=1, ArmorModifier:=1, RegenHealth:=0, mechanic_immune_mask:=0, flags_extra:=@FLAGS_EX, unit_flags:=`unit_flags`|@FLAGS_U, unit_flags2:=`unit_flags2`|@FLAGS_U2, AIName:='' +WHERE `entry` IN (SELECT `entry` FROM `creature_template_npcbot_extras` WHERE `class`=@CLASS_DRUID); +UPDATE `creature_template` SET exp:=2, faction:=35, minlevel:=80, maxlevel:=80, BaseAttackTime:=2800, RangeAttackTime:=2000, dynamicflags:=0, speed_walk:=1.1, speed_run:=1.1, scale:=1, HealthModifier:=1, ManaModifier:=1, ArmorModifier:=1, RegenHealth:=0, mechanic_immune_mask:=0, flags_extra:=@FLAGS_EX, unit_flags:=`unit_flags`|@FLAGS_U, unit_flags2:=`unit_flags2`|@FLAGS_U2, AIName:='' +WHERE `entry` IN (SELECT `entry` FROM `creature_template_npcbot_extras` WHERE `class`=@CLASS_HUNTER); +UPDATE `creature_template` SET exp:=2, faction:=35, minlevel:=80, maxlevel:=80, BaseAttackTime:=3800, RangeAttackTime:=2000, dynamicflags:=0, speed_walk:=1.1, speed_run:=1.1, scale:=1, HealthModifier:=1, ManaModifier:=1, ArmorModifier:=1, RegenHealth:=0, mechanic_immune_mask:=0, flags_extra:=@FLAGS_EX, unit_flags:=`unit_flags`|@FLAGS_U, unit_flags2:=`unit_flags2`|@FLAGS_U2, AIName:='' +WHERE `entry` IN (SELECT `entry` FROM `creature_template_npcbot_extras` WHERE `class`=@CLASS_MAGE); +UPDATE `creature_template` SET exp:=2, faction:=35, minlevel:=80, maxlevel:=80, BaseAttackTime:=2300, RangeAttackTime:=2000, dynamicflags:=0, speed_walk:=1.1, speed_run:=1.1, scale:=1, HealthModifier:=1, ManaModifier:=1, ArmorModifier:=1, RegenHealth:=0, mechanic_immune_mask:=0, flags_extra:=@FLAGS_EX, unit_flags:=`unit_flags`|@FLAGS_U, unit_flags2:=`unit_flags2`|@FLAGS_U2, AIName:='' +WHERE `entry` IN (SELECT `entry` FROM `creature_template_npcbot_extras` WHERE `class`=@CLASS_PALADIN); +UPDATE `creature_template` SET exp:=2, faction:=35, minlevel:=80, maxlevel:=80, BaseAttackTime:=3600, RangeAttackTime:=2000, dynamicflags:=0, speed_walk:=1.1, speed_run:=1.1, scale:=1, HealthModifier:=1, ManaModifier:=1, ArmorModifier:=1, RegenHealth:=0, mechanic_immune_mask:=0, flags_extra:=@FLAGS_EX, unit_flags:=`unit_flags`|@FLAGS_U, unit_flags2:=`unit_flags2`|@FLAGS_U2, AIName:='' +WHERE `entry` IN (SELECT `entry` FROM `creature_template_npcbot_extras` WHERE `class`=@CLASS_PRIEST); +UPDATE `creature_template` SET exp:=2, faction:=35, minlevel:=80, maxlevel:=80, BaseAttackTime:=1600, RangeAttackTime:=2000, dynamicflags:=0, speed_walk:=1.1, speed_run:=1.1, scale:=1, HealthModifier:=1, ManaModifier:=1, ArmorModifier:=1, RegenHealth:=0, mechanic_immune_mask:=0, flags_extra:=@FLAGS_EX, unit_flags:=`unit_flags`|@FLAGS_U, unit_flags2:=`unit_flags2`|@FLAGS_U2, AIName:='' +WHERE `entry` IN (SELECT `entry` FROM `creature_template_npcbot_extras` WHERE `class`=@CLASS_ROGUE); +UPDATE `creature_template` SET exp:=2, faction:=35, minlevel:=80, maxlevel:=80, BaseAttackTime:=2600, RangeAttackTime:=2000, dynamicflags:=0, speed_walk:=1.1, speed_run:=1.1, scale:=1, HealthModifier:=1, ManaModifier:=1, ArmorModifier:=1, RegenHealth:=0, mechanic_immune_mask:=0, flags_extra:=@FLAGS_EX, unit_flags:=`unit_flags`|@FLAGS_U, unit_flags2:=`unit_flags2`|@FLAGS_U2, AIName:='' +WHERE `entry` IN (SELECT `entry` FROM `creature_template_npcbot_extras` WHERE `class`=@CLASS_SHAMAN); +UPDATE `creature_template` SET exp:=2, faction:=35, minlevel:=80, maxlevel:=80, BaseAttackTime:=3500, RangeAttackTime:=2000, dynamicflags:=0, speed_walk:=1.1, speed_run:=1.1, scale:=1, HealthModifier:=1, ManaModifier:=1, ArmorModifier:=1, RegenHealth:=0, mechanic_immune_mask:=0, flags_extra:=@FLAGS_EX, unit_flags:=`unit_flags`|@FLAGS_U, unit_flags2:=`unit_flags2`|@FLAGS_U2, AIName:='' +WHERE `entry` IN (SELECT `entry` FROM `creature_template_npcbot_extras` WHERE `class`=@CLASS_WARLOCK); +UPDATE `creature_template` SET exp:=2, faction:=35, minlevel:=80, maxlevel:=80, BaseAttackTime:=3400, RangeAttackTime:=2000, dynamicflags:=0, speed_walk:=1.1, speed_run:=1.1, scale:=1, HealthModifier:=1, ManaModifier:=1, ArmorModifier:=1, RegenHealth:=0, mechanic_immune_mask:=0, flags_extra:=@FLAGS_EX, unit_flags:=`unit_flags`|@FLAGS_U, unit_flags2:=`unit_flags2`|@FLAGS_U2, AIName:='' +WHERE `entry` IN (SELECT `entry` FROM `creature_template_npcbot_extras` WHERE `class`=@CLASS_WARRIOR); +UPDATE `creature_template` SET exp:=2, faction:=35, minlevel:=81, maxlevel:=81, BaseAttackTime:=1940, RangeAttackTime:=1940, dynamicflags:=0, speed_walk:=1.1, speed_run:=1.1, scale:=1, HealthModifier:=1, ManaModifier:=1, ArmorModifier:=1, RegenHealth:=0, mechanic_immune_mask:=1, flags_extra:=@FLAGS_EXN,unit_flags:=`unit_flags`|@FLAGS_U, unit_flags2:=`unit_flags2`|@FLAGS_U2, AIName:='' +WHERE `entry` IN (SELECT `entry` FROM `creature_template_npcbot_extras` WHERE `class`=@CLASS_BM); +UPDATE `creature_template` SET exp:=2, faction:=35, minlevel:=83, maxlevel:=83, BaseAttackTime:=1350, RangeAttackTime:=1350, dynamicflags:=0, speed_walk:=1.1, speed_run:=1.1,scale:=0.7,HealthModifier:=1, ManaModifier:=1, ArmorModifier:=1, RegenHealth:=0, mechanic_immune_mask:=0, flags_extra:=@FLAGS_EX, unit_flags:=`unit_flags`|@FLAGS_U, unit_flags2:=`unit_flags2`|@FLAGS_U2_NM, AIName:='' +WHERE `entry` IN (SELECT `entry` FROM `creature_template_npcbot_extras` WHERE `class`=@CLASS_SPHYNX); +UPDATE `creature_template` SET exp:=2, faction:=35, minlevel:=81, maxlevel:=81, BaseAttackTime:=1420, RangeAttackTime:=1420, dynamicflags:=0, speed_walk:=1.1, speed_run:=2.1, scale:=1, HealthModifier:=1, ManaModifier:=1, ArmorModifier:=1, RegenHealth:=0, mechanic_immune_mask:=0, flags_extra:=@FLAGS_EX, unit_flags:=`unit_flags`|@FLAGS_U, unit_flags2:=`unit_flags2`|@FLAGS_U2, AIName:='' +WHERE `entry` IN (SELECT `entry` FROM `creature_template_npcbot_extras` WHERE `class`=@CLASS_ARCHMAGE); +UPDATE `creature_template` SET exp:=2, faction:=35, minlevel:=83, maxlevel:=83, BaseAttackTime:=2600, RangeAttackTime:=2600, dynamicflags:=0, speed_walk:=1.1, speed_run:=1.1,scale:=1.2,HealthModifier:=1, ManaModifier:=1, ArmorModifier:=1, RegenHealth:=0, mechanic_immune_mask:=0, flags_extra:=@FLAGS_EX, unit_flags:=`unit_flags`|@FLAGS_U, unit_flags2:=`unit_flags2`|@FLAGS_U2_NM, AIName:='' +WHERE `entry` IN (SELECT `entry` FROM `creature_template_npcbot_extras` WHERE `class`=@CLASS_DREADLORD); +UPDATE `creature_template` SET exp:=2, faction:=35, minlevel:=81, maxlevel:=81, BaseAttackTime:=1900, RangeAttackTime:=1900, dynamicflags:=0, speed_walk:=1.1, speed_run:=1.1,scale:=1.1,HealthModifier:=1, ManaModifier:=1, ArmorModifier:=1, RegenHealth:=0, mechanic_immune_mask:=0, flags_extra:=@FLAGS_EX, unit_flags:=`unit_flags`|@FLAGS_U, unit_flags2:=`unit_flags2`|@FLAGS_U2_NM, AIName:='' +WHERE `entry` IN (SELECT `entry` FROM `creature_template_npcbot_extras` WHERE `class`=@CLASS_SPELLBREAKER); +UPDATE `creature_template` SET exp:=2, faction:=35, minlevel:=82, maxlevel:=82, BaseAttackTime:=2000, RangeAttackTime:=2000, dynamicflags:=0, speed_walk:=1.1, speed_run:=1.1, scale:=1, HealthModifier:=1, ManaModifier:=1, ArmorModifier:=1, RegenHealth:=0, mechanic_immune_mask:=0, flags_extra:=@FLAGS_EX, unit_flags:=`unit_flags`|@FLAGS_U, unit_flags2:=`unit_flags2`|@FLAGS_U2_NM, AIName:='' +WHERE `entry` IN (SELECT `entry` FROM `creature_template_npcbot_extras` WHERE `class`=@CLASS_DARK_RANGER); + +-- pets +SET @PET_START = 70501; +SET @PET_END = 70550; +SET @PET_WATER_ELEMENTAL = 70556; +SET @PET_INFERNAL = 70562; +SET @PET_DARK_MINION = 70573; +SET @PET_DARK_MINION_ELITE = 70574; + +UPDATE `creature_template` SET exp:=2, faction:=35, minlevel:=80, maxlevel:=80, dynamicflags:=0, speed_walk:=1.2, speed_run:=1.3, scale:=1.0, HealthModifier:=1, ManaModifier:=1, ArmorModifier:=1, RegenHealth:=0, flags_extra:=@FLAGS_EX_PET, unit_flags:=`unit_flags`|@FLAGS_U_PET, unit_flags2:=`unit_flags2`|@FLAGS_U2_PET, AIName:='' +WHERE `entry` BETWEEN @PET_START and @PET_END; +UPDATE `creature_template` SET exp:=2, faction:=35, minlevel:=81, maxlevel:=81, dynamicflags:=0, speed_walk:=1.2, speed_run:=1.3, scale:=1.3, HealthModifier:=1, ManaModifier:=1, ArmorModifier:=1, RegenHealth:=0, flags_extra:=@FLAGS_EX_PET, unit_flags:=`unit_flags`|@FLAGS_U_PET, unit_flags2:=`unit_flags2`|@FLAGS_U2_PET, AIName:='' +WHERE `entry`=@PET_WATER_ELEMENTAL; +UPDATE `creature_template` SET exp:=2, faction:=35, minlevel:=83, maxlevel:=83, dynamicflags:=0, speed_walk:=1.2, speed_run:=1.3, scale:=1.5, HealthModifier:=1, ManaModifier:=1, ArmorModifier:=1, RegenHealth:=0, flags_extra:=@FLAGS_EX_PET, unit_flags:=`unit_flags`|@FLAGS_U_PET, unit_flags2:=`unit_flags2`|@FLAGS_U2_PET, AIName:='' +WHERE `entry`=@PET_INFERNAL; +UPDATE `creature_template` SET exp:=2, faction:=35, minlevel:=83, maxlevel:=83, dynamicflags:=0, speed_walk:=1.2, speed_run:=1.3, scale:=0.8, HealthModifier:=1, ManaModifier:=1, ArmorModifier:=1, RegenHealth:=0, flags_extra:=@FLAGS_EX_PET_BLOCK, unit_flags:=`unit_flags`|@FLAGS_U_PET, unit_flags2:=`unit_flags2`|@FLAGS_U2_PET, AIName:='' +WHERE `entry`=@PET_DARK_MINION; +UPDATE `creature_template` SET exp:=2, faction:=35, minlevel:=83, maxlevel:=83, dynamicflags:=0, speed_walk:=1.2, speed_run:=1.3, scale:=0.9, HealthModifier:=1, ManaModifier:=1, ArmorModifier:=1, RegenHealth:=0, flags_extra:=@FLAGS_EX_PET_BLOCK, unit_flags:=`unit_flags`|@FLAGS_U_PET, unit_flags2:=`unit_flags2`|@FLAGS_U2_PET, AIName:='' +WHERE `entry`=@PET_DARK_MINION_ELITE; diff --git a/sql/Bots/4_world_generate_bot_equips.sql b/sql/Bots/4_world_generate_bot_equips.sql new file mode 100644 index 000000000..3658fab46 --- /dev/null +++ b/sql/Bots/4_world_generate_bot_equips.sql @@ -0,0 +1,159 @@ +/*!50003 DROP PROCEDURE IF EXISTS `sp__generate_npcbot_equips`*/; + +DELIMITER ;; + +/*!50003 CREATE*/ +/*!50003 PROCEDURE `sp__generate_npcbot_equips`() +BEGIN + +DECLARE CLASS_WARRIOR INT DEFAULT 1; +DECLARE CLASS_PALADIN INT DEFAULT 2; +DECLARE CLASS_HUNTER INT DEFAULT 3; +DECLARE CLASS_ROGUE INT DEFAULT 4; +DECLARE CLASS_PRIEST INT DEFAULT 5; +DECLARE CLASS_DEATH_KNIGHT INT DEFAULT 6; +DECLARE CLASS_SHAMAN INT DEFAULT 7; +DECLARE CLASS_MAGE INT DEFAULT 8; +DECLARE CLASS_WARLOCK INT DEFAULT 9; +DECLARE CLASS_DRUID INT DEFAULT 11; +DECLARE CLASS_BLADEMASTER INT DEFAULT 12; +DECLARE CLASS_SPHYNX INT DEFAULT 13; +DECLARE CLASS_ARCHMAGE INT DEFAULT 14; +DECLARE CLASS_DREADLORD INT DEFAULT 15; +DECLARE CLASS_SPELL_BREAKER INT DEFAULT 16; +DECLARE CLASS_DARK_RANGER INT DEFAULT 17; + +DECLARE RACE_HUMAN INT DEFAULT 1; +DECLARE RACE_ORC INT DEFAULT 2; +DECLARE RACE_DWARF INT DEFAULT 3; +DECLARE RACE_NELF INT DEFAULT 4; +DECLARE RACE_UNDEAD INT DEFAULT 5; +DECLARE RACE_TAUREN INT DEFAULT 6; +DECLARE RACE_GNOME INT DEFAULT 7; +DECLARE RACE_TROLL INT DEFAULT 8; +DECLARE RACE_BELF INT DEFAULT 10; +DECLARE RACE_DRAENEI INT DEFAULT 11; + +DECLARE NPCBOT_ENTRY_BEGIN INT DEFAULT 70001; +DECLARE NPCBOT_ENTRY_END INT DEFAULT 71000; + +DECLARE NPCBOT_ENTRY_PET_DARK_MINION INT DEFAULT 70573; +DECLARE NPCBOT_ENTRY_PET_DARK_MINION_ELITE INT DEFAULT 70574; + +DECLARE cur_pos INT DEFAULT 0; +DECLARE myclass INT; +DECLARE myrace INT; +DECLARE item1 INT DEFAULT 0; +DECLARE item2 INT DEFAULT 0; +DECLARE item3 INT DEFAULT 0; + +DELETE FROM `creature_equip_template` WHERE `CreatureID` BETWEEN NPCBOT_ENTRY_BEGIN AND NPCBOT_ENTRY_END; + +SET cur_pos = NPCBOT_ENTRY_BEGIN; +WHILE cur_pos < NPCBOT_ENTRY_END DO + SET myclass = (SELECT `class` FROM `creature_template_npcbot_extras` WHERE `entry` = cur_pos); + SET myrace = (SELECT `race` FROM `creature_template_npcbot_extras` WHERE `entry` = cur_pos); + + IF myclass != 0 AND myrace != 0 THEN + + IF myclass = CLASS_WARRIOR THEN + IF myrace = RACE_TAUREN THEN + SET item1 = 2361; -- hammer + ELSEIF myrace IN(RACE_DWARF,RACE_ORC) THEN + SET item1 = 2483; -- axe + ELSEIF TRUE THEN + SET item1 = 2497; -- sword + END IF; + ELSEIF myclass = CLASS_PALADIN THEN + SET item1 = 2488; -- gladius + SET item3 = 0; + IF myrace = RACE_BELF THEN + SET item2 = 20841; -- sunstrider shield + ELSE + SET item2 = 7188; -- stormwind guard shield + END IF; + ELSEIF myclass = CLASS_HUNTER THEN + SET item1 = 12282; + SET item2 = 0; + SET item3 = 2506; + ELSEIF myclass = CLASS_ROGUE THEN + SET item3 = 25873; + IF (cur_pos % 2) = 1 THEN + SET item1 = 2092; + SET item2 = 3296; -- daggers + ELSE + SET item1 = 2131; + SET item2 = 2484; -- swords + END IF; + ELSEIF myclass = CLASS_PRIEST THEN + SET item1 = 1388; -- short staff + SET item2 = 0; + SET item3 = 0; + ELSEIF myclass = CLASS_DEATH_KNIGHT THEN + SET item1 = 38633; -- dk axe + SET item2 = 0; + SET item3 = 0; + ELSEIF myclass = CLASS_SHAMAN THEN + SET item1 = 15903; -- claw + SET item2 = 0; + SET item3 = 0; + ELSEIF myclass = CLASS_MAGE THEN + SET item1 = 2132; -- staff + SET item2 = 0; + SET item3 = 0; + ELSEIF myclass = CLASS_WARLOCK THEN + SET item1 = 3661; -- staff + SET item2 = 0; + SET item3 = 0; + ELSEIF myclass = CLASS_DRUID THEN + SET item1 = 3327; -- staff + SET item2 = 0; + SET item3 = 0; + ELSEIF myclass = CLASS_BLADEMASTER THEN + SET item1 = 24044; -- blademaster polearm + SET item2 = 0; + SET item3 = 0; + ELSEIF myclass = CLASS_SPHYNX THEN + SET item1 = 5208; -- smoldering wand + SET item2 = 5208; + SET item3 = 0; + ELSEIF myclass = CLASS_ARCHMAGE THEN + SET item1 = 25917; -- white staff + SET item2 = 0; + SET item3 = 0; + ELSEIF myclass = CLASS_DREADLORD THEN + SET item1 = 0; + SET item2 = 0; + SET item3 = 0; + ELSEIF myclass = CLASS_SPELL_BREAKER THEN + SET item1 = 0; + SET item2 = 0; + SET item3 = 0; + ELSEIF myclass = CLASS_DARK_RANGER THEN + SET item1 = 20849; -- arcane forged shortsword (1.7 dps) + SET item2 = 0; + SET item3 = 34529; -- vengeful gladiator's longbow + END IF; + + INSERT INTO `creature_equip_template` (`CreatureID`,`ID`,`itemID1`,`itemID2`,`itemID3`,`VerifiedBuild`) VALUES (cur_pos,1,item1,item2,item3,-1); + + ELSEIF cur_pos = NPCBOT_ENTRY_PET_DARK_MINION OR cur_pos = NPCBOT_ENTRY_PET_DARK_MINION_ELITE THEN + SET item1 = 3935; + SET item2 = 15648; + SET item3 = 0; + + INSERT INTO `creature_equip_template` (`CreatureID`,`ID`,`itemID1`,`itemID2`,`itemID3`,`VerifiedBuild`) VALUES (cur_pos,1,item1,item2,item3,-1); + + END IF; + + SET cur_pos = cur_pos + 1; + +END WHILE; + +END */;; + +DELIMITER ; + +CALL `sp__generate_npcbot_equips`(); + +DROP PROCEDURE IF EXISTS `sp__generate_npcbot_equips`; diff --git a/sql/Bots/5_world_botgiver.sql b/sql/Bots/5_world_botgiver.sql new file mode 100644 index 000000000..71bf34434 --- /dev/null +++ b/sql/Bots/5_world_botgiver.sql @@ -0,0 +1,18 @@ +DELETE FROM `creature_template` WHERE `entry` = 70000; +INSERT INTO `creature_template` +(`entry`,`difficulty_entry_1`,`difficulty_entry_2`,`difficulty_entry_3`,`KillCredit1`,`KillCredit2`, +`modelid1`,`modelid2`,`modelid3`,`modelid4`,`name`,`subname`,`IconName`,`gossip_menu_id`,`minlevel`,`maxlevel`,`exp`, +`faction`,`npcflag`,`speed_walk`,`speed_run`,`scale`,`rank`,`dmgschool`,`BaseAttackTime`,`RangeAttackTime`, +`BaseVariance`,`RangeVariance`,`unit_class`,`unit_flags`,`unit_flags2`,`dynamicflags`,`family`,`type`,`type_flags`,`lootid`, +`pickpocketloot`,`skinloot`,`PetSpellDataId`,`VehicleId`,`mingold`,`maxgold`,`AIName`,`MovementType`, +`HoverHeight`,`HealthModifier`,`ManaModifier`,`ArmorModifier`,`DamageModifier`,`ExperienceModifier`,`RacialLeader`,`movementId`,`RegenHealth`, +`mechanic_immune_mask`,`spell_school_immune_mask`,`flags_extra`,`ScriptName`,`VerifiedBuild`) +VALUES +('70000','0','0','0','0','0','27541','0','0','0','Lagretta','Bots for hire','','0','83','83','2','35','1','1.4','1.14286','0.7','4','0','0','0','1','1','1','33088','2048','0','0','0','0','0','0','0','0','0','0','0','','0','1','4.8','1','1','1','1','0','0','1','0','0','0','script_bot_giver','-1'); + +DELETE FROM `npc_text` WHERE `ID` BETWEEN 70201 AND 70204; +INSERT INTO `npc_text` (`ID`,`text0_0`,`VerifiedBuild`) VALUES +('70201','There are always dudes ready to kill for money.','-1'), +('70202','Mercenaries are always in demand. Here is what available right now.','-1'), +('70203','Mercenaries are always in demand. Here is what available right now.','-1'), +('70204','Seems like there is nobody available right now, check again later.','-1'); diff --git a/sql/Bots/characters_bots.sql b/sql/Bots/characters_bots.sql new file mode 100644 index 000000000..ab0a51466 --- /dev/null +++ b/sql/Bots/characters_bots.sql @@ -0,0 +1,29 @@ +-- +SET FOREIGN_KEY_CHECKS=0; + +DROP TABLE IF EXISTS `characters_npcbot`; +CREATE TABLE `characters_npcbot` ( + `entry` int(10) unsigned NOT NULL COMMENT 'creature_template.entry', + `owner` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'characters.guid (lowguid)', + `roles` smallint(5) unsigned NOT NULL COMMENT 'bitmask: tank(1),dps(2),heal(4),ranged(8)', + `faction` int(10) unsigned NOT NULL DEFAULT '35', + `equipMhEx` int(10) unsigned NOT NULL DEFAULT '0', + `equipOhEx` int(10) unsigned NOT NULL DEFAULT '0', + `equipRhEx` int(10) unsigned NOT NULL DEFAULT '0', + `equipHead` int(10) unsigned NOT NULL DEFAULT '0', + `equipShoulders` int(10) unsigned NOT NULL DEFAULT '0', + `equipChest` int(10) unsigned NOT NULL DEFAULT '0', + `equipWaist` int(10) unsigned NOT NULL DEFAULT '0', + `equipLegs` int(10) unsigned NOT NULL DEFAULT '0', + `equipFeet` int(10) unsigned NOT NULL DEFAULT '0', + `equipWrist` int(10) unsigned NOT NULL DEFAULT '0', + `equipHands` int(10) unsigned NOT NULL DEFAULT '0', + `equipBack` int(10) unsigned NOT NULL DEFAULT '0', + `equipBody` int(10) unsigned NOT NULL DEFAULT '0', + `equipFinger1` int(10) unsigned NOT NULL DEFAULT '0', + `equipFinger2` int(10) unsigned NOT NULL DEFAULT '0', + `equipTrinket1` int(10) unsigned NOT NULL DEFAULT '0', + `equipTrinket2` int(10) unsigned NOT NULL DEFAULT '0', + `equipNeck` int(10) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`entry`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; diff --git a/sql/Bots/locales/deDE/npc_text_locale.sql b/sql/Bots/locales/deDE/npc_text_locale.sql new file mode 100644 index 000000000..d2a6f6be4 --- /dev/null +++ b/sql/Bots/locales/deDE/npc_text_locale.sql @@ -0,0 +1,409 @@ +DELETE FROM `npc_text_locale` WHERE `Locale`='deDE' AND `ID` BETWEEN '70000' AND '71000'; +INSERT INTO `npc_text_locale` (`ID`, `Locale`, `Text0_0`, `Text0_1`, `Text1_0`, `Text1_1`, `Text2_0`, `Text2_1`, `Text3_0`, `Text3_1`, `Text4_0`, `Text4_1`, `Text5_0`, `Text5_1`, `Text6_0`, `Text6_1`, `Text7_0`, `Text7_1`) +VALUES +('70001','deDE','Ich lebe nur um dem Meister zu dienen.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70002','deDE','Brauchst du etwas?',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70003','deDE','Sterbliche... Normalerweise töte ich Abschaum wie euch auf den ersten Blick.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70004','deDE','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70005','deDE','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70006','deDE','Bist du überrascht, Sterblicher? Als unbedeutenderer Nathrezim muss ich mir Verbündete suchen. Du siehst aus, als könntest du mich zumindest unterhalten.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70007','deDE','Was ist es diesmal, Sterblicher?',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70008','deDE','Kannst du mich einfach in Ruhe lassen? ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70009','deDE','Was nun?',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70010','deDE','$B$BS-s-s-spare die Worte, Sterblicher...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70011','deDE','Habe ich wieder meine Haare durcheinander gebracht? $B...Nein, habe ich nicht. Was ist es dann?',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70012','deDE','$B$BWas wird es sein, kleine Kreatur? Dein Fleisch wird wie jedes andere sein...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70013','deDE','Ich verzehre die Lebenden und die Toten.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70101','deDE','|cffff3300Klingenmeister|r$b|cffdd6600-=Warcraft III Tribut=-|r$B$B "Ein Elite-Schwertkämpfer, ehemaliges Mitglied des Clans der Brennenden Klinge, jetzt ein Elite-Kämpfer der Horde".$B$BHauptattribut: Beweglichkeit.$B$BNetherlauf (Windlauf). Ermöglicht es dem Blademaster, unsichtbar zu werden und sich für eine bestimmte Zeit schneller zu bewegen. Wenn der Klingenmeister eine Einheit angreift, um die Unsichtbarkeit zu durchbrechen, verursacht er zusätzlichen Schaden.$B$BSpiegelbild. Verwirrt den Feind, indem er Illusionen des Klingenmeisters erzeugt und alle auf den Klingenmeister wirkende Zauber bannt.$B$BKritischer Schlag (passiv). Gewährt eine 15%ige Chance, mit seinen Angriffen das 2(3,4)-fache des normalen Schadens als kritisch zu verursachen.$B$BKlingensturm (NYI). Gewährt Immunität gegen Magie und fügt allen umstehenden Feinden Schaden zu.$B$B',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70102','deDE','|cff9900ccObsidian Zerstörer|r$b|cffdd6600-=Warcraft III Tribut=-|r$B$B "Ein geflügeltes Monstrum aus Obsidian mit unstillbarem Hunger nach Magie". $B$BHohe Rüstung, sehr hohe Resistenzen, teilweise immun gegen Magie, verliert mit der Zeit Mana und profitiert nicht von passiven Manaregenerationseffekten, Kettenhemd/Plattenrüstung, zwei Zauberstäbe, verursacht Schattenzauber-Schaden, kein physischer Angriff, kann keine Gegner angreifen, die sich nicht in der Nähe befinden, während er sich bewegt, Zauberkraft-Bonus: 50% Angriffskraft + 200% Intellekt + Zauberstab-Schaden.$B$BMagie verschlingen. Entfernt bis zu 2 magische Effekte von Gegnern, bis zu 2 magische Effekte und bis zu 2 Flüche von Verbündeten und Schaden verursachende beschworene Einheiten im Umkreis von 20 Metern. Jeder gebannte Effekt stellt 20% Mana und 5% Gesundheit wieder her, 7 Sekunden Abklingzeit.$B$BSchattenschlag. Verstärkter Angriff, der erhöhten Splash-Schaden verursacht.$B$BMana entziehen. Entzieht einer zufälligen befreundeten Einheit das gesamte Mana (begrenzt durch den Manapool des Zaubernden).$B$BMana auffrischen. Versorgt umstehende Gruppen- und Schlachtzugsmitglieder im Umkreis von 25 Metern mit 3% ihres maximalen Manas, wobei das Mana des Zaubernden annulliert wird, 3 Sekunden Abklingzeit.$B$BRegenerierende Aura. Heilt Gruppen- und Schlachtzugsmitglieder im Umkreis von 25 Metern um 3% ihrer maximalen Gesundheit und macht das Mana des Zaubernden zunichte, 3 Sekunden Abklingzeit.$B$BSchattenrüstung (passiv). Stellt Mana in Höhe eines Prozentsatzes des erlittenen Schadens wieder her.$B$B',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70103','deDE','|cff0000ddErzmagier|r$b|cffdd6600-=Warcraft III Tribut=-|r$B$BSZauberschaden um 35% reduziert, teilweise immun gegen Kontrolleffekte, Stoffrüstung, verursacht Feuerzauber/Frostzauber Schaden, kein physischer Angriff, Zaubermachtbonus: 100% Intelligenz. Hauptattribut: Intelligenz.$B$BBlizzard. Ein typischer Blizzard, nur etwas mächtiger, 6 Sekunden Abklingzeit.$B$BWasserelementar beschwören. Beschwört ein Wasserelementar, das die Feinde des Erzmagiers 1 Minute lang angreift, 20 Sekunden Abklingzeit.$B$BBrillianz Aura. Erhöht das maximale Mana um 10% und erhöht die Manaregeneration von Gruppen- und Schlachtzugsmitgliedern im Umkreis von 40 Metern erheblich.$B$BMassenteleport. NYI.$B$B',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70104','deDE','|cff9900ccSchreckenslord|r$b|cffdd6600-=Warcraft III Tribut=-|r$B$B"|Unglaublich mächtiger Dämon, der die Macht der Finsternis und geistige Beherrschung ausübt".$B$BHohe Rüstung, hohe Resistenzen, teilweise immun gegen Kontrolleffekte, erlittener Schaden beschleunigt die Wiederaufladung von Zaubern, Plattenrüstung, verursacht Nahkampf-/Schattenzauberschaden, Bonusschaden gegen kampfunfähige Ziele, Bonus auf Zaubermacht: 200% Stärke. Hauptattribut: Stärke.$B$BAasschwarm. Schickt eine Horde Fledermäuse in Kombination mit Chaos Magie, um Gegnern in einem sehr großen Frontalkegel Schaden zuzufügen, kann nicht kritisch sein, 10 Sekunden Abklingzeit.$B$BSchlaf. Versetzt das gegnerische Ziel für 60 Sekunden in Schlaf und ermöglicht es, dass der nächste physische Angriff auf dieses Ziel die Rüstung umgeht, direkter verursachter Schaden weckt das Ziel auf, 6 Sekunden Abklingzeit.$B$BVampirische Aura. Erhöht physischen kritischen Schaden um 5% und heilt Gruppen- und Schlachtzugsmitglieder im Umkreis von 40 Metern um einen bestimmten Prozentsatz (100% für den Schreckenslord und 25% für alle anderen) des durch physische Nahkampfangriffe und Aasschwarm verursachten Schadens, keine Bedrohung.$B$BSBeschwörung eines Höllendieners. Ruft einen Höllendiener vom Himmel, der Schaden verursacht und gegnerische Einheiten betäubt. Der Höllendiener ist sehr resistent gegen Magie und hält 180 Sekunden lang, 180 Sekunden Abklingzeit.$B$B',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70105','deDE','|cff0000ddZauberbrecher|r$b|cffdd6600-=Warcraft III Tribut=-|r$B$B"Ein elfischer Krieger, der darauf trainiert ist, magische Energien zu unterbrechen und zu verzerren".$B$BSZauberschaden um 75% reduziert, teilweise immun gegen Kontrolleffekte, Rüstungsabzug -30%, Ketten-/Panzerrüstung, verursacht Nahkampf-/Arkanschaden, Zaubermachtbonus: 200% Stärke. Hauptattribut: Stärke.$B$BMagie stehlen (Zauberraub). Stiehlt einem Feind einen nützlichen Zauber und wendet ihn auf einen Verbündeten in der Nähe an oder entfernt einen negativen Zauber von einem Verbündeten und wendet ihn auf einen Feind in der Nähe an, wirkt auf Magie- und Fluch-Effekte, 2 Sekunden Abklingzeit.$B$BRückkopplung (passiv). Erfolgreiche Nahkampfangriffe verbrennen das Mana des Ziels in Höhe des verursachten Schadens (erhöht durch Zaubermacht) und verursachen arkanen Schaden. Wenn das Mana des Ziels aufgebraucht ist, verursachen die Nahkampfangriffe von Zauberbrecher dreifachen Schaden mit erhöhter Chance auf einen kritischen Treffer. Wenn das Ziel kein Mana hat, erhält der Zauberbrecher Mana in Höhe von 25% des verursachten Schadens.$B$B',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70106','deDE','|cff9900ccDunkler Waldläufer|r$b|cffdd6600-=Warcraft III Tribut=-|r$B$B"Ein ehemaliger Waldläufer von Quel Thalas, der gewaltsam von den Toten auferweckt wurde."$B$BSZauberschaden um 35% reduziert, untot, teilweise immun gegen Kontrolleffekte, Leder-/Stoffrüstung, verursacht physischen/zauberhaften Schattenschaden, haftet an Schatten und stellt keine Bedrohung dar, Zauberkraftbonus: 50% Intelligenz. Hauptattribut: Beweglichkeit.$B$BStille. Bringt einen Gegner und bis zu 4 Ziele in der Nähe 8 Sekunden lang zum Schweigen, 15 Sekunden Abklingzeit.$B$BBSchwarzer Pfeil. Feuert einen verfluchten Pfeil ab, der 150% Waffenschaden und zusätzlichen Schattenzauberschaden über Zeit verursacht. Wenn das betroffene Ziel durch den Schaden des dunklen Waldläufers stirbt, spawnen aus der Leiche dunkle Schergen (maximal 5 Schergen, 80 Sekunden Dauer, funktioniert nur bei Humanoiden, Bestien und Drachenkin). Verursacht fünfmal mehr Schaden, wenn das Ziel weniger als 20% Gesundheit hat.$B$BLebensentzug. Entzieht einem Feind 5 Sekunden lang jede Sekunde Leben und heilt den dunklen Waldläufer für 200% der entzogenen Menge.$B$B',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70107','deDE','|cff9900ccTotenbeschwörer|r$b|cffdd6600-=Warcraft III / Diablo II Tribut=-|r$B$BSZauberschaden um 20% reduziert, teilweise immun gegen Kontrolleffekte, Stoffrüstung, verursacht Zauberschattenschaden, kein physischer Angriff, Zaubermachtbonus: 100% Intelligenz. Hauptattribut: Intelligenz.$B$BRaise Dead. Erweckt 2 Skelette aus einer Leiche (maximal 6 Skelette, 65 Sekunden Dauer, funktioniert nur bei Humanoiden, Bestien und Drachenkin).$B$BUnheilige Raserei. Erhöht das Nahkampfangriffstempo des Ziels um 75%, entzieht ihm aber ständig Lebenspunkte. Hält 45 Sekunden lang an. Kann nicht abgebrochen werden. Wird auf Stufe 30 freigeschaltet.$B$BCorpse Explosion. Lässt eine Leiche explodieren und fügt allen umstehenden Gegnern Schaden in Höhe von 35% bis 75% der maximalen Lebenspunkte der toten Einheit zu (abhängig von der Stufe des Nekromanten). Dieser Schaden erzeugt keine Bedrohung. Wird auf Stufe 40 freigeschaltet.$B$BCVerkrüppeln. Verringert das Bewegungstempo, das Nahkampfangriffstempo und die Gesamtstärke des Ziels 60 Sekunden lang um 50%. Wird auf Stufe 50 freigeschaltet.$B$B',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70108','deDE','|cff0000ddMeereshexe|r$b|cffdd6600-=Warcraft III Tribut=-|r$B$B"Eine bösartige, schuppige Meeresbewohnerin, die oft mit dem Aufkommen gewaltiger Stürme in Verbindung gebracht wird." $B$BSZauberschaden um 30% reduziert, teilweise immun gegen Kontrolleffekte, Stoffrüstung, verursacht physischen Schaden, Zauberfrost und Zaubersturm, Angriffskraftbonus: Beweglichkeit x2, Zauberkraftbonus: 200% Intelligenz. Hauptattribut: Intelligenz.$B$BGabelblitzschlag. Ruft einen Blitzkegel herbei, um Feinden Schaden zuzufügen. Trifft 2 bis alle Ziele (je nach Stufe der Meerhexe) und betäubt sie 2 Sekunden lang. Dieser Schaden erzeugt keine Bedrohung.$B$BFrostpfeile. Durchtränkt Pfeile mit Frostzauber für zusätzlichen Schaden und verlangsamt die Bewegungs-, Angriffs- und Wirkgeschwindigkeit des Ziels um 30% bis 70% (abhängig von der Stufe der Meereshexe).$B$BMana-Schild. Erzeugt einen Schild, der 100% des eingehenden (nicht gemilderten) Schadens absorbiert, indem er das Mana der Meerhexe verwendet. Die Wirkung reicht von 1 Schaden pro 10 Mana bis 10 Schaden pro 1 Mana (je nach Stufe der Meereshexe).$B$BTornado. Beschwört einen heftigen Tornado, der gegnerische Einheiten in der Nähe beschädigt und verlangsamt, manchmal sogar komplett außer Gefecht setzt. Der Tornado wächst im Freien mit der Zeit und erhöht den verursachten Schaden und den Wirkungsbereich, schrumpft aber in geschlossenen Räumen und löst sich schnell auf. Wird auf Stufe 60 freigeschaltet.$B$BNaga (Passiv). Schwimmgeschwindigkeit, Schaden und Ausweichchance werden im Wasser stark erhöht.$B$B',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70109','deDE','|cff9900ccGruftlord|r$b|cffdd6600-=Warcraft III Tribut=-|r$B$B"Uraltes Ungetüm, einst einer der Könige von Azjol-Nerub, jetzt ein untotes Monster in den Reihen der mächtigsten Krieger des Lichkönigs".$B$BSehr hohe Rüstung, erhöhte Resistenzen, teilweise immun gegen Steuerungseffekte, immun gegen Gifteffekte, Kettenhemd/Plattenrüstung, verursacht Nahkampf-/Zauberschattenschaden, Zauberkraftbonus: 200% Stärke. Hauptattribut: Stärke.$B$BImpale. Der Gruftlord schlägt mit seinen massiven Klauen auf den Boden und schießt Stacheln in einem frontalen Kegel aus, die Schaden verursachen und feindliche Einheiten in die Luft schleudern, um sie zu betäuben. Wird auf Stufe 20 freigeschaltet.$B$BSpitzenpanzer. Der Chitinpanzer des Crypt Lords erhöht die Schadensresistenz und fügt gegnerischen Nahkämpfern 15% bis 50% Schaden zu.$B$BAaskäfer. Der Gruftlord generiert aus der frischen Leiche eines Feindes einen Aaskäfer, der seine Feinde angreift. Die Käfer sind permanent, regenerieren aber keine Gesundheit und es können nur 6 gleichzeitig kontrolliert werden. Höhere Stufen erlauben es dem Gruftlord, mächtigere Käfer zu beschwören. Wird auf Stufe 10 freigeschaltet.$B$BLKäferschwarm. Der Gruftlord lässt einen Schwarm von 20-40 (hängt von der Stufe des Crypt Lords ab) wütenden Heuschrecken frei, die in der Nähe befindliche feindliche Einheiten beißen und zerreißen, wodurch deren Bewegungs- und Angriffsfähigkeit eingeschränkt wird. Während sie das gegnerische Fleisch verzehren, wandeln sie es in eine Substanz um, die dem Gruftlord bei seiner Rückkehr Trefferpunkte zurückgibt. Wird auf Stufe 40 freigeschaltet.$B$B',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70201','deDE','Es gibt immer Leute, die bereit sind, für Geld zu töten.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70202','deDE','Söldner sind immer gefragt. Hier ist, was im Moment verfügbar ist:',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70203','deDE','Söldner sind immer gefragt. Hier ist, was im Moment verfügbar ist:',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70204','deDE','Im Moment scheint niemand verfügbar zu sein, schauen Sie später noch einmal nach.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70300','deDE','Stirb!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70301','deDE','Belebe dich wieder',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70302','deDE','Wiederbelebe ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70303','deDE','dein bot',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70304','deDE',' Bot',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70305','deDE','Ich kann noch kein Wasser herbeizaubern',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70306','deDE','Ich kann noch kein Essen herbeizaubern',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70307','deDE','Ich kann das jetzt nicht tun',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70308','deDE','Bitte sehr...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70309','deDE','Deaktiviert',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70310','deDE','Noch nicht bereit',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70311','deDE','Ungültiger Objekttyp',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70312','deDE','Fehlgeschlagen',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70313','deDE','Erledigt',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70314','deDE','Ich bin nicht gestaltgewandelt',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70315','deDE','Ich habe keinen Gesundheitsstein',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70316','deDE','Ich kann noch keine Gesundheitssteine erstellen!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70317','deDE','WTF Ich kann keine Schlösser knacken!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70318','deDE','Mein Fähigkeitslevel ist nicht hoch genug',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70319','deDE','Ich ändere meine Talente zu ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70320','deDE','Waffen',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70321','deDE','Furor',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70322','deDE','Schutz',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70323','deDE','Vergeltung',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70324','deDE','Tierherrschaft',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70325','deDE','Treffsicherheit',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70326','deDE','Überleben',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70327','deDE','Meucheln',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70328','deDE','Kampf',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70329','deDE','Täuschung',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70330','deDE','Disziplin',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70331','deDE','Heilig',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70332','deDE','Schatten',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70333','deDE','Blut',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70334','deDE','Frost',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70335','deDE','Unheilig',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70336','deDE','Elementar',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70337','deDE','Verstärkung',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70338','deDE','Wiederherstellung',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70339','deDE','Arkan',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70340','deDE','Feuer',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70341','deDE','Gebrechen',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70342','deDE','Dämonologie',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70343','deDE','Zerstörung',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70344','deDE','Gleichgewicht',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70345','deDE','Widlheit',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70346','deDE','Unbekannt',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70347','deDE','Verschwinde, Schwächling',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70348','deDE',' ist nicht überzeugt',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70349','deDE','Ich werde meine Zeit nicht mit irgendetwas verschwenden.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70350','deDE','NYI',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70351','deDE','NYI',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70352','deDE','NYI',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70353','deDE','Ich bin bereit',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70354','deDE','Geh weg. Ich diene meinem Herrn ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70355','deDE','unbekannt',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70356','deDE',' auf dich!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70357','deDE',' auf mich!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70358','deDE',' auf ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70359','deDE',' benutzt!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70360','deDE','bot tank',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70361','deDE','Klasse',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70362','deDE','Spieler',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70363','deDE','Meister',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70364','deDE','nichts',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70365','deDE','Rang',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70366','deDE','Talent',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70367','deDE','Passiv',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70368','deDE','versteckt',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70369','deDE','bekannt',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70370','deDE','Fähigkeit',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70371','deDE','Str',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70372','deDE','Bew',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70373','deDE','Aus',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70374','deDE','Int',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70375','deDE','Wil',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70376','deDE','unk stat',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70377','deDE','Gesamt',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70378','deDE','Nahkampf AP',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70379','deDE','Distanz AP',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70380','deDE','Rüstung',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70381','deDE','Krit',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70382','deDE','Verteidigung',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70383','deDE','Verfehlen',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70384','deDE','Ausweichen',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70385','deDE','Parrieren',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70386','deDE','Blocken',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70387','deDE','Blockwert',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70388','deDE','Schaden erhalten Nahkampf',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70389','deDE','Schaden erhalten Zauber',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70390','deDE','Schadensreichweite Waffenhand',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70391','deDE','Schadensmultiplikator Waffenhand',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70392','deDE','Angriffszeit Waffenahnd',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70393','deDE','Schadensreichweite Schildhand',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70394','deDE','Schadensmultiplikator Schildhand',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70395','deDE','Angriffszeit Schildhand',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70396','deDE','Schadensreichweite Distanz',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70397','deDE','SchadensmultiplikatorDistanz',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70398','deDE','Angriffszeit Distanz',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70399','deDE','min',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70400','deDE','max',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70401','deDE','DPS',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70402','deDE','Grundlegende HP',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70403','deDE','Gesamt HP',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70404','deDE','Grundlegendes Mana',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70405','deDE','Gesamt Mana',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70406','deDE','Aktuelles Mana',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70407','deDE','Zaubermacht',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70408','deDE','Leben alle 5 Sekunden bonus',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70409','deDE','Mana alle 5 Sekunden ohne Zaubern',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70410','deDE','Mana alle 5 Sekunden beim Zaubern',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70411','deDE','Tempo',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70412','deDE','Trefferwertung',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70413','deDE','Waffenkunde',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70414','deDE','Rüstungsdurchschlag',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70415','deDE','Zauberdurchschlag',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70416','deDE','%',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70417','deDE','Heilig',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70418','deDE','Feuer',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70419','deDE','Natur',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70420','deDE','Frost',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70421','deDE','Schatten',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70422','deDE','Arkan',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70423','deDE','Resistenz',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70424','deDE','Befehlszustand',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70425','deDE','Folgen',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70426','deDE','Angreifen',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70427','deDE','Warten',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70428','deDE','Zurücksetzen',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70429','deDE','Vollständig stoppen',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70430','deDE','Folgeabstand',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70431','deDE','Spezifikation',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70432','deDE','Hauptaufgabe des Bots',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70433','deDE','Bot Sammelrolle',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70434','deDE','PvP Tötungen',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70435','deDE','Spieler',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70436','deDE','Gestorben ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70437','deDE',' mal',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70438','deDE','%s (Bot) beruhigt sich wieder',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70439','deDE','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70440','deDE','Bist du sicher, dass du es riskieren willst ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70441','deDE',' Aufmerksamkeit?',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70442','deDE','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70443','deDE','Möchtest du anlocken ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70444','deDE','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70445','deDE','Möchtest du folgendes anheuern ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70446','deDE','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70447','deDE','Ausrüstung verwalten...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70448','deDE','Rollen verwalten...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70449','deDE','Formation verwalten...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70450','deDE','Fähigkeiten verwalten...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70451','deDE','Talente verwalten...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70452','deDE','Verbrauchsgüter geben...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70453','deDE','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70454','deDE','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70455','deDE','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70456','deDE','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70457','deDE','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70458','deDE','Folge mir',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70459','deDE','Halte deine Position',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70460','deDE','Bleibe hier und tue nichts',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70461','deDE','Ich brauche Essen',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70462','deDE','Ich brauche was zu trinken',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70463','deDE','Ich brauche einen gedeckten Tisch',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70464','deDE','Hilf mir ein Schloss zu knacken',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70465','deDE','Ich brauche einen Gesundheitsstein',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70466','deDE','Ich brauche einen Seelenbrunnen',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70467','deDE','Frische deine Gifte auf',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70468','deDE','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70469','deDE','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70470','deDE','Ich möchte Verzauberungen erneuern',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70471','deDE','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70472','deDE','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70473','deDE','Entferne deine Gestaltwandlung',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70474','deDE','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70475','deDE','Du bist entlassen',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70476','deDE','Willst du aufgeben ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70477','deDE','Du wirst es vielleicht bereuen...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70478','deDE','Reiß dich zusammen, verdammt noch mal!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70479','deDE','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70480','deDE','Vergiss es',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70481','deDE','Distanz',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70482','deDE','ZURÜCK',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70483','deDE','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70484','deDE','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70485','deDE','Zufällig (Gerissenheit)',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70486','deDE','Zufällig (Wildheit)',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70487','deDE','Zufällig (Zähigkeit)',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70488','deDE','Zeig mir deine Ausrüstung',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70489','deDE','Automatisch anlegen',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70490','deDE','Waffenhand',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70491','deDE','Schildhand',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70492','deDE','Distanz',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70493','deDE','Relikt',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70494','deDE','Kopf',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70495','deDE','Schultern',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70496','deDE','Brust',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70497','deDE','Tailie',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70498','deDE','Beine',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70499','deDE','Füße',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70500','deDE','Handgelenke',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70501','deDE','Hände',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70502','deDE','Rücken',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70503','deDE','Hemd',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70504','deDE','Finger 1',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70505','deDE','Finger 2',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70506','deDE','Schmuck 1',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70507','deDE','Schmuck 2',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70508','deDE','Hals',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70509','deDE','Alles ablegen',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70510','deDE','Ausrüstung aktualisieren (nur Visuell)',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70511','deDE','nur Visuell',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70512','deDE','Ausgerüstet',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70513','deDE','nichts',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70514','deDE','Verwende deine alte Ausrüstung',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70515','deDE','Ablegen',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70516','deDE','Hm... Ich habe nichts, was ich dir geben könnte.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70517','deDE','Sammeln',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70518','deDE','Fähigkeiten Status',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70519','deDE','Erlaubte Fähigkeiten verwalten',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70520','deDE','Benutze ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70521','deDE','Aktualisieren',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70522','deDE','Schaden',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70523','deDE','Kontrolle',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70524','deDE','Heiler',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70525','deDE','Anderes',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70526','deDE',' macht ein knirschendes Geräusch und beginnt zu folgen ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70527','deDE','%s wird sich dir erst anschließen, wenn sein Besitzer ihn entlässt',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70528','deDE','%s wird sich dir erst anschließen, wenn du Stufe 60 erreicht hast.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70529','deDE','%s wird sich dir erst anschließen, wenn du Stufe 55 erreicht hast.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70530','deDE','%s wird sich dir erst anschließen, wenn du Stufe 40 erreicht hast.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70531','deDE','%s wird sich dir erst anschließen, wenn du Stufe 20 erreicht hast.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70532','deDE','Du hast die maximale Anzahl an Npcbots für dein Level überschritten (%u)',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70533','deDE','Sie haben nicht genug Geld',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70534','deDE','Du kannst nicht mehr Bots dieser Klasse haben! %u von %u',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70535','deDE','Ausrüstung in Slot %u (%s) kann nicht zurückgesetzt werden! Bot kann nicht entlassen werden!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70536','deDE','aktuell',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70537','deDE','Angriffsdistanz',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70538','deDE','Angriffe aus kurzer Entfernung',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70539','deDE','Angriffe aus großer Entfernung',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70540','deDE','Exakt',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70541','deDE','Buff entfernen',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70542','deDE','Lege deinen Krafttyp fest',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70543','deDE','Kann %s aus irgendeinem dummen Grund nicht ausrüsten! Versenden per Post',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70544','deDE','Tank',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70545','deDE','Distanz',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70546','deDE','Bergbauer',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70547','deDE','Kräuterkundler',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70548','deDE','Kürschner',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70549','deDE','Ingenieur',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70550','deDE','Besitz des Bots abgelaufen',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70551','deDE','Das NpcBot-System ist derzeit deaktiviert. Bitte kontaktiere die Administration.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70552','deDE','%s wird sich Ihnen nicht anschließen, hat bereits einen Meister: %s',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70553','deDE','%s kann Ihnen nicht beitreten, wenn Sie sich teleportieren wollen',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70554','deDE','Aspekt',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70555','deDE','des Affen',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70556','deDE','des Falken',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70557','deDE','des Geparden',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70558','deDE','der Viper',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70559','deDE','des Wildtiers',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70560','deDE','des Rudels',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70561','deDE','der Wildnis',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70562','deDE','des Drachenfalken',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70563','deDE','kein Aspekt',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70564','deDE','Aura',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70565','deDE','der Hingabe',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70566','deDE','der Konzentration',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70567','deDE','des Feuerwiderstands',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70568','deDE','des Frostwiderstands',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70569','deDE','des Schattenwiderstands',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70570','deDE','der Vergeltung',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70571','deDE','des Kreuzfahrers',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70572','deDE','Keine Aura',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70573','deDE','Verkrüppelndes Gift',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70574','deDE','Sofort wirkendes Gift',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70575','deDE','Tödliches Gift',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70576','deDE','Wundgift',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70577','deDE','Gedankenbenebelndes Gift',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70578','deDE','Narkotisierendes Gift',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70579','deDE','Nichts',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70580','deDE','Flammenzunge',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70581','deDE','Frostbrand',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70582','deDE','Windzorn',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70583','deDE','Lebensgeister',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70584','deDE','Ich brauche deine Dienste',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70585','deDE','Du hast zuviele Bots',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70586','deDE','Möchtest du diesen Bot anheuern? ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70587','deDE',' ist im Moment etwas beschäftigt, versuchen Sie es später noch einmal.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70588','deDE','Ein Vergnügen, mit Ihnen Geschäfte zu machen',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70589','deDE','Krieger',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70590','deDE','Paladine',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70591','deDE','Magier',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70592','deDE','Priester',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70593','deDE','Hexenmeister',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70594','deDE','Druiden',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70595','deDE','Todesritter',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70596','deDE','Schurken',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70597','deDE','Schamanen',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70598','deDE','Jäger',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70599','deDE','Klingenmeister',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70600','deDE','Zerstörer',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70601','deDE','Erzmagier',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70602','deDE','Schreckenslords',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70603','deDE','Zauberbrecher',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70604','deDE','Dunkle Waldläufer',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70605','deDE','Krieger',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70606','deDE','Paladin',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70607','deDE','Magier',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70608','deDE','Priester',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70609','deDE','Hexenmeister',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70610','deDE','Druide',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70611','deDE','Todesritter',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70612','deDE','Schurke',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70613','deDE','Schamane',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70614','deDE','Jäger',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70615','deDE','Klingenmeister',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70616','deDE','Zerstörer',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70617','deDE','Erzmagier',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70618','deDE','Schreckenslord',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70619','deDE','Zauberbrecher',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70620','deDE','Dunkler Waldläufer',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70621','deDE','Männlich',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70622','deDE','Weiblich',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70623','deDE','Mensch',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70624','deDE','Orc',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70625','deDE','Zwerg',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70626','deDE','Nachtelf',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70627','deDE','Untote',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70628','deDE','Tauren',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70629','deDE','Gnom',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70630','deDE','Troll',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70631','deDE','Blutelf',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70632','deDE','Draenei',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70633','deDE','Unbekannt',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70634','deDE','Plündern',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70635','deDE','|cff9d9d9dSchlecht|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70636','deDE','|cffffffffGewöhnlich|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70637','deDE','|cff1eff00Außergewöhnlich|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70638','deDE','|cff0070ddSelten|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70639','deDE','|cffa335eeEpisch|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70640','deDE','|cffff8000Legendär|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70641','deDE','Kampfbeginn verhalten',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70642','deDE','Verzögerung des Angriffs um',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70643','deDE','Verzögerung der Heilung um',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70644','deDE','s',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70645','deDE','Off-Tank',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70646','deDE','Totenbeschwörers',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70647','deDE','Totenbeschwörer',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70648','deDE','Angriffswinkel',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70649','deDE','Normal',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70650','deDE','Frontales AOE vermeiden',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70651','deDE','NYI',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70652','deDE','Bist du sicher, dass das klappt? Es sollte besser das beste Wasser der Welt sein...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70653','deDE','Es scheint, als könnten Sie wirklich einen Schluck frisches Wasser gebrauchen.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70654','deDE','Meereshexes',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70655','deDE','Meereshexe',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70656','deDE','Mana pro Schaden',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70657','deDE','Schaden pro Mana',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70658','deDE','TransfiguTransmogrifikation...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70659','deDE','Kampfpositionierung DEAKTIVIEREN',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70660','deDE','Vorrangiges Ziel',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70661','deDE','Bot Ausrüstungsbank...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70662','deDE','Gegenstände einlagern...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70663','deDE','Gegenstände entnehmen...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70664','deDE','Bank ist leer',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70665','deDE','Vorherige Seite',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70666','deDE','Nächste Seite',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70667','deDE','Willst du wirklich so viel Geld ausgeben, damit der Gruftlord sich wieder bewegt?',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70668','deDE','Ich bezweifle, dass ihr in eurem jetzigen Zustand viel Schaden anrichten könnt, aber ich bin bereit, euch zu führen und euch dabei zu helfen, eure Kräfte wiederherzustellen.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70669','deDE','Gruftlords',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70670','deDE','Gruftlord',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70671','deDE','Reflektieren',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70672','deDE','Heuschrecken',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70673','deDE','Gesundheitsschwelle des Ziels heilen',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70674','deDE','Ich benötige ein Portal',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70675','deDE','Sturmwind',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70676','deDE','Eisenschmiede',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70680','deDE','Unterstadt',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70681','deDE','Donnerfels',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70682','deDE','Silbermond',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); diff --git a/sql/Bots/locales/esES/npc_text_locale.sql b/sql/Bots/locales/esES/npc_text_locale.sql new file mode 100644 index 000000000..8e0f8b2ea --- /dev/null +++ b/sql/Bots/locales/esES/npc_text_locale.sql @@ -0,0 +1,382 @@ +DELETE FROM `npc_text_locale` WHERE `Locale`='esES' AND `ID` BETWEEN '70000' AND '71000'; +INSERT INTO `npc_text_locale` (`ID`, `Locale`, `Text0_0`, `Text0_1`, `Text1_0`, `Text1_1`, `Text2_0`, `Text2_1`, `Text3_0`, `Text3_1`, `Text4_0`, `Text4_1`, `Text5_0`, `Text5_1`, `Text6_0`, `Text6_1`, `Text7_0`, `Text7_1`) +VALUES +('70001','esES','Vivo solo para servir a mi dueño.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70002','esES','¿Necesitas algo?',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70003','esES','Mortales... normalmente mato a miserables como tú a la vista.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70004','esES','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70005','esES','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70006','esES','¿Estás sorprendido, mortal? Como nathrezim menor, tengo que recurrir a buscar aliados. Parece que podrías divertirme al menos.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70007','esES','¿Qué pasa ahora, mortal?',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70008','esES','¿Puedes dejarme en paz? ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70009','esES','¿Ahora que?',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70101','esES','|cffff3300Blademaster|r$b|cffdd6600-=Warcraft III tribute=-|r$B$B"An elite swordsman, former member of Burning Blade clan, now an elite fighter within the Horde".$B$BMain attribute: Agility.$B$BNetherwalk (Windwalk). Allows Blademaster to become invisible, and move faster for a set amount of time. When the Blademaster attacks a unit to break invisibility, he will deal bonus damage.$B$BMirror Image. Confuses the enemy by creating illusions of the Blademaster and dispelling all magic from the Blademaster.$B$BCritical Strike (passive). Gives a 15% chance to deal critical x2(x3,x4) times normal damage on his attacks.$B$BBladestorm (NIY). Grants immunity to magic and deals damage to all surrounding enemies.$B$B',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70102','esES','|cff9900ccObsidian Destroyer|r$b|cffdd6600-=Warcraft III tribute=-|r$B$B"An obsidian winged monstrocity with insatiable hunger for magic".$B$BHigh armor, very high resistances, partially immune to magic, loses mana over time and doesnt benefit from passive mana regeneration effects, mail/plate armor, dual-wielding wands, deals spellshadow damage, no physical attack, cannot attack enemies not in front while moving, spell power bonus: 50% attack power + 200% intellect + wands damage.$B$BDevour Magic. Dispels up to 2 magic effects from enemies, up to 2 magic effects and up to 2 curses from allies and damaging summoned units in 20 yards area. Every dispelled effect restores 20% mana and 5% health, 7 seconds cooldown.$B$BShadow Blast. Empowered attack that deals increased splash damage.$B$BDrain Mana. Drains all mana (limited by casters mana pool) from a random friendly unit.$B$BReplenish Mana. Energizes surrounding party and raid members within 25 yards for 2% of their maximum mana nullifying casters mana, affects up to 10 targets, 3 seconds cooldown.$B$BRegenerating Aura. Heals surrounding party and raid members within 25 yards for 3% of their maximum health nullifying casters mana, affects up to 10 targets, 3 seconds cooldown.$B$BShadow Armor (passive). Restores mana equal to a percentage of damage taken.$B$B',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70103','esES','|cff0000ddArchmage|r$b|cffdd6600-=Warcraft III tribute=-|r$B$BSpell damage taken reduced by 35%, partially immune to control effects, cloth armor, deals spellsfire/spellfrost damage, no physical attack, spell power bonus: 100% intellect. Main attribute: Intellect.$B$BBlizzard. Your typical blizzard, just a little more powerful, 6 seconds cooldown.$B$BSummon Water Elemental. Summons a water elemental to attack archmage enemies for 1 min, 20 seconds cooldown.$B$BBrilliance Aura. Increases maximum mana by 10% and greatly increases mana regeneration of party and raid members within 40 yards.$B$BMass Teleport. NIY.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70104','esES','|cff9900ccDreadlord|r$b|cffdd6600-=Warcraft III tribute=-|r$B$B"Incredibly powerful demon who wields power of darkness and mental domination".$B$BHigh armor, high resistances, partially immune to control effects, damage taken speeds up spells recharge, plate armor, deals melee/spellshadow damage, bonus damage against incapacitated targets, spell power bonus: 200% strength. Main attribute: Strength.$B$BCarrion Swarm. Sends a horde of bats combined with chaotic magic to damage enemies in a very large frontal cone, cannot crit, 10 seconds cooldown.$B$BSleep. Puts the enemy target to sleep for 60 seconds and allows next physical attack on that target to bypass armor, direct damage caused will awaken the target, 6 seconds cooldown.$B$BVampiric Aura. Increases physical critical damage by 5% and heals party and raid members within 40 yards for a percentage (100% for Dreadlord and 25% for everyone else) of damage done by melee physical attacks and Carrion Swarm, no threat.$B$BSummon Infernal Servant. Calls an infernal down from the sky dealing damage and stunning enemy units, infernal is very resistant to magic and lasts 180 seconds, 180 seconds cooldown.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70105','esES','|cff0000ddSpell Breaker|r$b|cffdd6600-=Warcraft III tribute=-|r$B$B"An elven warrior trained to disrupt and contort magical energies".$B$BSpell damage taken reduced by 75%, partially immune to control effects, armor penalty -30%, mail/plate armor, deals melee/arcane damage, spell power bonus: 200% strength. Main attribute: Strength.$B$BSteal Magic (Spellsteal). Steals a benefical spell from an enemy and applies it to a nearby ally or removes a negative spell from an ally and applies it to a nearby enemy, affects magic and curse effects, 3 seconds cooldown.$B$BFeedback (passive). Successful melee attacks burn target mana equal to damage caused (increased by spellpower) dealing arcane damage. If target is drained, Spell Breaker melee attacks will do triple damage with increased critical strike chance',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70106','esES','|cff9900ccDark Ranger|r$b|cffdd6600-=Warcraft III tribute=-|r$B$B"A former ranger of Quel thalas forcibly raised from the dead".$B$BSpell damage taken reduced by 35%, undead, partially immune to control effects, leather/cloth armor, deals physical/spellshadow damage, stick to shadows and generates no threat, spell power bonus: 50% intellect. Main attribute: Agility.$B$BSilence. Silences an enemy and up to 4 nearby targets for 8 seconds, 15 seconds cooldown.$B$BBlack Arrow. Fires a cursed arrow dealing 150% weapon damage and additional spellshadow damage over time. If affected target dies from Dark Ranger damage, Dark Minion will spawn from the corpse (maximum 5 Minions, 80 seconds duration, only works on humanoids, beasts and dragonkin). Deals five times more damage if target is under 20% health.$B$BDrain Life. Drains health from an enemy every second for 5 seconds, healing Dark Ranger for 200% of the drained amount.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70201','esES','Siempre hay tipos dispuestos a matar por dinero.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70202','esES','Estos son los bots que están disponibles en este momento: ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70203','esES','Estos son los bots que están disponibles en este momento: ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70204','esES','Parece que no hay nadie disponible en este momento, vuelve más tarde.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70300','esES','¡Muere!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70301','esES','Resucitandote',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70302','esES','Resucitando ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70303','esES','tu bot',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70304','esES',' bot',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70305','esES','Todavía no puedo crear agua',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70306','esES','Todavía no puedo crear comida',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70307','esES','No puedo hacerlo ahora',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70308','esES','Aquí tienes...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70309','esES','Desactivado',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70310','esES','No está listo todavía',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70311','esES','Tipo de objeto no válido',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70312','esES','Fallido',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70313','esES','Vale',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70314','esES','No estoy en ninguna forma',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70315','esES','No tengo piedra de salud',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70316','esES','¡Aún no puedo crear piedras de salud!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70317','esES','¡WTF, no tengo forzar cerraduras!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70318','esES','Mi nivel de habilidad no es alto',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70319','esES','Cambiando mi talento a ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70320','esES','Armas',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70321','esES','Furia',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70322','esES','Protección',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70323','esES','Retribución',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70324','esES','Maestro de bestias',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70325','esES','Punteria',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70326','esES','Supervivencia',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70327','esES','Asesinato',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70328','esES','Combate',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70329','esES','Sutileza',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70330','esES','Disciplina',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70331','esES','Sagrado',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70332','esES','Sombra',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70333','esES','Sangre',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70334','esES','Escarcha',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70335','esES','Profano',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70336','esES','Elemental',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70337','esES','Mejora',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70338','esES','Restauración',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70339','esES','Arcano',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70340','esES','Fuego',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70341','esES','Aflición',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70342','esES','Demologia',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70343','esES','Destrucción',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70344','esES','Equilibrio',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70345','esES','Combate feral',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70346','esES','Unknown',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70347','esES','Vete, debilucho',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70348','esES',' no está convencido',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70349','esES','No voy a perder el tiempo en nada',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70350','esES','NIY',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70351','esES','NIY',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70352','esES','NIY',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70353','esES','Estoy listo',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70354','esES','Vete, sirvo a mi dueño ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70355','esES','unknown',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70356','esES',' en ti!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70357','esES',' en mí mismo!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70358','esES',' en ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70359','esES',' usado!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70360','esES','bot tank',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70361','esES','clase',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70362','esES','jugador',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70363','esES','dueño',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70364','esES','ninguno',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70365','esES','Rango',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70366','esES','talento',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70367','esES','pasivo',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70368','esES','oculto',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70369','esES','conocido',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70370','esES','habilidad',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70371','esES','str',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70372','esES','agi',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70373','esES','sta',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70374','esES','int',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70375','esES','spi',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70376','esES','unk stat',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70377','esES','total',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70378','esES','Melee AP',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70379','esES','Ranged AP',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70380','esES','armadura',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70381','esES','crit',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70382','esES','defensa',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70383','esES','miss',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70384','esES','evasión',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70385','esES','parry',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70386','esES','bloqueo',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70387','esES','valor de bloqueo',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70388','esES','Daño recibido cuerpo a cuerpo',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70389','esES','Daño recibido de hechizos',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70390','esES','Rango de daño mano principal',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70391','esES','Daño múltiple mano principal',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70392','esES','Tiempo de ataque mano principal',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70393','esES','Rango de daño de mano secundaria',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70394','esES','Daño múltiple mano secundaria',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70395','esES','Tiempo de ataque mano secundaria',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70396','esES','Rango de daño a distancia',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70397','esES','Daño a distancia múltiple',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70398','esES','Tiempo de ataque a distancia',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70399','esES','min',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70400','esES','max',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70401','esES','DPS',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70402','esES','base hp',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70403','esES','total hp',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70404','esES','base mana',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70405','esES','total mana',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70406','esES','mana actual',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70407','esES','poder de hechizos',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70408','esES','health regen_5 bonus',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70409','esES','mana regen_5 no cast',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70410','esES','mana regen_5 casting',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70411','esES','haste',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70412','esES','hit',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70413','esES','pericia',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70414','esES','penetración de armadura',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70415','esES','penetración de hechizos',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70416','esES','%',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70417','esES','sagrado',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70418','esES','fuego',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70419','esES','naturaleza',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70420','esES','escarcha',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70421','esES','sombra',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70422','esES','arcano',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70423','esES','Resistencia',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70424','esES','Estados de comando',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70425','esES','Seguir',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70426','esES','Ataque',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70427','esES','Quédate',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70428','esES','Reiniciar',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70429','esES','Parar por completo',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70430','esES','Distancia de seguimiento',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70431','esES','Especificaciones',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70432','esES','Rol principal del bot',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70433','esES','Rol de recoleción del bot',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70434','esES','Muertes PvP',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70435','esES','jugadores',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70436','esES','Murió ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70437','esES',' veces',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70438','esES','%s (bot) se calma',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70439','esES','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70440','esES','¿Estás seguro de que quieres arriesgarte a dibujar ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70441','esES',' atención?',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70442','esES','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70443','esES','Quieres atraer ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70444','esES','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70445','esES','Quieres contratar a ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70446','esES','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70447','esES','Administrar equipamiento...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70448','esES','Administrar rol...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70449','esES','Administrar formación...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70450','esES','Administrar habilidades...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70451','esES','Administrar talentos...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70452','esES','Dar consumibles...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70453','esES','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70454','esES','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70455','esES','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70456','esES','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70457','esES','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70458','esES','¡Sigueme!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70459','esES','¡Mantén tu posición!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70460','esES','¡Quédate aquí, no hagas nada!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70461','esES','Necesito comida',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70462','esES','Necesito agua',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70463','esES','Necesito una mesa de refrigerios',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70464','esES','Ayúdame a abrir esta cerradura',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70465','esES','Necesito una piedra de salud',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70466','esES','Necesito un pozo de alma',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70467','esES','Necesito que actualices tus venenos',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70468','esES','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70469','esES','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70470','esES','Necesito que actualices tus encantamientos',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70471','esES','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70472','esES','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70473','esES','Necesito que te quites el cambio de forma',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70474','esES','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70475','esES','Despedir',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70476','esES','Despedir a ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70477','esES','Puede que te arrepientas...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70478','esES','!Tranquilizaté!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70479','esES','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70480','esES','Nada',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70481','esES','Distancia',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70482','esES','ATRÁS',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70483','esES','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70484','esES','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70485','esES','Aleatoria (Astucia)',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70486','esES','Aleatoria (Ferocidad)',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70487','esES','Aleatoria (Tenacidad)',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70488','esES','Muéstrame tu inventario',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70489','esES','Auto-equipar',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70490','esES','Mano Principal',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70491','esES','Mano Secundaria',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70492','esES','A distancia',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70493','esES','Reliquia',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70494','esES','Cabeza',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70495','esES','Hombros',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70496','esES','Pecho',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70497','esES','Cintura',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70498','esES','Piernas',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70499','esES','Pies',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70500','esES','Brazales',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70501','esES','Guantes',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70502','esES','Capa',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70503','esES','Camisa',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70504','esES','Anillo1...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70505','esES','Anillo2...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70506','esES','Abalorio1',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70507','esES','Abalorio2',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70508','esES','Collar',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70509','esES','Desequipar todo',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70510','esES','Actualizar equipamiento (Visual)',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70511','esES','Solo visual',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70512','esES','Equipado',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70513','esES','nada',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70514','esES','Usa tu equipamiento antiguo',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70515','esES','Desequipar',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70516','esES','Mmmm... no tengo nada que darte',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70517','esES','Recolectar material',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70518','esES','Estado de las habilidades',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70519','esES','Administrar habilidades disponibles',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70520','esES','Usar ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70521','esES','Actualizar',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70522','esES','Daño',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70523','esES','Control',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70524','esES','Healer',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70525','esES','Otros',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70526','esES',' hace un ruido chirriante y comienza a seguir ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70527','esES','%s no se unira a ti hasta que su dueño no lo despida.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70528','esES','%s no se unira a ti hasta que seas nivel 60',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70529','esES','%s no se unira a ti hasta que seas nivel 55',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70530','esES','%s no se unira a ti hasta que seas nivel 40',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70531','esES','%s no se unira a ti hasta que seas nivel 20',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70532','esES','Superas el número máximo de bots (%u)',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70533','esES','No tienes suficiente dinero',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70534','esES','¡No puedes tener más bots de esa clase! %u de %u ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70535','esES','¡No se puede reiniciar el equipamiento en el slot %u (%s)! ¡No se puede despedir el bot!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70536','esES','actual',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70537','esES','Distancia de ataque',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70538','esES','Ataques de corto alcance',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70539','esES','Ataques de largo alcance',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70540','esES','Exacto',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70541','esES','Eliminar Buff',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70542','esES','Fija tu tipo de poder',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70543','esES','¡No se puede desequipar a %s por alguna razón! Enviando por correo',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70544','esES','Tank',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70545','esES','A distancia',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70546','esES','Mineria',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70547','esES','Herbolistaeria',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70548','esES','Desollar',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70549','esES','Ingeniería',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70550','esES','El contrato del bot expiró',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70551','esES','Los NPCBot están deshabilitado actualmente.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70552','esES','%s No se unira a ti, ya tiene un dueño: %s',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70553','esES','%s no puede unirse mientras estás a punto de teletransportarte',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70554','esES','Aspecto',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70555','esES','de Mono',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70556','esES','de Halcón',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70557','esES','de Guepardo',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70558','esES','de Víbora',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70559','esES','de la Bestia',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70560','esES','de la Manada',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70561','esES','de lo Salvaje',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70562','esES','de Dracohalcón',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70563','esES','Sin Aspecto',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70564','esES','Aura',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70565','esES','de Devoción',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70566','esES','de Concentración',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70567','esES','de Resistencia al fuego',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70568','esES','de Resistencia a la escarcha',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70569','esES','de Resistencia a las sombras',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70570','esES','de Reprensión',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70571','esES','de Cruzado',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70572','esES','Sin Aura',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70573','esES','Veneno entorpecedor',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70574','esES','Veneno instantáneo',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70575','esES','Veneno mortal',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70576','esES','Veneno hiriente',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70577','esES','Veneno de aturdimiento mental',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70578','esES','Veneno anestésico',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70579','esES','Nada',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70580','esES','Lengua de fuego',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70581','esES','Estigma de escarcha',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70582','esES','Viento furioso',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70583','esES','Vida terrestre',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70584','esES','Necesito tus servicios',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70585','esES','Tienes demasiados bots',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70586','esES','Quieres contratar a ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70587','esES',' está ocupado en este momento, vuelve a intentarlo más tarde.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70588','esES','Un placer hacer negocios contigo',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70589','esES','Guerreros',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70590','esES','Paladines',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70591','esES','Magos',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70592','esES','Sacerdotes',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70593','esES','Brujos',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70594','esES','Druidas',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70595','esES','Caballeros de la Muerte',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70596','esES','Picaros',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70597','esES','Chamanes',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70598','esES','Cazadores',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70599','esES','Blademasters',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70600','esES','Destroyers',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70601','esES','Archmagi',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70602','esES','Dreadlords',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70603','esES','Spell Breakers',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70604','esES','Dark Rangers',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70605','esES','Guerrero',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70606','esES','Paladin',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70607','esES','Mago',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70608','esES','Sacerdote',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70609','esES','Brujo',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70610','esES','Druida',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70611','esES','Caballero de la Muerte',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70612','esES','Picaro',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70613','esES','Chaman',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70614','esES','Cazador',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70615','esES','Blademaster',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70616','esES','Destroyer',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70617','esES','Archmage',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70618','esES','Dreadlord',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70619','esES','Spell Breaker',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70620','esES','Dark Ranger',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70621','esES','Hombre',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70622','esES','Mujer',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70623','esES','Humano',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70624','esES','Orco',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70625','esES','Enano',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70626','esES','Elfo de la noche',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70627','esES','No-muerto',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70628','esES','Tauren',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70629','esES','Gnomo',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70630','esES','Troll',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70631','esES','Elfo de sangre',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70632','esES','Draenei',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70633','esES','Unknown',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70634','esES','Saquear',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70635','esES','|cff9d9d9dBasura|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70636','esES','|cffffffffComún|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70637','esES','|cff1eff00Poco común|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70638','esES','|cff0070ddRaro|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70639','esES','|cffa335eeÉpico|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70640','esES','|cffff8000Legendario|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70641','esES','Elige un comprtamineto',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70642','esES','Retrasa el ataque a',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70643','esES','Retrasa la sanación a',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70644','esES','s',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70645','esES','Off-Tank',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70646','esES','Necromancers',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70647','esES','Necromancer',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70648','esES','Ángulo de ataque',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70649','esES','Normal',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70650','esES','Evitar AOE frontal',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70652','esES','¿Estás seguro de que esto va a funcionar? Más vale que sea la mejor agua del mundo...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70653','esES','Parece que realmente te vendría bien un trago de agua fresca.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70654','esES','Brujas del mar',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70655','esES','Bruja de mar',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70656','esES','Maná por daño',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70657','esES','Daño por maná',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70658','esES','Transfiguración...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70659','esES','DESACTIVAR el posicionamiento de combate',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70660','esES','Objetivo prioritario',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); diff --git a/sql/Bots/locales/esMX/npc_text_locale.sql b/sql/Bots/locales/esMX/npc_text_locale.sql new file mode 100644 index 000000000..d71d85823 --- /dev/null +++ b/sql/Bots/locales/esMX/npc_text_locale.sql @@ -0,0 +1,382 @@ +DELETE FROM `npc_text_locale` WHERE `Locale`='esMX' AND `ID` BETWEEN '70000' AND '71000'; +INSERT INTO `npc_text_locale` (`ID`, `Locale`, `Text0_0`, `Text0_1`, `Text1_0`, `Text1_1`, `Text2_0`, `Text2_1`, `Text3_0`, `Text3_1`, `Text4_0`, `Text4_1`, `Text5_0`, `Text5_1`, `Text6_0`, `Text6_1`, `Text7_0`, `Text7_1`) +VALUES +('70001','esMX','Vivo solo para servir a mi dueño.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70002','esMX','¿Necesitas algo?',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70003','esMX','Mortales... normalmente mato a miserables como tú a la vista.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70004','esMX','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70005','esMX','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70006','esMX','¿Estás sorprendido, mortal? Como nathrezim menor, tengo que recurrir a buscar aliados. Parece que podrías divertirme al menos.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70007','esMX','¿Qué pasa ahora, mortal?',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70008','esMX','¿Puedes dejarme en paz? ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70009','esMX','¿Ahora que?',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70101','esMX','|cffff3300Blademaster|r$b|cffdd6600-=Warcraft III tribute=-|r$B$B"An elite swordsman, former member of Burning Blade clan, now an elite fighter within the Horde".$B$BMain attribute: Agility.$B$BNetherwalk (Windwalk). Allows Blademaster to become invisible, and move faster for a set amount of time. When the Blademaster attacks a unit to break invisibility, he will deal bonus damage.$B$BMirror Image. Confuses the enemy by creating illusions of the Blademaster and dispelling all magic from the Blademaster.$B$BCritical Strike (passive). Gives a 15% chance to deal critical x2(x3,x4) times normal damage on his attacks.$B$BBladestorm (NIY). Grants immunity to magic and deals damage to all surrounding enemies.$B$B',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70102','esMX','|cff9900ccObsidian Destroyer|r$b|cffdd6600-=Warcraft III tribute=-|r$B$B"An obsidian winged monstrocity with insatiable hunger for magic".$B$BHigh armor, very high resistances, partially immune to magic, loses mana over time and doesnt benefit from passive mana regeneration effects, mail/plate armor, dual-wielding wands, deals spellshadow damage, no physical attack, cannot attack enemies not in front while moving, spell power bonus: 50% attack power + 200% intellect + wands damage.$B$BDevour Magic. Dispels up to 2 magic effects from enemies, up to 2 magic effects and up to 2 curses from allies and damaging summoned units in 20 yards area. Every dispelled effect restores 20% mana and 5% health, 7 seconds cooldown.$B$BShadow Blast. Empowered attack that deals increased splash damage.$B$BDrain Mana. Drains all mana (limited by casters mana pool) from a random friendly unit.$B$BReplenish Mana. Energizes surrounding party and raid members within 25 yards for 2% of their maximum mana nullifying casters mana, affects up to 10 targets, 3 seconds cooldown.$B$BRegenerating Aura. Heals surrounding party and raid members within 25 yards for 3% of their maximum health nullifying casters mana, affects up to 10 targets, 3 seconds cooldown.$B$BShadow Armor (passive). Restores mana equal to a percentage of damage taken.$B$B',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70103','esMX','|cff0000ddArchmage|r$b|cffdd6600-=Warcraft III tribute=-|r$B$BSpell damage taken reduced by 35%, partially immune to control effects, cloth armor, deals spellsfire/spellfrost damage, no physical attack, spell power bonus: 100% intellect. Main attribute: Intellect.$B$BBlizzard. Your typical blizzard, just a little more powerful, 6 seconds cooldown.$B$BSummon Water Elemental. Summons a water elemental to attack archmage enemies for 1 min, 20 seconds cooldown.$B$BBrilliance Aura. Increases maximum mana by 10% and greatly increases mana regeneration of party and raid members within 40 yards.$B$BMass Teleport. NIY.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70104','esMX','|cff9900ccDreadlord|r$b|cffdd6600-=Warcraft III tribute=-|r$B$B"Incredibly powerful demon who wields power of darkness and mental domination".$B$BHigh armor, high resistances, partially immune to control effects, damage taken speeds up spells recharge, plate armor, deals melee/spellshadow damage, bonus damage against incapacitated targets, spell power bonus: 200% strength. Main attribute: Strength.$B$BCarrion Swarm. Sends a horde of bats combined with chaotic magic to damage enemies in a very large frontal cone, cannot crit, 10 seconds cooldown.$B$BSleep. Puts the enemy target to sleep for 60 seconds and allows next physical attack on that target to bypass armor, direct damage caused will awaken the target, 6 seconds cooldown.$B$BVampiric Aura. Increases physical critical damage by 5% and heals party and raid members within 40 yards for a percentage (100% for Dreadlord and 25% for everyone else) of damage done by melee physical attacks and Carrion Swarm, no threat.$B$BSummon Infernal Servant. Calls an infernal down from the sky dealing damage and stunning enemy units, infernal is very resistant to magic and lasts 180 seconds, 180 seconds cooldown.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70105','esMX','|cff0000ddSpell Breaker|r$b|cffdd6600-=Warcraft III tribute=-|r$B$B"An elven warrior trained to disrupt and contort magical energies".$B$BSpell damage taken reduced by 75%, partially immune to control effects, armor penalty -30%, mail/plate armor, deals melee/arcane damage, spell power bonus: 200% strength. Main attribute: Strength.$B$BSteal Magic (Spellsteal). Steals a benefical spell from an enemy and applies it to a nearby ally or removes a negative spell from an ally and applies it to a nearby enemy, affects magic and curse effects, 3 seconds cooldown.$B$BFeedback (passive). Successful melee attacks burn target mana equal to damage caused (increased by spellpower) dealing arcane damage. If target is drained, Spell Breaker melee attacks will do triple damage with increased critical strike chance',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70106','esMX','|cff9900ccDark Ranger|r$b|cffdd6600-=Warcraft III tribute=-|r$B$B"A former ranger of Quel thalas forcibly raised from the dead".$B$BSpell damage taken reduced by 35%, undead, partially immune to control effects, leather/cloth armor, deals physical/spellshadow damage, stick to shadows and generates no threat, spell power bonus: 50% intellect. Main attribute: Agility.$B$BSilence. Silences an enemy and up to 4 nearby targets for 8 seconds, 15 seconds cooldown.$B$BBlack Arrow. Fires a cursed arrow dealing 150% weapon damage and additional spellshadow damage over time. If affected target dies from Dark Ranger damage, Dark Minion will spawn from the corpse (maximum 5 Minions, 80 seconds duration, only works on humanoids, beasts and dragonkin). Deals five times more damage if target is under 20% health.$B$BDrain Life. Drains health from an enemy every second for 5 seconds, healing Dark Ranger for 200% of the drained amount.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70201','esMX','Siempre hay tipos dispuestos a matar por dinero.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70202','esMX','Estos son los bots que están disponibles en este momento: ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70203','esMX','Estos son los bots que están disponibles en este momento: ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70204','esMX','Parece que no hay nadie disponible en este momento, vuelve más tarde.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70300','esMX','¡Muere!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70301','esMX','Resucitandote',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70302','esMX','Resucitando ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70303','esMX','tu bot',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70304','esMX',' bot',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70305','esMX','Todavía no puedo crear agua',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70306','esMX','Todavía no puedo crear comida',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70307','esMX','No puedo hacerlo ahora',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70308','esMX','Aquí tienes...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70309','esMX','Desactivado',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70310','esMX','No está listo todavía',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70311','esMX','Tipo de objeto no válido',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70312','esMX','Fallido',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70313','esMX','Vale',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70314','esMX','No estoy en ninguna forma',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70315','esMX','No tengo piedra de salud',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70316','esMX','¡Aún no puedo crear piedras de salud!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70317','esMX','¡WTF, no tengo forzar cerraduras!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70318','esMX','Mi nivel de habilidad no es alto',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70319','esMX','Cambiando mi talento a ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70320','esMX','Armas',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70321','esMX','Furia',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70322','esMX','Protección',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70323','esMX','Retribución',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70324','esMX','Maestro de bestias',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70325','esMX','Punteria',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70326','esMX','Supervivencia',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70327','esMX','Asesinato',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70328','esMX','Combate',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70329','esMX','Sutileza',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70330','esMX','Disciplina',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70331','esMX','Sagrado',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70332','esMX','Sombra',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70333','esMX','Sangre',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70334','esMX','Escarcha',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70335','esMX','Profano',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70336','esMX','Elemental',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70337','esMX','Mejora',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70338','esMX','Restauración',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70339','esMX','Arcano',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70340','esMX','Fuego',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70341','esMX','Aflición',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70342','esMX','Demologia',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70343','esMX','Destrucción',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70344','esMX','Equilibrio',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70345','esMX','Combate feral',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70346','esMX','Unknown',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70347','esMX','Vete, debilucho',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70348','esMX',' no está convencido',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70349','esMX','No voy a perder el tiempo en nada',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70350','esMX','NIY',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70351','esMX','NIY',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70352','esMX','NIY',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70353','esMX','Estoy listo',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70354','esMX','Vete, sirvo a mi dueño ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70355','esMX','unknown',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70356','esMX',' en ti!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70357','esMX',' en mí mismo!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70358','esMX',' en ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70359','esMX',' usado!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70360','esMX','bot tank',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70361','esMX','clase',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70362','esMX','jugador',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70363','esMX','dueño',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70364','esMX','ninguno',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70365','esMX','Rango',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70366','esMX','talento',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70367','esMX','pasivo',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70368','esMX','oculto',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70369','esMX','conocido',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70370','esMX','habilidad',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70371','esMX','str',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70372','esMX','agi',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70373','esMX','sta',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70374','esMX','int',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70375','esMX','spi',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70376','esMX','unk stat',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70377','esMX','total',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70378','esMX','Melee AP',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70379','esMX','Ranged AP',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70380','esMX','armadura',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70381','esMX','crit',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70382','esMX','defensa',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70383','esMX','miss',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70384','esMX','evasión',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70385','esMX','parry',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70386','esMX','bloqueo',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70387','esMX','valor de bloqueo',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70388','esMX','Daño recibido cuerpo a cuerpo',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70389','esMX','Daño recibido de hechizos',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70390','esMX','Rango de daño mano principal',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70391','esMX','Daño múltiple mano principal',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70392','esMX','Tiempo de ataque mano principal',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70393','esMX','Rango de daño de mano secundaria',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70394','esMX','Daño múltiple mano secundaria',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70395','esMX','Tiempo de ataque mano secundaria',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70396','esMX','Rango de daño a distancia',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70397','esMX','Daño a distancia múltiple',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70398','esMX','Tiempo de ataque a distancia',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70399','esMX','min',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70400','esMX','max',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70401','esMX','DPS',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70402','esMX','base hp',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70403','esMX','total hp',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70404','esMX','base mana',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70405','esMX','total mana',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70406','esMX','mana actual',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70407','esMX','poder de hechizos',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70408','esMX','health regen_5 bonus',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70409','esMX','mana regen_5 no cast',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70410','esMX','mana regen_5 casting',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70411','esMX','haste',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70412','esMX','hit',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70413','esMX','pericia',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70414','esMX','penetración de armadura',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70415','esMX','penetración de hechizos',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70416','esMX','%',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70417','esMX','sagrado',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70418','esMX','fuego',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70419','esMX','naturaleza',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70420','esMX','escarcha',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70421','esMX','sombra',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70422','esMX','arcano',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70423','esMX','Resistencia',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70424','esMX','Estados de comando',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70425','esMX','Seguir',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70426','esMX','Ataque',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70427','esMX','Quédate',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70428','esMX','Reiniciar',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70429','esMX','Parar por completo',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70430','esMX','Distancia de seguimiento',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70431','esMX','Especificaciones',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70432','esMX','Rol principal del bot',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70433','esMX','Rol de recoleción del bot',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70434','esMX','Muertes PvP',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70435','esMX','jugadores',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70436','esMX','Murió ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70437','esMX',' veces',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70438','esMX','%s (bot) se calma',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70439','esMX','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70440','esMX','¿Estás seguro de que quieres arriesgarte a dibujar ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70441','esMX',' atención?',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70442','esMX','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70443','esMX','Quieres atraer ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70444','esMX','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70445','esMX','Quieres contratar a ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70446','esMX','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70447','esMX','Administrar equipamiento...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70448','esMX','Administrar rol...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70449','esMX','Administrar formación...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70450','esMX','Administrar habilidades...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70451','esMX','Administrar talentos...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70452','esMX','Dar consumibles...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70453','esMX','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70454','esMX','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70455','esMX','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70456','esMX','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70457','esMX','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70458','esMX','¡Sigueme!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70459','esMX','¡Mantén tu posición!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70460','esMX','¡Quédate aquí, no hagas nada!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70461','esMX','Necesito comida',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70462','esMX','Necesito agua',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70463','esMX','Necesito una mesa de refrigerios',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70464','esMX','Ayúdame a abrir esta cerradura',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70465','esMX','Necesito una piedra de salud',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70466','esMX','Necesito un pozo de alma',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70467','esMX','Necesito que actualices tus venenos',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70468','esMX','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70469','esMX','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70470','esMX','Necesito que actualices tus encantamientos',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70471','esMX','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70472','esMX','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70473','esMX','Necesito que te quites el cambio de forma',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70474','esMX','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70475','esMX','Despedir',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70476','esMX','Despedir a ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70477','esMX','Puede que te arrepientas...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70478','esMX','!Tranquilizaté!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70479','esMX','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70480','esMX','Nada',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70481','esMX','Distancia',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70482','esMX','ATRÁS',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70483','esMX','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70484','esMX','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70485','esMX','Aleatoria (Astucia)',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70486','esMX','Aleatoria (Ferocidad)',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70487','esMX','Aleatoria (Tenacidad)',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70488','esMX','Muéstrame tu inventario',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70489','esMX','Auto-equipar',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70490','esMX','Mano Principal',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70491','esMX','Mano Secundaria',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70492','esMX','A distancia',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70493','esMX','Reliquia',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70494','esMX','Cabeza',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70495','esMX','Hombros',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70496','esMX','Pecho',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70497','esMX','Cintura',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70498','esMX','Piernas',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70499','esMX','Pies',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70500','esMX','Brazales',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70501','esMX','Guantes',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70502','esMX','Capa',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70503','esMX','Camisa',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70504','esMX','Anillo1...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70505','esMX','Anillo2...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70506','esMX','Abalorio1',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70507','esMX','Abalorio2',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70508','esMX','Collar',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70509','esMX','Desequipar todo',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70510','esMX','Actualizar equipamiento (Visual)',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70511','esMX','Solo visual',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70512','esMX','Equipado',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70513','esMX','nada',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70514','esMX','Usa tu equipamiento antiguo',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70515','esMX','Desequipar',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70516','esMX','Mmmm... no tengo nada que darte',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70517','esMX','Recolectar material',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70518','esMX','Estado de las habilidades',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70519','esMX','Administrar habilidades disponibles',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70520','esMX','Usar ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70521','esMX','Actualizar',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70522','esMX','Daño',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70523','esMX','Control',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70524','esMX','Healer',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70525','esMX','Otros',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70526','esMX',' hace un ruido chirriante y comienza a seguir ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70527','esMX','%s no se unira a ti hasta que su dueño no lo despida.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70528','esMX','%s no se unira a ti hasta que seas nivel 60',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70529','esMX','%s no se unira a ti hasta que seas nivel 55',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70530','esMX','%s no se unira a ti hasta que seas nivel 40',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70531','esMX','%s no se unira a ti hasta que seas nivel 20',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70532','esMX','Superas el número máximo de bots (%u)',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70533','esMX','No tienes suficiente dinero',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70534','esMX','¡No puedes tener más bots de esa clase! %u de %u ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70535','esMX','¡No se puede reiniciar el equipamiento en el slot %u (%s)! ¡No se puede despedir el bot!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70536','esMX','actual',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70537','esMX','Distancia de ataque',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70538','esMX','Ataques de corto alcance',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70539','esMX','Ataques de largo alcance',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70540','esMX','Exacto',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70541','esMX','Eliminar Buff',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70542','esMX','Fija tu tipo de poder',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70543','esMX','¡No se puede desequipar a %s por alguna razón! Enviando por correo',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70544','esMX','Tank',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70545','esMX','A distancia',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70546','esMX','Mineria',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70547','esMX','Herbolistaeria',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70548','esMX','Desollar',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70549','esMX','Ingeniería',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70550','esMX','El contrato del bot expiró',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70551','esMX','Los NPCBot están deshabilitado actualmente.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70552','esMX','%s No se unira a ti, ya tiene un dueño: %s',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70553','esMX','%s no puede unirse mientras estás a punto de teletransportarte',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70554','esMX','Aspecto',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70555','esMX','de Mono',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70556','esMX','de Halcón',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70557','esMX','de Guepardo',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70558','esMX','de Víbora',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70559','esMX','de la Bestia',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70560','esMX','de la Manada',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70561','esMX','de lo Salvaje',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70562','esMX','de Dracohalcón',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70563','esMX','Sin Aspecto',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70564','esMX','Aura',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70565','esMX','de Devoción',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70566','esMX','de Concentración',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70567','esMX','de Resistencia al fuego',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70568','esMX','de Resistencia a la escarcha',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70569','esMX','de Resistencia a las sombras',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70570','esMX','de Reprensión',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70571','esMX','de Cruzado',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70572','esMX','Sin Aura',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70573','esMX','Veneno entorpecedor',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70574','esMX','Veneno instantáneo',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70575','esMX','Veneno mortal',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70576','esMX','Veneno hiriente',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70577','esMX','Veneno de aturdimiento mental',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70578','esMX','Veneno anestésico',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70579','esMX','Nada',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70580','esMX','Lengua de fuego',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70581','esMX','Estigma de escarcha',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70582','esMX','Viento furioso',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70583','esMX','Vida terrestre',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70584','esMX','Necesito tus servicios',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70585','esMX','Tienes demasiados bots',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70586','esMX','Quieres contratar a ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70587','esMX',' está ocupado en este momento, vuelve a intentarlo más tarde.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70588','esMX','Un placer hacer negocios contigo',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70589','esMX','Guerreros',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70590','esMX','Paladines',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70591','esMX','Magos',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70592','esMX','Sacerdotes',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70593','esMX','Brujos',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70594','esMX','Druidas',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70595','esMX','Caballeros de la Muerte',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70596','esMX','Picaros',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70597','esMX','Chamanes',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70598','esMX','Cazadores',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70599','esMX','Blademasters',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70600','esMX','Destroyers',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70601','esMX','Archmagi',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70602','esMX','Dreadlords',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70603','esMX','Spell Breakers',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70604','esMX','Dark Rangers',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70605','esMX','Guerrero',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70606','esMX','Paladin',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70607','esMX','Mago',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70608','esMX','Sacerdote',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70609','esMX','Brujo',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70610','esMX','Druida',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70611','esMX','Caballero de la Muerte',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70612','esMX','Picaro',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70613','esMX','Chaman',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70614','esMX','Cazador',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70615','esMX','Blademaster',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70616','esMX','Destroyer',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70617','esMX','Archmage',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70618','esMX','Dreadlord',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70619','esMX','Spell Breaker',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70620','esMX','Dark Ranger',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70621','esMX','Hombre',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70622','esMX','Mujer',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70623','esMX','Humano',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70624','esMX','Orco',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70625','esMX','Enano',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70626','esMX','Elfo de la noche',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70627','esMX','No-muerto',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70628','esMX','Tauren',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70629','esMX','Gnomo',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70630','esMX','Troll',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70631','esMX','Elfo de sangre',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70632','esMX','Draenei',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70633','esMX','Unknown',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70634','esMX','Saquear',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70635','esMX','|cff9d9d9dBasura|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70636','esMX','|cffffffffComún|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70637','esMX','|cff1eff00Poco común|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70638','esMX','|cff0070ddRaro|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70639','esMX','|cffa335eeÉpico|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70640','esMX','|cffff8000Legendario|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70641','esMX','Elige un comprtamineto',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70642','esMX','Retrasa el ataque a',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70643','esMX','Retrasa la sanación a',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70644','esMX','s',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70645','esMX','Off-Tank',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70646','esMX','Necromancers',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70647','esMX','Necromancer',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70648','esMX','Ángulo de ataque',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70649','esMX','Normal',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70650','esMX','Evitar AOE frontal',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70652','esMX','¿Estás seguro de que esto va a funcionar? Más vale que sea la mejor agua del mundo...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70653','esMX','Parece que realmente te vendría bien un trago de agua fresca.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70654','esMX','Brujas del mar',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70655','esMX','Bruja de mar',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70656','esMX','Maná por daño',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70657','esMX','Daño por maná',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70658','esMX','Transfiguración...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70659','esMX','DESACTIVAR el posicionamiento de combate',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70660','esMX','Objetivo prioritario',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); diff --git a/sql/Bots/locales/ruRU/npc_text_locale.sql b/sql/Bots/locales/ruRU/npc_text_locale.sql new file mode 100644 index 000000000..015aca79b --- /dev/null +++ b/sql/Bots/locales/ruRU/npc_text_locale.sql @@ -0,0 +1,380 @@ +DELETE FROM `npc_text_locale` WHERE `Locale`='ruRU' AND `ID` BETWEEN '70000' AND '71000'; +INSERT INTO `npc_text_locale` (`ID`, `Locale`, `Text0_0`, `Text0_1`, `Text1_0`, `Text1_1`, `Text2_0`, `Text2_1`, `Text3_0`, `Text3_1`, `Text4_0`, `Text4_1`, `Text5_0`, `Text5_1`, `Text6_0`, `Text6_1`, `Text7_0`, `Text7_1`) +VALUES +('70001', 'ruRU', 'Я живу только для того, чтобы служить хозяину!', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70002', 'ruRU', 'Тебе что-то нужно?', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70003', 'ruRU', 'Смертные... обычно я убиваю тварей вроде тебя как только увижу!', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70004', 'ruRU', '<Перед вами, похоже, обсидиановый разрушитель. Этот, впрочем, выглядит иначе, поврежденный и поблекший, он не реагирует на ваше присутствие. Вам помнится Плеть когда-то давно использовала таких. Как, черт возьми, он оказался здесь? При дальнейшем осмотре вы замечаете щель на его спине.>', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70005', 'ruRU', '<Обсидиановый Разрушитель смотрит на вас и издает глубокий рычащий звук.>', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70006', 'ruRU', 'Ты удивлен, смертный? Как низший натрезим, я вынужден прибегать к поиску союзников. Ты выглядишь так, будто сможешь меня хотя бы развлечь.', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70007', 'ruRU', 'Ну что ещё, смертный?', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70008', 'ruRU', 'Ты можешь просто оставить меня в покое? <вздох>', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70009', 'ruRU', 'Что теперь?', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70010', 'ruRU', '<Вы видите истощенную предводительницу наг. Она выглядит усталой и слабой, и пытается не смотреть на вас.>$B$BНе нуж-жно с-с-слов, с-с-смертный...', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70011', 'ruRU', 'У меня опять что-то не так с-с причёс-ской? <Она расчесывает свои "волосы">$B... Нет, вс-сё в порядке. Так в чём же дело?', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70101', 'ruRU', '|cffff3300Мастер Клинка|r$b|cffdd6600-=отсылка к Warcraft III=-|r$B$B"Элитный мечник, бывший член клана Пылающего Клинка, ныне элитный воин ​​Орды".$B$BОсновная характеристика: Ловкость.$B$BПуть Пустоты (Прогулка с ветром). Позволяет Мастеру клинка становиться невидимым и двигаться быстрее в течение определенного времени. Если Мастер клинка атакует врага, выходя из невидимости, он наносит дополнительный урон.$B$BЗеркальное изображение. Сбивает противника с толку, создавая иллюзию Мастера клинка и рассеивая всю магию Мастера клинка.$B$BКритический удар (пассивный). Дает 15% шанс нанести критический урон в 2(3,4) раза больше обычного при атаках.$B$BВихрь клинков (NIY). Дает иммунитет к магии и наносит урон всем окружающим врагам.$B$B', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70102', 'ruRU', '|cff9900ccОбсидиановый Разрушитель|r$b|cffdd6600-=отсылка к Warcraft III=-|r$B$B"Крылатое чудовище из обсидиана, обладающее ненасытной жаждой магии".$B$BКрепкая броня, очень высокое сопротивление, частичный иммунитет к магии, постоянно теряет ману, пассивные эффекты регенерации маны для него бесполезны, кольчужная/латная броня, использует жезлы в обоих руках, наносит урон темной магией, нет физической атаки, не может атаковать во время перемещения, бонус к силе заклинаний: 50% силы атаки + 200% интеллекта + урон жезлов.$B$BПожирание магии. Снимает до 2 магических эффектов с врагов, до 2 магических эффектов и до 2 проклятий с союзников и наносит урон призванным юнитам в радиусе 20 м. Каждый развеянный эффект восстанавливает 20% маны и 5% здоровья, время восстановления 7 секунд.$B$BТеневой взрыв. Усиленная атака, наносящая повышенный урон по площади.$B$BВытягивание маны. Вытягивает всю ману (ограниченную запасом маны заклинателя) из случайного дружественного юнита.$B$BПополнить запасы маны. Восполняет манну окружающим участникав группы и рейда в радиусе 25 ярдов на 3% от их максимального запаса, сводя на нет ману заклинателя, время восстановления 3 секунды.$B$BАура восстановления. Исцеляет окружающих членов группы и рейда в радиусе 25 м на 3% от их максимального запаса здоровья, сводя на нет ману заклинателя, время восстановления 3 секунды.$B$BТемная броня (пассивная). Восстанавливает ману в размере процента от полученного урона.$B$B', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70103', 'ruRU', '|cff0000ddАрхимаг|r$b|cffdd6600-=отсылка к Warcraft III=-|r$B$B Получаемый урон от заклинаний уменьшен на 35%, частично невосприимчив к эффектам контроля, тканевая броня, наносит урон от магии огня/льда, нет физической атаки, бонус к силе заклинаний: 100% интеллекта. Основная характеристика: Интеллект.$B$BСнежная буря. Обычная снежная буря, только немного мощнее, время восстановления 6 секунд.$B$BПризыв элементаля воды. Призывает элементаля воды, который атакует врагов архимага. Время восстановления: 1 мин., 20 сек.$B$BАура великолепия. Увеличивает максимальный запас маны на 10% и значительно увеличивает регенерацию маны участников группы и рейда в радиусе 40 м.$B$BM Массовая телепортация. NIY.$B$B', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70104', 'ruRU', '|cff9900ccПовелитель Ужаса|r$b|cffdd6600-=отсылка к Warcraft III=-|r$B$B"Невероятно могущественный демон, владеющий силами тьмы и управления разумом".$B$BКрепкая броня, высокое сопротивление, частичная невосприимчивость к эффектам контроля, получаемый урон ускоряет перезарядку заклинаний, латная броня, наносит урон в ближнем бою а также урон от темной магии, дополнительный урон по целям выведенным из равновесия, бонус к силе заклинаний: 200% силы. Основная характеристика: Сила.$B$BТемная Стая. Посылает стаю летучих мышей наносящих урон от магии в конусе перед собой, не может нанести критический урон, время восстановления 10 секунд.$B$BСон. Погружает вражескую цель в сон на 60 секунд и позволяет при наненсения урона следующей физической атаке этой цели игнорировать ее броню, нанесенный прямой урон пробудит цель, время восстановления 6 секунд.$B$BАура вампиризма. Увеличивает физический критический урон на 5% и исцеляет членов группы и рейда в радиусе 40 м в процентном соотношении (100% для Повелителя ужаса и 25% для всех остальных) от урона, нанесенного физическими атаками в ближнем бою и Темной Стаей, без угрозы.$B$BПризыв Инфернала. Призывает инфернала с неба на 180 секунд, нанося урон и оглушая врагов, инфернал очень устойчив к магии, время восстановления 180 секунд.$B$B', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70105', 'ruRU', '|cff0000ddРазрушитель Заклинаний|r$b|cffdd6600-=отсылка к Warcraft III=-|r$B$B"Эльфийский воин, специально обученный разрушать и искажать магию".$B$BУрон, получаемый от заклинаний, уменьшен на 75%, частично невосприимчив к эффектам контроля, штраф за броню -30%, кольчужная/латная броня, наносит урон в ближнем бою и урон от тайной магии, бонус к силе заклинаний: 200% силы. Основная характеристика: Сила.$B$BПохищение Магии. Похищает полезное заклинание у врага и передаёт его ближайшему союзнику или снимает отрицательное заклинание с союзника на ближайшего врага, влияет на эффекты магии и проклятия, время восстановления 2 секунды.$B$BСожжение Маны (пассивная). Успешные атаки ближнего боя сжигают ману цели, равную нанесенному урону (увеличенному силой заклинаний), нанося урон от тайной магии. Если мана цели исчерпана, атаки ближнего боя Разрушителя Заклинаний будут наносить тройной урон с повышенным шансом критического удара. Если у цели нет маны, Разрушитель Заклинаний восполнит ману в количестве 25% от нанесенного урона.$B$B', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70106', 'ruRU', '|cff9900ccТемная Охотница|r$b|cffdd6600-=отсылка к Warcraft III=-|r$B$B"Бывшая охотница Кель\'Таласа, насильно возвращённая из мира мёртвых".$B$B Получаемый урон от заклинаний уменьшен на 35%, нежить, частично невосприимчива к эффектам контроля, кожаная/тканевая броня, наносит физический урон/урон от темной магии, придерживается теней и не генерирует угрозы, бонус к силе заклинаний: 50% интеллекта. Основное характеристика: Ловкость.$B$BБезмолвие. Заставляет врага и до 4 его ближайших друзей замолчать на 8 секунд, теряя возможность применять заклинания, время восстановления 15 секунд.$B$BЧерная стрела. Выпускает проклятую стрелу, наносящую 150% урона от оружия и дополнительный урон от темной магии каждые несколько секунд. Если цель умирает от урона Темной Охотницы, она превратится в Тёмного Приспешника (максимум 5 приспешников, продолжительность 80 секунд, срабатывает только на гуманоидах, зверях и драконах). Наносит пятикратный урон, если у цели меньше 20% здоровья.$B$Bпохищение Жизни. Высасывает здоровье врага каждую секунду в течение 5 секунд, исцеляя Темную Охотницу на 200% от похищенного количества.$B$B', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70107', 'ruRU', '|cff9900ccНекромант|r$b|cffdd6600-=отсылка к Warcraft III / Diablo II=-|r$B$BПолучаемый урон от заклинаний уменьшен на 20%, частично невосприимчив к эффектам контроля, тканевая броня, наносит урон от темной магии, нет физической атаки, бонус к силе заклинаний: интеллект 100%. Основная характеристика: Интеллект.$B$BВоскрешение Мертвых. Поднимает 2 скелета из трупа (максимум 6 скелетов, продолжительность 65 секунд, работает только с гуманоидами, зверями и драконами).$B$BНечестивое Бешенство. Увеличивает скорость атаки цели в ближнем бою на 75%, но постоянно истощает здоровье. Длится 45 секунд. Не может быть отменено. Разблокируется на 30 уровне.$B$BВзрыв Трупа. Заставляет труп взорваться, нанося урон в размере от 35% до 75% от максимального здоровья мертвого существа (зависит от уровня Некроманта) всем окружающим врагам. Этот урон не генерирует угрозы. Разблокируется на 40 уровне.$B$BУвечье. Снижает скорость передвижения цели, скорость атаки в ближнем бою и общую силу на 50% на 60 секунд. Разблокируется на уровне 50.$B$B', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70108', 'ruRU', '|cff0000ddМорская Ведьма|r$b|cffdd6600-=отсылка к Warcraft III=-|r$B$B"Грозная колдунья наг, часто ассоциирующаяся с приходом ужасных штормов".$B$BПолучаемый урон от заклинаний уменьшен на 30%, частично невосприимчив к эффектам контроля, тканевая броня, наносит физический урон/урон от магии льда, бонус к силе атаки: ловкость x2, бонус к силе заклинаний: 200% к интеллекту. Основная характеристика: Интеллект.$B$BРаздвоенная молния. Вызывает разветвлённую молнию, наносящую урон врагам. Поражает от 2 до всех целей (в зависимости от уровня Морской Ведьмы), оглушая их на 2 секунды. Этот урон не создает угрозы.$B$BЛедяные Стрелы. Наполняет стрелу магическим морозом для дополнительного урона, снижая скорость движения цели, скорость атаки и произнесения заклинаний на 30-70% (в зависимости от уровня Морской ведьмы).$B$BЩит Маны. Создает щит, который поглощает 100% входящего (не смягченного) урона, используя ману Морской Ведьмы. Эффект варьируется от 1 единицы урона за 10 единиц маны до 10 единиц урона за 1 единицу ману (в зависимости от уровня Морской Ведьмы).$B$BТорнадо. Вызывает яростный торнадо, который наносит урон и замедляет ближайших врагов, иногда полностью выводя их из строя. На открытом пространстве Торнадо со временем разрастается, увеличивая наносимый урон и область действия, но в закрытом помещении уменьшается и быстро рассеивается. Разблокируется на уровне 60.$B$BНага (пассивный эффект). Скорость плавания, урон и шанс уклонения значительно увеличиваются в воде.$B$B', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70201', 'ruRU', 'Всегда найдутся чуваки, готовые убить за деньги.', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70202', 'ruRU', 'Наёмники востребованы всегда. Вот кто доступен прямо сейчас.', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70203', 'ruRU', 'Наёмники востребованы всегда. Вот кто доступен прямо сейчас.', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70204', 'ruRU', 'Похоже сейчас никого нет, проверь позже.', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70300', 'ruRU', 'Умри!', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70301', 'ruRU', 'Воскрешаю тебя', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70302', 'ruRU', 'Воскрешаю ', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70303', 'ruRU', 'твой бот', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70304', 'ruRU', ' бот', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70305', 'ruRU', 'Я пока не могу применить заклинание создания воды', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70306', 'ruRU', 'Я пока не могу применить заклинание создания еды', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70307', 'ruRU', 'Я не могу сделать это сейчас', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70308', 'ruRU', 'Во-о-от...', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70309', 'ruRU', 'Отключено', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70310', 'ruRU', 'Ещё не готово', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70311', 'ruRU', 'Неверный тип объекта', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70312', 'ruRU', 'Не удалось', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70313', 'ruRU', 'Готово', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70314', 'ruRU', 'Я не изменил форму', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70315', 'ruRU', 'У меня нет камня здоровья', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70316', 'ruRU', 'Я пока не могу создавать камни здоровья!', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70317', 'ruRU', 'WTF у меня нет отмычек!', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70318', 'ruRU', 'Мой уровень навыка недостаточно высок', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70319', 'ruRU', 'Меняю специализацию на ', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70320', 'ruRU', 'Оружие', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70321', 'ruRU', 'Неистовство', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70322', 'ruRU', 'Защита', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70323', 'ruRU', 'Воздаяние', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70324', 'ruRU', 'Повелитель зверей', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70325', 'ruRU', 'Стрельба', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70326', 'ruRU', 'Выживание', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70327', 'ruRU', 'Ликвидация', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70328', 'ruRU', 'Бой', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70329', 'ruRU', 'Скрытность', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70330', 'ruRU', 'Послушание', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70331', 'ruRU', 'Свет', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70332', 'ruRU', 'Тьма', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70333', 'ruRU', 'Кровь', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70334', 'ruRU', 'Лёд', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70335', 'ruRU', 'Нечестивость', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70336', 'ruRU', 'Стихии', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70337', 'ruRU', 'Совершенствование', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70338', 'ruRU', 'Исцеление', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70339', 'ruRU', 'Тайная магия', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70340', 'ruRU', 'Огонь', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70341', 'ruRU', 'Колдовство', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70342', 'ruRU', 'Демонология', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70343', 'ruRU', 'Разрушение', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70344', 'ruRU', 'Баланс', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70345', 'ruRU', 'Сила зверя', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70346', 'ruRU', 'Неизвестно', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70347', 'ruRU', 'Проваливай, слабак', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70348', 'ruRU', ' не убеждён', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70349', 'ruRU', 'Я не собираюсь тратить свое время на всякую ерунду', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70353', 'ruRU', 'Я готов', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70354', 'ruRU', 'Уходи. Я служу своему хозяину ', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70355', 'ruRU', 'неизвестный', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70356', 'ruRU', ' на тебя!', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70357', 'ruRU', ' на себя!', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70358', 'ruRU', ' на ', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70359', 'ruRU', ' использовано!', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70360', 'ruRU', 'бот-танк', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70361', 'ruRU', 'класс', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70362', 'ruRU', 'игрок', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70363', 'ruRU', 'владелец', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70364', 'ruRU', 'никто', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70365', 'ruRU', 'Уровень', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70366', 'ruRU', 'талант', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70367', 'ruRU', 'пассивный', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70368', 'ruRU', 'скрытый', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70369', 'ruRU', 'изучен', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70370', 'ruRU', 'способность', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70371', 'ruRU', 'сила', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70372', 'ruRU', 'ловкость', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70373', 'ruRU', 'выносливость', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70374', 'ruRU', 'интеллект', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70375', 'ruRU', 'дух', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70376', 'ruRU', 'неизвестный стат', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70377', 'ruRU', 'всего', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70378', 'ruRU', 'Сила атаки ближний бой', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70379', 'ruRU', 'Сила атаки дальний бой', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70380', 'ruRU', 'броня', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70381', 'ruRU', 'крит', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70382', 'ruRU', 'защита', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70383', 'ruRU', 'промах', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70384', 'ruRU', 'уклонение', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70385', 'ruRU', 'парирование', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70386', 'ruRU', 'блок', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70387', 'ruRU', 'показатель блокирования', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70388', 'ruRU', 'Получаемый урон в ближнем бою', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70389', 'ruRU', 'Получаемый урон от заклинаний', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70390', 'ruRU', 'Разброс урона оружия в правой руке', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70391', 'ruRU', 'Множитель урона оружия в правой руке', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70392', 'ruRU', 'Скорость атаки оружием в правой руке', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70393', 'ruRU', 'Разброс урон оружия в левой руке', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70394', 'ruRU', 'Множитель урона оружия в левой руке', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70395', 'ruRU', 'Скорость атаки оружием в левой руке', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70396', 'ruRU', 'Разброс урона оружия дальнего боя', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70397', 'ruRU', 'Множитель урона оружия дальнего боя', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70398', 'ruRU', 'Скорость атаки оружием дальнего боя', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70399', 'ruRU', 'минимум', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70400', 'ruRU', 'максимум', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70402', 'ruRU', 'базовый уровень здоровья', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70403', 'ruRU', 'всего здоровья', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70404', 'ruRU', 'базовый уровень маны', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70405', 'ruRU', 'всего маны', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70406', 'ruRU', 'текущий запас маны', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70407', 'ruRU', 'сила заклинаний', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70408', 'ruRU', 'бонус регенерации здоровья_5', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70409', 'ruRU', 'регенерация маны_5 без использования заклинания', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70410', 'ruRU', 'регенерация маны_5 при использовании заклинания', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70411', 'ruRU', 'скорость', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70412', 'ruRU', 'меткость', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70413', 'ruRU', 'мастерство', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70414', 'ruRU', 'пробивание брони', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70415', 'ruRU', 'проникновение заклинаний', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70416', 'ruRU', 'проц.', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70417', 'ruRU', 'святлая магия', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70418', 'ruRU', 'магия огня', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70419', 'ruRU', 'силы природы', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70420', 'ruRU', 'магия льда', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70421', 'ruRU', 'темная магия', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70422', 'ruRU', 'тайная магия', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70423', 'ruRU', 'Сопротивление', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70424', 'ruRU', 'Состояния команд', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70425', 'ruRU', 'Следовать', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70426', 'ruRU', 'Атаковать', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70427', 'ruRU', 'Стоять', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70428', 'ruRU', 'Сброс', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70429', 'ruRU', 'Полная остановка', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70430', 'ruRU', 'Дистанция следования', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70431', 'ruRU', 'Специализация', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70432', 'ruRU', 'Маска ролей ботов (главная)', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70433', 'ruRU', 'Маска ролей ботов (сбор)', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70434', 'ruRU', 'PvP-убийства', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70435', 'ruRU', 'игроки', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70436', 'ruRU', 'Умер ', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70437', 'ruRU', ' раз', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70438', 'ruRU', '%s (бот) успокаивается', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70439', 'ruRU', '<Отладка>', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70440', 'ruRU', 'Вы уверены, что хотите рискнуть, привлекая внимание ', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70441', 'ruRU', '?', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70442', 'ruRU', '<Вставить монету>', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70443', 'ruRU', 'Вы хотите приманить ', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70444', 'ruRU', '<Попробовать сделать подношение>', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70445', 'ruRU', 'Вы хотите нанять ', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70446', 'ruRU', '<Нанять бота>', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70447', 'ruRU', 'Снаряжение...', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70448', 'ruRU', 'Роли...', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70449', 'ruRU', 'Построение...', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70450', 'ruRU', 'Способности...', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70451', 'ruRU', 'Специализация...', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70452', 'ruRU', 'Дать использовать...', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70453', 'ruRU', '<Создать группу>', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70454', 'ruRU', '<Создать группу (все боты)>', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70455', 'ruRU', '<Добавить в группу>', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70456', 'ruRU', '<Добавить всех ботов в группу>', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70457', 'ruRU', '<Удалить из группы>', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70458', 'ruRU', 'Следуй за мной', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70459', 'ruRU', 'Удерживай позицию', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70460', 'ruRU', 'Стой здесь и ничего не делай', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70461', 'ruRU', 'Мне нужна еда', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70462', 'ruRU', 'Мне нужна вода', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70463', 'ruRU', 'Мне нужен стол с едой', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70464', 'ruRU', 'Помоги мне взломать замок', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70465', 'ruRU', 'Мне нужен камень здоровья', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70466', 'ruRU', 'Мне нужен источник душ', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70467', 'ruRU', 'Мне нужно, чтобы ты обновил яды', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70468', 'ruRU', '<Выберите яд (правая рука)>', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70469', 'ruRU', '<Выберите яд (левая рука)>', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70470', 'ruRU', 'Мне нужно, чтобы ты обновил чары', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70471', 'ruRU', '<Выберите чары (правая рука)>', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70472', 'ruRU', '<Выберите чары (левая рука)>', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70473', 'ruRU', 'Мне нужно, чтобы ты вышел из формы', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70474', 'ruRU', '<Выбрать тип питомца>', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70475', 'ruRU', 'Свободен отсюда', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70476', 'ruRU', 'Вы действительно хотите уволить ', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70477', 'ruRU', 'Вы можете пожалеть об этом...', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70478', 'ruRU', 'Соберись, тряпка', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70479', 'ruRU', '<Рассмотреть существо>', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70480', 'ruRU', 'Ладно, не важно', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70481', 'ruRU', 'дист.', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70482', 'ruRU', 'НАЗАД', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70483', 'ruRU', '<Авто>', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70484', 'ruRU', '<Нет>', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70485', 'ruRU', 'Случайный (Хитрость)', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70486', 'ruRU', 'Случайный (Свирепость)', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70487', 'ruRU', 'Случайный (Упорство)', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70488', 'ruRU', 'Покажи мне свой инвентарь', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70489', 'ruRU', 'Автовыбор', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70490', 'ruRU', 'Правая рука', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70491', 'ruRU', 'Левая рука', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70492', 'ruRU', 'Дальний бой', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70493', 'ruRU', 'Реликвия', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70494', 'ruRU', 'Голова', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70495', 'ruRU', 'Плечи', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70496', 'ruRU', 'Грудь', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70497', 'ruRU', 'Пояс', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70498', 'ruRU', 'Ноги', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70499', 'ruRU', 'Ступни', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70500', 'ruRU', 'Запястья', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70501', 'ruRU', 'Кисти рук', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70502', 'ruRU', 'Спина', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70503', 'ruRU', 'Рубашка', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70504', 'ruRU', 'Палец1', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70505', 'ruRU', 'Палец2', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70506', 'ruRU', 'Аксессуар1', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70507', 'ruRU', 'Аксессуар2', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70508', 'ruRU', 'Шея', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70509', 'ruRU', 'Снять все', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70510', 'ruRU', 'Обновить внешний вид', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70511', 'ruRU', 'только внешний вид', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70512', 'ruRU', 'Надето', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70513', 'ruRU', 'ничего', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70514', 'ruRU', 'Используй свое старое cнаряжение', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70515', 'ruRU', 'Снять', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70516', 'ruRU', 'Хм... мне нечего тебе дать', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70517', 'ruRU', 'Сбор ингредиентов', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70518', 'ruRU', 'Статус способностей', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70519', 'ruRU', 'Разрешённые способности', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70520', 'ruRU', 'Используй ', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70521', 'ruRU', 'Обновить', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70522', 'ruRU', 'Урон', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70523', 'ruRU', 'Контроль', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70524', 'ruRU', 'Лечение', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70525', 'ruRU', 'Другое', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70526', 'ruRU', ' издает скрежет и начинает следовать за ', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70527', 'ruRU', '%s не присоединится к вам, пока владелец не уволит', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70528', 'ruRU', '%s не присоединится к вам, пока вы не достигнете 60-го уровня', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70529', 'ruRU', '%s не присоединится к вам, пока вы не достигнете 55-го уровня', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70530', 'ruRU', '%s не присоединится к вам, пока вы не достигнете 40-го уровня', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70531', 'ruRU', '%s не присоединится к вам, пока вы не достигнете 20-го уровня', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70532', 'ruRU', 'Вы превысили максимальное количество ботов (%u)', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70533', 'ruRU', 'У вас недостаточно денег', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70534', 'ruRU', 'У вас не может быть больше ботов этого класса! %u из %u', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70535', 'ruRU', 'Не удается сбросить снаряжение в слоте %u (%s)! Не могу уволить бота!', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70536', 'ruRU', 'сейчас', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70537', 'ruRU', 'Дистанция атаки', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70538', 'ruRU', 'Короткая дистанция', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70539', 'ruRU', 'Длинная дистанция', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70540', 'ruRU', 'Заданная', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70541', 'ruRU', 'Снять бафф', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70542', 'ruRU', 'Исправь тип энергии', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70543', 'ruRU', 'Не могу снять %s по какой-то идиотской причине! Отправляю по почте', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70544', 'ruRU', 'Танк', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70545', 'ruRU', 'Дальний бой', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70546', 'ruRU', 'Горное дело', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70547', 'ruRU', 'Травничество', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70548', 'ruRU', 'Снятие шкур', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70549', 'ruRU', 'Инженерное дело', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70550', 'ruRU', 'Срок владения ботом истек из-за бездействия', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70551', 'ruRU', 'Система NpcBot в данный момент отключена. Пожалуйста, обратитесь к администратору', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70552', 'ruRU', '%s не присоединится к вам, уже есть владелец: %s', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70553', 'ruRU', '%s не может присоединиться к вам: телепортируется', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70554', 'ruRU', 'Дух', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70555', 'ruRU', 'Обезьяна', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70556', 'ruRU', 'Ястреб', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70557', 'ruRU', 'Гепард', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70558', 'ruRU', 'Гадюка', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70559', 'ruRU', 'Зверь', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70560', 'ruRU', 'Стая', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70561', 'ruRU', 'Дикий', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70562', 'ruRU', 'Дракондор', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70563', 'ruRU', 'Нет духа', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70564', 'ruRU', 'Аура', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70565', 'ruRU', 'Благочестие', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70566', 'ruRU', 'Сосредоточенность', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70567', 'ruRU', 'Защита от огня', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70568', 'ruRU', 'Защита от магии льда', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70569', 'ruRU', 'Защита от темной магии', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70570', 'ruRU', 'Воздаяние', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70571', 'ruRU', 'Воин света', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70572', 'ruRU', 'Нет ауры', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70573', 'ruRU', 'Калечащий', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70574', 'ruRU', 'Быстродействующий', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70575', 'ruRU', 'Смертельный', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70576', 'ruRU', 'Нейтрализующий', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70577', 'ruRU', 'Дурманящий', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70578', 'ruRU', 'Анестезирующий', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70579', 'ruRU', 'Ничего', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70580', 'ruRU', 'Языки пламени', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70581', 'ruRU', 'Ледяное клеймо', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70582', 'ruRU', 'Неистовство ветра', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70583', 'ruRU', 'Жизнь земли', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70584', 'ruRU', 'Мне нужны твои услуги', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70585', 'ruRU', 'У тебя слишком много ботов', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70586', 'ruRU', 'Вы хотите нанять ', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70587', 'ruRU', ' сейчас немного занят, повторите попытку позже.', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70588', 'ruRU', 'Приятно иметь с тобой дело', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70589', 'ruRU', 'Воины', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70590', 'ruRU', 'Паладины', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70591', 'ruRU', 'Маги', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70592', 'ruRU', 'Жрецы', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70593', 'ruRU', 'Чернокнижники', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70594', 'ruRU', 'Друиды', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70595', 'ruRU', 'Рыцари Смерти', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70596', 'ruRU', 'Разбойники', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70597', 'ruRU', 'Шаманы', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70598', 'ruRU', 'Охотники', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70599', 'ruRU', 'Мастера Клинка', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70600', 'ruRU', 'Разрушители', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70601', 'ruRU', 'Архимаги', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70602', 'ruRU', 'Повелители Ужаса', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70603', 'ruRU', 'Разрушители Заклинаний', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70604', 'ruRU', 'Тёмные Охотницы', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70605', 'ruRU', 'Воин', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70606', 'ruRU', 'Паладин', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70607', 'ruRU', 'Маг', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70608', 'ruRU', 'Жрец', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70609', 'ruRU', 'Чернокнижник', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70610', 'ruRU', 'Друид', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70611', 'ruRU', 'Рыцарь смерти', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70612', 'ruRU', 'Разбойник', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70613', 'ruRU', 'Шаман', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70614', 'ruRU', 'Охотник', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70615', 'ruRU', 'Мастер Клинка', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70616', 'ruRU', 'Разрушитель', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70617', 'ruRU', 'Архимаг', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70618', 'ruRU', 'Повелитель Ужаса', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70619', 'ruRU', 'Разрушитель Заклинаний', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70620', 'ruRU', 'Темная Охотница', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70621', 'ruRU', 'Мужчина', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70622', 'ruRU', 'Женщина', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70623', 'ruRU', 'Человек', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70624', 'ruRU', 'Орк', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70625', 'ruRU', 'Гном', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70626', 'ruRU', 'Ночной эльф', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70627', 'ruRU', 'Нежить', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70628', 'ruRU', 'Таурен', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70629', 'ruRU', 'Гном', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70630', 'ruRU', 'Тролль', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70631', 'ruRU', 'Эльф крови', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70632', 'ruRU', 'Дреней', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70633', 'ruRU', 'Неизвестно', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70634', 'ruRU', 'Сбор добычи', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70635', 'ruRU', '|cff9d9d9dПлохой|r', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70636', 'ruRU', '|cffffffffОбычный|r', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70637', 'ruRU', '|cff1eff00Необычный|r', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70638', 'ruRU', '|cff0070ddРедкий|r', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70639', 'ruRU', '|cffa335eeЭпический|r', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70640', 'ruRU', '|cffff8000Легендарный|r', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70641', 'ruRU', 'Активное действие', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70642', 'ruRU', 'Задержка атаки на', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70643', 'ruRU', 'Задержка лечения на', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70644', 'ruRU', 'с', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70645', 'ruRU', 'Off-танк (второй танк)', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70646', 'ruRU', 'Некроманты', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70647', 'ruRU', 'Некромант', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70648', 'ruRU', 'Позиционирование в дальнем бою', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70649', 'ruRU', 'Обычное', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70650', 'ruRU', 'Избегать фронтального АОЕ', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70652', 'ruRU', 'Вы уверены, что это сработает? Это должна быть самая лучшая вода в мире...', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70653', 'ruRU', 'Похоже, тебе не помешает хороший глоток свежей воды.', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70654', 'ruRU', 'Морские Ведьмы', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70655', 'ruRU', 'Морская Ведьма', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70656', 'ruRU', 'Маны на единицу урона', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70657', 'ruRU', 'Урона на еденицу маны', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('70658', 'ruRU', 'Трансмогрификация...', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); diff --git a/sql/Bots/locales/zhCN/creature_template_locale.sql b/sql/Bots/locales/zhCN/creature_template_locale.sql new file mode 100644 index 000000000..59fd94371 --- /dev/null +++ b/sql/Bots/locales/zhCN/creature_template_locale.sql @@ -0,0 +1,742 @@ +DELETE FROM `creature_template_locale` WHERE `entry`=70595 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70595, 'zhCN', '蝗虫', '', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70594 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70594, 'zhCN', '腐尸甲虫', '', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70593 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70593, 'zhCN', '腐尸甲虫', '', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70592 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70592, 'zhCN', '腐尸甲虫', '', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70591 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70591, 'zhCN', '荷鲁斯明', '机动 地穴领主', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70590 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70590, 'zhCN', '阿拉克-阿拉姆', '机动 地穴领主', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70589 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70589, 'zhCN', '翡若斯', '机动 地穴领主', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70588 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70588, 'zhCN', '厄努比洛斯', '机动 地穴领主', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70587 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70587, 'zhCN', '图坦阿拉克', '机动 地穴领主', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70586 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70586, 'zhCN', '龙卷风', '', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70585 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70585, 'zhCN', '恰利布迪沙', '机动 深渊海巫', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70584 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70584, 'zhCN', '灰云', '机动 深渊海巫', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70583 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70583, 'zhCN', '小丽', '机动 深渊海巫', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70582 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70582, 'zhCN', '毒蛇娜', '机动 深渊海巫', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70581 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70581, 'zhCN', '康德拉', '机动 深渊海巫', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70580 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70580, 'zhCN', '骷髅', '', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70579 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70579, 'zhCN', '德罗萨姆', '机动 死灵法师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70578 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70578, 'zhCN', '丰磨', '机动 死灵法师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70577 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70577, 'zhCN', '赫克斯尔', '机动 死灵法师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70576 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70576, 'zhCN', '罗希克', '机动 死灵法师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70575 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70575, 'zhCN', '帕卡', '机动 死灵法师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70574 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70574, 'zhCN', '黑暗使徒', '', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70573 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70573, 'zhCN', '暗影使徒', '', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70572 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70572, 'zhCN', '丽拉', '机动 黑暗游侠', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70571 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70571, 'zhCN', '达莉丝', '机动 黑暗游侠', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70570 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70570, 'zhCN', '伊娃', '机动 黑暗游侠', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70569 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70569, 'zhCN', '谭', '机动 黑暗游侠', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70568 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70568, 'zhCN', '玛拉', '机动 黑暗游侠', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70567 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70567, 'zhCN', '涅什达', '机动 破法者', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70566 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70566, 'zhCN', '丹斯德', '机动 破法者', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70565 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70565, 'zhCN', '凯尔诺', '机动 破法者', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70564 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70564, 'zhCN', '纳森', '机动 破法者', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70563 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70563, 'zhCN', '伊诺尔', '机动 破法者', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70562 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70562, 'zhCN', '炼狱', '', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70561 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70561, 'zhCN', '拉马罗特', '机动 恐惧魔王', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70560 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70560, 'zhCN', '罗特修斯', '机动 恐惧魔王', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70559 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70559, 'zhCN', '扎拉蒙', '机动 恐惧魔王', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70558 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70558, 'zhCN', '恶魔罗斯', '机动 恐惧魔王', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70557 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70557, 'zhCN', '尼罗斯', '机动 恐惧魔王', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70556 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70556, 'zhCN', '水元素', '', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70555 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70555, 'zhCN', '迪崔', '机动 高阶法师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70554 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70554, 'zhCN', '阿默', '机动 殁境神蚀者', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70553 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70553, 'zhCN', '欧西斯', '机动 殁境神蚀者', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70552 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70552, 'zhCN', '剑圣', '机动 剑圣', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70551 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70551, 'zhCN', '戈克拉马托', '机动 剑圣', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70545 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70545, 'zhCN', '树人', '', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70544 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70544, 'zhCN', '水元素', '', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70543 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70543, 'zhCN', '灵狼', '', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70542 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70542, 'zhCN', '暗影恶魔', '', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70538 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70538, 'zhCN', '升起食尸鬼', '', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70537 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70537, 'zhCN', '虫子', '', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70536 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70536, 'zhCN', '犀牛', '', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70535 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70535, 'zhCN', '魔暴龙', '', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70534 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70534, 'zhCN', '核心犬', '', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70533 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70533, 'zhCN', '灵兽', '', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70532 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70532, 'zhCN', '奇美拉', '', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70531 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70531, 'zhCN', '蛰鞭兽', '', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70530 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70530, 'zhCN', '扭曲潜行者', '', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70529 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70529, 'zhCN', '鳄鱼兽', '', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70528 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70528, 'zhCN', '螃蟹', '', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70527 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70527, 'zhCN', '猪', '', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70526 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70526, 'zhCN', '熊', '', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70525 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70525, 'zhCN', '大猩猩', '', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70524 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70524, 'zhCN', '乌龟', '', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70523 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70523, 'zhCN', '天蝎灵', '', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70522 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70522, 'zhCN', '特罗莫斯', '', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70521 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70521, 'zhCN', '黄蜂', '', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70520 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70520, 'zhCN', '鬣狗', '', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70519 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70519, 'zhCN', '猫', '', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70518 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70518, 'zhCN', '高大步行者', '', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70517 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70517, 'zhCN', '狼', '', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70516 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70516, 'zhCN', '猛禽', '', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70515 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70515, 'zhCN', '腐肉鸟', '', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70514 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70514, 'zhCN', '孢子蝙', '', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70513 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70513, 'zhCN', '虚空鳐', '', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70512 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70512, 'zhCN', '龙鹰', '', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70511 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70511, 'zhCN', '毁灭者', '', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70510 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70510, 'zhCN', '风蛇', '', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70509 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70509, 'zhCN', '蝙蝠', '', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70508 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70508, 'zhCN', '掠食之鸟', '', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70507 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70507, 'zhCN', '毒蛇', '', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70506 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70506, 'zhCN', '蜘蛛', '', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70505 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70505, 'zhCN', '恶魔卫士', '', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70504 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70504, 'zhCN', '邪能猎犬', '', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70503 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70503, 'zhCN', '魅魔', '', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70502 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70502, 'zhCN', '虚空行者', '', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70501 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70501, 'zhCN', '小恶魔', '', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70465 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70465, 'zhCN', '史蒂芬', '机动 死亡骑士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70464 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70464, 'zhCN', '曼陀罗', '机动 死亡骑士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70463 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70463, 'zhCN', '佐贝', '机动 死亡骑士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70462 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70462, 'zhCN', '克洛克', '机动 死亡骑士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70461 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70461, 'zhCN', '伊利丽额', '机动 死亡骑士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70460 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70460, 'zhCN', '乌佐', '机动 死亡骑士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70459 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70459, 'zhCN', '星澈', '机动 死亡骑士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70458 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70458, 'zhCN', '阿莉', '机动 死亡骑士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70457 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70457, 'zhCN', '维瑞思', '机动 死亡骑士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70456 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70456, 'zhCN', '银缕', '机动 死亡骑士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70455 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70455, 'zhCN', '岚克雷', '机动 死亡骑士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70454 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70454, 'zhCN', '缪妮克斯', '机动 死亡骑士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70453 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70453, 'zhCN', '维伦', '机动 死亡骑士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70452 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70452, 'zhCN', '音风', '机动 死亡骑士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70451 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70451, 'zhCN', '苏西', '机动 死亡骑士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70418 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70418, 'zhCN', '夏兰尼斯', '机动 德鲁伊', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70417 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70417, 'zhCN', '哈琳', '机动 德鲁伊', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70416 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70416, 'zhCN', '洛加纳尔', '机动 德鲁伊', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70415 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70415, 'zhCN', '高林', '机动 德鲁伊', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70414 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70414, 'zhCN', '杰诺斯', '机动 德鲁伊', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70413 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70413, 'zhCN', '曼德林', '机动 德鲁伊', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70412 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70412, 'zhCN', '瑟里德兰', '机动 德鲁伊', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70411 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70411, 'zhCN', '谢尔德拉斯', '机动 德鲁伊', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70410 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70410, 'zhCN', '费勒里安', '机动 德鲁伊', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70409 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70409, 'zhCN', '丹纳萨利安', '机动 德鲁伊', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70408 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70408, 'zhCN', '玛斯兰吉尔', '机动 德鲁伊', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70407 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70407, 'zhCN', '卡尔', '机动 德鲁伊', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70406 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70406, 'zhCN', '马丹特', '机动 德鲁伊', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70405 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70405, 'zhCN', '珍雅', '机动 德鲁伊', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70404 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70404, 'zhCN', '嘉特', '机动 德鲁伊', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70403 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70403, 'zhCN', '凯姆', '机动 德鲁伊', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70402 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70402, 'zhCN', '希尔', '机动 德鲁伊', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70401 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70401, 'zhCN', '图拉克', '机动 德鲁伊', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70377 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70377, 'zhCN', '巴巴加亚', '机动 术士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70376 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70376, 'zhCN', '赞尼恩', '机动 术士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70375 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70375, 'zhCN', '复仇者', '机动 术士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70374 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70374, 'zhCN', '阿拉玛', '机动 术士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70373 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70373, 'zhCN', '赛洛尼斯', '机动 术士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70372 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70372, 'zhCN', '泰莉拉琳', '机动 术士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70371 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70371, 'zhCN', '金瑞兹', '机动 术士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70370 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70370, 'zhCN', '桑达尔', '机动 术士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70369 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70369, 'zhCN', '厄伊', '机动 术士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70368 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70368, 'zhCN', '亚历山大', '机动 术士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70367 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70367, 'zhCN', '荆棘霜', '机动 术士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70366 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70366, 'zhCN', '蓟心', '机动 术士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70365 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70365, 'zhCN', '理查德', '机动 术士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70364 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70364, 'zhCN', '路德', '机动 术士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70363 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70363, 'zhCN', '卡尔', '机动 术士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70362 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70362, 'zhCN', '泽弗洛斯', '机动 术士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70361 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70361, 'zhCN', '米尔凯', '机动 术士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70360 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70360, 'zhCN', '格洛达', '机动 术士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70359 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70359, 'zhCN', '杜古如', '机动 术士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70358 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70358, 'zhCN', '纳托克', '机动 术士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70357 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70357, 'zhCN', '鲁珀', '机动 术士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70356 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70356, 'zhCN', '马克西米利安', '机动 术士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70355 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70355, 'zhCN', '卡托什', '机动 术士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70354 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70354, 'zhCN', '马克西米利安', '机动 术士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70353 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70353, 'zhCN', '德米赛特', '机动 术士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70352 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70352, 'zhCN', '阿拉玛', '机动 术士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70351 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70351, 'zhCN', '杜瑟拉', '机动 术士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70336 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70336, 'zhCN', '德里克', '机动 法师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70335 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70335, 'zhCN', '巴蒂', '机动 法师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70334 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70334, 'zhCN', '哈南', '机动 法师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70333 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70333, 'zhCN', '半葵', '机动 法师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70332 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70332, 'zhCN', '华兰斯', '机动 法师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70331 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70331, 'zhCN', '伊迪拉', '机动 法师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70330 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70330, 'zhCN', '纳林思', '机动 法师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70329 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70329, 'zhCN', '因特芬', '机动 法师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70328 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70328, 'zhCN', '奎萨思', '机动 法师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70327 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70327, 'zhCN', '泽达娜', '机动 法师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70326 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70326, 'zhCN', '瓦拉图', '机动 法师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70325 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70325, 'zhCN', '嘉瑞德', '机动 法师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70324 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70324, 'zhCN', '朱莉', '机动 法师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70323 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70323, 'zhCN', '丁克', '机动 法师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70322 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70322, 'zhCN', '优瑟妮', '机动 法师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70321 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70321, 'zhCN', '迪诺', '机动 法师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70320 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70320, 'zhCN', '麦亚', '机动 法师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70319 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70319, 'zhCN', '恩尤', '机动 法师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70318 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70318, 'zhCN', '菲佛德', '机动 法师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70317 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70317, 'zhCN', '恩图瓦', '机动 法师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70316 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70316, 'zhCN', '洁娜', '机动 法师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70315 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70315, 'zhCN', '尼特尔伯', '机动 法师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70314 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70314, 'zhCN', '朱莉', '机动 法师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70313 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70313, 'zhCN', '彬克', '机动 法师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70312 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70312, 'zhCN', '安娜斯塔西娅', '机动 法师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70311 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70311, 'zhCN', '皮尔斯', '机动 法师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70310 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70310, 'zhCN', '瑟斯顿', '机动 法师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70309 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70309, 'zhCN', '乌尔辛', '机动 法师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70308 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70308, 'zhCN', '什姆', '机动 法师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70307 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70307, 'zhCN', '肯', '机动 法师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70306 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70306, 'zhCN', '伊莎贝拉', '机动 法师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70305 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70305, 'zhCN', '魔姬', '机动 法师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70304 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70304, 'zhCN', '玛瑞克', '机动 法师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70303 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70303, 'zhCN', '玛吉诺', '机动 法师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70302 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70302, 'zhCN', '赞迪玛', '机动 法师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70301 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70301, 'zhCN', '凯尔登', '机动 法师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70268 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70268, 'zhCN', '贾瓦德', '机动 萨满', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70267 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70267, 'zhCN', '幽蓝', '机动 萨满', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70265 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70265, 'zhCN', '舒拉', '机动 萨满', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70261 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70261, 'zhCN', '萨戈恩', '机动 萨满', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70260 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70260, 'zhCN', '思安德', '机动 萨满', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70259 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70259, 'zhCN', '卡德里斯', '机动 萨满', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70258 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70258, 'zhCN', '斯沃特', '机动 萨满', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70257 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70257, 'zhCN', '采莎', '机动 萨满', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70256 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70256, 'zhCN', '楠琳', '机动 萨满', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70255 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70255, 'zhCN', '米拉', '机动 萨满', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70254 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70254, 'zhCN', '贝拉姆', '机动 萨满', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70253 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70253, 'zhCN', '腾格', '机动 萨满', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70252 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70252, 'zhCN', '思琳', '机动 萨满', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70251 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70251, 'zhCN', '哈罗姆', '机动 萨满', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70240 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70240, 'zhCN', '法拉特', '机动 牧师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70239 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70239, 'zhCN', '伊兹密尔', '机动 牧师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70238 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70238, 'zhCN', '谷万', '机动 牧师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70237 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70237, 'zhCN', '凯德莫斯', '机动 牧师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70236 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70236, 'zhCN', '贝莱斯特拉', '机动 牧师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70235 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70235, 'zhCN', '洛西兰', '机动 牧师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70234 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70234, 'zhCN', '艾尔德雷', '机动 牧师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70233 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70233, 'zhCN', '沙尔顿', '机动 牧师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70232 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70232, 'zhCN', '波纳里斯', '机动 牧师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70231 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70231, 'zhCN', '竞技场', '机动 牧师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70230 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70230, 'zhCN', '若涵', '机动 牧师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70229 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70229, 'zhCN', '艾莉西娅', '机动 牧师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70228 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70228, 'zhCN', '娜拉', '机动 牧师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70227 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70227, 'zhCN', '乌尔奇奥', '机动 牧师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70226 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70226, 'zhCN', '席雅拉', '机动 牧师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70225 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70225, 'zhCN', '泽尤斯', '机动 牧师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70224 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70224, 'zhCN', '约书亚', '机动 牧师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70223 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70223, 'zhCN', '本杰明', '机动 牧师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70222 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70222, 'zhCN', '托尔德伦', '机动 牧师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70221 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70221, 'zhCN', '布雷娜', '机动 牧师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70220 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70220, 'zhCN', '西奥德鲁斯', '机动 牧师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70219 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70219, 'zhCN', '拉撒路', '机动 牧师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70218 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70218, 'zhCN', '兰开斯特', '机动 牧师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70217 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70217, 'zhCN', '拉莉娅', '机动 牧师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70216 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70216, 'zhCN', '杨迪儿', '机动 牧师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70215 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70215, 'zhCN', '阿斯塔丽', '机动 牧师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70214 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70214, 'zhCN', '肯杰', '机动 牧师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70213 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70213, 'zhCN', '泰津', '机动 牧师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70212 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70212, 'zhCN', '罗娜', '机动 牧师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70211 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70211, 'zhCN', '珊达', '机动 牧师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70210 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70210, 'zhCN', '科布', '机动 牧师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70209 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70209, 'zhCN', '马拉凯', '机动 牧师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70208 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70208, 'zhCN', '迈尔斯', '机动 牧师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70207 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70207, 'zhCN', '贝丽', '机动 牧师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70206 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70206, 'zhCN', '迪斯滕', '机动 牧师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70205 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70205, 'zhCN', '马克森', '机动 牧师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70204 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70204, 'zhCN', '枝根', '机动 牧师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70203 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70203, 'zhCN', '若青', '机动 牧师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70202 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70202, 'zhCN', '罗芮娜', '机动 牧师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70201 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70201, 'zhCN', '安妮塔', '机动 牧师', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70181 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70181, 'zhCN', '涅莉森', '机动 潜行者', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70180 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70180, 'zhCN', '艾拉拉', '机动 潜行者', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70179 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70179, 'zhCN', '泽兰尼斯', '机动 潜行者', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70178 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70178, 'zhCN', '坦娜莉娅', '机动 潜行者', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70177 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70177, 'zhCN', '卡丽尔', '机动 潜行者', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70176 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70176, 'zhCN', '托尼', '机动 潜行者', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70175 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70175, 'zhCN', '法拉德', '机动 潜行者', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70174 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70174, 'zhCN', '芬瑟维克', '机动 潜行者', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70173 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70173, 'zhCN', '奥米尔', '机动 潜行者', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70172 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70172, 'zhCN', '霍尔夫丹', '机动 潜行者', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70171 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70171, 'zhCN', '格雷戈里', '机动 潜行者', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70170 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70170, 'zhCN', '迈尔斯', '机动 潜行者', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70169 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70169, 'zhCN', '凯琳', '机动 潜行者', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70168 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70168, 'zhCN', '安尼莎', '机动 潜行者', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70167 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70167, 'zhCN', '艾瑞昂', '机动 潜行者', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70166 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70166, 'zhCN', '思娜', '机动 潜行者', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70165 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70165, 'zhCN', '简诺', '机动 潜行者', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70164 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70164, 'zhCN', '弗拉洪', '机动 潜行者', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70163 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70163, 'zhCN', '深石', '机动 潜行者', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70162 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70162, 'zhCN', '奥莫克', '机动 潜行者', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70161 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70161, 'zhCN', '盖斯特', '机动 潜行者', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70160 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70160, 'zhCN', '卡普拉', '机动 潜行者', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70159 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70159, 'zhCN', '鲁瓦格', '机动 潜行者', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70158 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70158, 'zhCN', '玛丽安', '机动 潜行者', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70157 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70157, 'zhCN', '大卫', '机动 潜行者', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70156 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70156, 'zhCN', '伊恩', '机动 潜行者', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70155 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70155, 'zhCN', '猎獭', '机动 潜行者', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70154 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70154, 'zhCN', '奥斯本', '机动 潜行者', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70153 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70153, 'zhCN', '凯琳', '机动 潜行者', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70152 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70152, 'zhCN', '索尔姆', '机动 潜行者', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70151 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70151, 'zhCN', '乔里克', '机动 潜行者', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70139 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70139, 'zhCN', '杀手', '机动 猎人', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70138 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70138, 'zhCN', '沃德', '机动 猎人', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70137 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70137, 'zhCN', '阿克特翁', '机动 猎人', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70136 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70136, 'zhCN', '德蕾米丝', '机动 猎人', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70135 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70135, 'zhCN', '赞迪娜', '机动 猎人', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70134 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70134, 'zhCN', '奥尼纳丝', '机动 猎人', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70133 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70133, 'zhCN', '塔娜', '机动 猎人', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70132 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70132, 'zhCN', '凯尔内', '机动 猎人', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70131 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70131, 'zhCN', '汉诺维亚', '机动 猎人', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70130 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70130, 'zhCN', '萨琳娜', '机动 猎人', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70129 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70129, 'zhCN', '达格', '机动 猎人', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70128 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70128, 'zhCN', '阿伦达', '机动 猎人', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70127 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70127, 'zhCN', '托尔芬', '机动 猎人', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70126 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70126, 'zhCN', '乌尔菲尔', '机动 猎人', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70125 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70125, 'zhCN', '恩瑞斯', '机动 猎人', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70124 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70124, 'zhCN', '凯布鲁斯', '机动 猎人', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70123 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70123, 'zhCN', '雷格努斯', '机动 猎人', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70122 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70122, 'zhCN', '奥尔明', '机动 猎人', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70121 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70121, 'zhCN', '德拉', '机动 猎人', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70120 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70120, 'zhCN', '多里昂', '机动 猎人', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70119 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70119, 'zhCN', '约卡斯特', '机动 猎人', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70118 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70118, 'zhCN', '洁娜', '机动 猎人', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70117 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70117, 'zhCN', '丹拉尔', '机动 猎人', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70116 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70116, 'zhCN', '达扎拉', '机动 猎人', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70115 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70115, 'zhCN', '艾安娜', '机动 猎人', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70114 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70114, 'zhCN', '思安杜尔', '机动 猎人', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70113 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70113, 'zhCN', '索兹朱尔', '机动 猎人', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70112 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70112, 'zhCN', '奥玛克', '机动 猎人', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70111 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70111, 'zhCN', '索费雷', '机动 猎人', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70110 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70110, 'zhCN', '杰尚', '机动 猎人', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70109 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70109, 'zhCN', '尧', '机动 猎人', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70108 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70108, 'zhCN', '兰卡', '机动 猎人', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70107 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70107, 'zhCN', '优瑞', '机动 猎人', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70106 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70106, 'zhCN', '霍尔特', '机动 猎人', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70105 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70105, 'zhCN', '凯瑞', '机动 猎人', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70104 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70104, 'zhCN', '克拉格', '机动 猎人', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70103 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70103, 'zhCN', '格里夫', '机动 猎人', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70102 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70102, 'zhCN', '奥格罗姆', '机动 猎人', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70101 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70101, 'zhCN', '索尔加斯', '机动 猎人', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70100 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70100, 'zhCN', '深海对话触发器', '', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70074 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70074, 'zhCN', '如云', '机动 圣骑士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70073 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70073, 'zhCN', '烈焰者', '机动 圣骑士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70072 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70072, 'zhCN', '希莎', '机动 圣骑士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70071 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70071, 'zhCN', '乔勒', '机动 圣骑士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70070 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70070, 'zhCN', '图拉斯', '机动 圣骑士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70069 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70069, 'zhCN', '卡万', '机动 圣骑士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70068 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70068, 'zhCN', '巴屯', '机动 圣骑士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70067 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70067, 'zhCN', '巴奇', '机动 圣骑士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70066 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70066, 'zhCN', '艾瑟丽丝', '机动 圣骑士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70065 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70065, 'zhCN', '欧瑟兰', '机动 圣骑士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70064 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70064, 'zhCN', '奥瑞隆', '机动 圣骑士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70063 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70063, 'zhCN', '娜琳', '机动 圣骑士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70062 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70062, 'zhCN', '杰斯汀', '机动 圣骑士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70061 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70061, 'zhCN', '卡曼', '机动 圣骑士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70060 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70060, 'zhCN', '凯瑟琳', '机动 圣骑士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70059 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70059, 'zhCN', '亚瑟', '机动 圣骑士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70058 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70058, 'zhCN', '布兰德尔', '机动 圣骑士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70057 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70057, 'zhCN', '贝尔德鲁克', '机动 圣骑士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70056 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70056, 'zhCN', '瓦尔加', '机动 圣骑士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70055 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70055, 'zhCN', '阿萨尔', '机动 圣骑士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70054 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70054, 'zhCN', '格雷森', '机动 圣骑士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70053 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70053, 'zhCN', '威廉', '机动 圣骑士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70052 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70052, 'zhCN', '溴盐', '机动 圣骑士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70051 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70051, 'zhCN', '山缪尔', '机动 圣骑士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70038 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70038, 'zhCN', '凯丽娅', '机动 战士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70037 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70037, 'zhCN', '卡茲', '机动 战士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70036 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70036, 'zhCN', '如娜', '机动 战士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70035 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70035, 'zhCN', '贝霍玛', '机动 战士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70034 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70034, 'zhCN', '阿霍南', '机动 战士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70033 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70033, 'zhCN', '柯蕊', '机动 战士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70032 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70032, 'zhCN', '均糖', '机动 战士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70031 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70031, 'zhCN', '达纳斯', '机动 战士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70030 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70030, 'zhCN', '伊莎', '机动 战士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70029 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70029, 'zhCN', '吴', '机动 战士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70028 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70028, 'zhCN', '毕尔邦', '机动 战士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70027 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70027, 'zhCN', '凯尔夫', '机动 战士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70026 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70026, 'zhCN', '巴尔图斯', '机动 战士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70025 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70025, 'zhCN', '安琪拉', '机动 战士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70024 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70024, 'zhCN', '克里斯', '机动 战士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70023 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70023, 'zhCN', '西尔丹爱尔', '机动 战士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70022 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70022, 'zhCN', '阿丽亚思塔', '机动 战士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70021 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70021, 'zhCN', '凯拉', '机动 战士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70020 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70020, 'zhCN', '爱丽西娅', '机动 战士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70019 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70019, 'zhCN', '泽尔马克', '机动 战士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70018 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70018, 'zhCN', '索热克', '机动 战士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70017 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70017, 'zhCN', '格雷兹', '机动 战士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70016 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70016, 'zhCN', '塔尔肖', '机动 战士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70015 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70015, 'zhCN', '方儿', '机动 战士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70014 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70014, 'zhCN', '克兰', '机动 战士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70013 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70013, 'zhCN', '哈鲁特', '机动 战士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70012 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70012, 'zhCN', '柯尔', '机动 战士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70011 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70011, 'zhCN', '萨克', '机动 战士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70010 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70010, 'zhCN', '托姆', '机动 战士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70009 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70009, 'zhCN', '奥斯汀', '机动 战士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70008 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70008, 'zhCN', '丹娜', '机动 战士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70007 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70007, 'zhCN', '凯尔斯特拉姆', '机动 战士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70006 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70006, 'zhCN', '格兰尼斯', '机动 战士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70005 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70005, 'zhCN', '马洛什', '机动 战士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70004 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70004, 'zhCN', '安德尔', '机动 战士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70003 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70003, 'zhCN', '丽娜', '机动 战士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70002 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70002, 'zhCN', '思兰', '机动 战士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70001 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70001, 'zhCN', '莱恩', '机动 战士', NULL); +DELETE FROM `creature_template_locale` WHERE `entry`=70000 AND `locale`='zhCN'; +INSERT INTO `creature_template_locale` (`entry`, `locale`, `Name`, `Title`, `VerifiedBuild`) VALUES (70000, 'zhCN', '拉格雷塔', '机动雇佣兵 首领', NULL); diff --git a/sql/Bots/locales/zhCN/npc_text_locale.sql b/sql/Bots/locales/zhCN/npc_text_locale.sql new file mode 100644 index 000000000..f31b23a40 --- /dev/null +++ b/sql/Bots/locales/zhCN/npc_text_locale.sql @@ -0,0 +1,410 @@ +DELETE FROM `npc_text_locale` WHERE `Locale`='zhCN' AND `ID` BETWEEN '70000' AND '71000'; +INSERT INTO `npc_text_locale` (`ID`, `Locale`, `Text0_0`, `Text0_1`, `Text1_0`, `Text1_1`, `Text2_0`, `Text2_1`, `Text3_0`, `Text3_1`, `Text4_0`, `Text4_1`, `Text5_0`, `Text5_1`, `Text6_0`, `Text6_1`, `Text7_0`, `Text7_1`) +VALUES +('70001','zhCN','我愿意为你奉献一切。',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70002','zhCN','你需要什么吗?',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70003','zhCN','凡人,通常情况下,我会立刻杀了你这混蛋!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70004','zhCN','<你看着这个黑曜石毁灭者。它看起来有点与众不同,它并没有因为你的伤害而消失,在仔细检查过后,你注意到它的背部有一个小口。>',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70005','zhCN','<毁灭者盯着你,发出低沉的咆哮。>',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70006','zhCN','你惊讶吗,凡人?作为一名纳斯雷兹姆,我不得不寻求盟友,你看起来至少可以逗我开心。',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70007','zhCN','现在怎么样了,凡人?',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70008','zhCN','你就不能让我一个人静一静吗? <唉。。。>',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70009','zhCN','现在如何?',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70101','zhCN','|cffff3300剑圣|r$b|cffdd6600-=致敬魔兽争霸III=-|r$B$B\\\"剑圣,以前是燃烧之刃家族的成员,现在是部落中的精锐战士\".$B$B主要属性:敏捷.$B$B疾风步.让剑圣隐形,并在一段时间内移动得更快.当剑圣攻击一个单位以打破隐身状态时,他将造成额外的伤害.$B$B镜像.制造剑圣幻象迷惑敌人,并驱散剑圣的所有魔法.$B$B爆击(被动).有15%的几率在他的攻击中造成致命的x2(x3,x4)倍的正常伤害.$B$B剑刃风暴. 给予魔法免疫并对周围所有敌人造成伤害.$B$B',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70102','zhCN','|cff9900cc黑曜石毁灭者|r$b|cffdd6600-=致敬魔兽争霸III=-|r$B$B\\\"黑曜石毁灭者,对魔法有着无法满足的渴望\\\".$B$B高护甲,非常高的抵抗力,部分免疫魔法,随着时间的推移失去法力\\并且不受益于被动法力再生效果,板甲,双持魔杖,造成暗影法术伤害,没有物理攻击\\法术伤害加成:50%攻击力+200% 智力增加魔杖伤害.$B$B吞噬魔法.从敌人身上驱散最多2个魔法效果,从盟友身上驱散最多2个魔法效果和最多2个诅咒,并在20码范围内伤害召唤单位.每次驱散效果回复20%法力和5%生命,7秒冷却.$B$B暗影爆炸.强化攻击,增加飞溅伤害.$B$B吸取法力.从随机友方单位吸取所有法力(受施法者法力限制).$B$B补充法力.给周围15码范围内的团队和团队成员通电,使其法力值达到最大值的1%,影响最多10个目标,冷却3秒.$B$B再生光环.治疗周围15码范围内的团队和团队成员,使其生命值降低施法者法力上限的2%,最多影响10个目标,冷却3秒.$B$B暗影护甲(被动).恢复相当于所受伤害百分比的法力值.$B$B',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70103','zhCN','|cff0000dd大魔导师|r$b|cffdd6600-=致敬魔兽争霸III=-|r$B$B\\\"大法师,无法用言语来形容的暴雪亲儿子\\\".$B$B法术伤害降低35%,部分免疫控制效果,布甲,造成火/冰法术伤害,无物理攻击,法术能量加成:100%智力.主要属性:智力.$B$B暴风雪!典型的暴风雪,只是稍微强大一点,6秒冷却.召唤水元素攻击大法师的敌人1分钟,冷却20秒.$B$B光辉光环.增加10%的法力上限,大大提高40码范围内己方和队友的法力回复.$B$B大规模传送.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70104','zhCN','|cff9900cc恐惧魔王|r$b|cffdd6600-=致敬魔兽争霸III=-|r$B$B\\\"恐惧魔王,拥有黑暗力量和精神控制力的强大恶魔\\\".$B$B高护甲,高抵抗,部分免疫控制效果,所受伤害加速法术能量,板甲,造成近战/暗影法术伤害,对瘫痪目标造成额外伤害,法术能量加成:200%力量.主要属性:力量.$B$B腐蚀虫群.派一大群蝙蝠与混乱魔法结合,对面前锥形范围的敌人造成伤害,无法暴击,冷却10秒.$B$B沉睡.使敌方目标睡眠60秒,并允许下次对该目标的物理攻击忽视护甲,造成的直接伤害将唤醒目标,冷却6秒.$B$B吸血鬼的光环.增加5%的物理暴击伤害,治疗40码范围内的团队和突袭成员受到近战物理攻击和腐肉群伤害的百分比(恐惧领主100%,其他人25%),没有威胁.$B$B召唤地狱恶魔.召唤一个来自天空的恶魔,恶魔对魔法有很强的抵抗力并且持续180秒,180秒的冷却时间.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70105','zhCN','|cff0000dd破法者|r$b|cffdd6600-=致敬魔兽争霸III=-|r$B$B\\\"破法者,一个被训练来破坏和扭曲魔法能量的精灵战士\\\".$B$B法术伤害减少75%,部分免疫控制效果,护甲降低-30%,板甲,近战/奥术伤害,法术能量加成:200%力量.主要属性:力量.$B$B魔法偷取.从敌人身上偷取一个有益的法术并施于附近的盟友,或者从盟友身上移除一个消极的法术并施于附近的敌人,影响魔法和诅咒效果,3秒冷却.$B$B能量窃取(被动).成功的近战攻击消耗目标的法力,相当于造成的伤害(由法术力增加),造成奥术伤害.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70106','zhCN','|cff9900cc黑暗游侠|r$b|cffdd6600-=致敬魔兽争霸III=-|r$B$B\\\"黑暗游侠,强行起死回生的奎尔萨拉斯的前游侠\\\".$B$B法术伤害减少35%,不死系,部分免疫控制效果,皮甲/布甲,造成物理/暗影法术伤害,不产生威胁,法术力量加成:50%智力.主要属性:敏捷.$B$B沉默.使一个敌人和最多4个附近目标沉默8秒,15秒冷却.$B$B黑箭.射出一支诅咒之箭,造成150%武器伤害和额外的暗影伤害.如果受影响的目标死于黑暗游侠的伤害,黑暗仆从将从尸体中产生(最多5个仆从,持续时间80秒,只对人形、野兽和龙类有效).如果目标生命低于20%,造成5倍伤害.$B$B吸取生命.每秒吸取一个敌人的生命值,持续5秒,治疗黑暗游侠消耗生命值的200%.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70107','zhCN','|cff9900cc死灵法师|r$b|cffdd6600-=魔兽争霸III / 暗黑破坏神II致敬=-|r$B$B受到的法术伤害减少20%,部分免疫控制效果,布甲,造成暗影法术伤害,没有物理攻击,法术强度加成: 智力的100%。主属性: 智力。$B$B复活死者: 从尸体上复活2个骷髅(最多6个骷髅,持续65秒,只对人型生物、野兽和龙类有效)。$B$B邪恶狂热: 增加目标的近战攻击速度75%,但持续消耗生命。持续45秒。不能取消。30级解锁。$B$B尸体爆炸。让尸体爆炸,对周围所有敌人造成相当于死亡单位最大生命值35%至75%的伤害(取决于死灵法师的等级)。这种伤害不产生威胁。40级解锁。$B$B残废: 减少目标的移动速度、近战攻击速度和总力量50%,持续60秒。50级解锁。$B$B',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70108','zhCN','|cff0000dd海巫|r$b|cffdd6600-=魔兽争霸III致敬=-|r$B$B"海洋中的凶猛鳞皮居民,常与巨大风暴的到来联系在一起。"$B$B受到的法术伤害减少30%,部分免疫控制效果,布甲,造成物理/冰霜法术/风暴法术伤害,攻击力加成: 敏捷x2,法术强度加成: 智力的200%。主属性: 智力。$B$B分叉闪电: 召唤一道闪电锥,伤害敌人。从2到所有目标(取决于海巫的等级),使它们昏迷2秒。这种伤害不产生威胁。$B$B冰霜箭。用冰霜法术为箭矢增加额外伤害,减慢目标的移动、攻击和施法速度30%至70%(取决于海巫的等级)。$B$B法力护盾: 创造一个护盾,使用海巫的法力吸收100%的(未减免的)进来伤害。效果范围从每10法力吸收1伤害到每1法力吸收10伤害(取决于海巫的等级)。$B$B龙卷风: 召唤一个猛烈的龙卷风,伤害并减慢附近敌方单位的速度,有时完全使它们失去行动能力。龙卷风在户外随时间增长,增加伤害和影响区域,但在室内缩小,迅速消散。60级解锁。$B$B娜迦(被动): 在水中游泳速度、伤害和躲避几率大幅提升。$B$B',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70109','zhCN','|cff9900cc地穴领主|r$b|cffdd6600-=魔兽争霸III致敬=-|r$B$B"古老的庞然大物,曾是艾泽拉斯之王之一,现在是巫妖王麾下最强大的亡灵战士之一。"$B$B高度护甲,增加的抗性,部分免疫控制效果,免疫基于毒素的效果,锁甲/板甲,造成近战/暗影法术伤害,法术强度加成: 力量的200%。主属性: 力量。$B$B穿刺。地穴领主用他巨大的爪子猛击地面,向前锥形射出尖刺,造成伤害并将敌方单位掀入空中,使他们晕眩。20级解锁。$B$B刺甲: 地穴领主的几丁质护甲增加伤害抗性,并将15%至50%的伤害反弹给近战攻击者。$B$B腐尸甲虫。地穴领主从敌人的新鲜尸体上产生一个腐尸甲虫来攻击他的敌人。甲虫是永久的,但不会恢复生命值,一次最多控制6个。更高等级允许地穴领主召唤更强大的甲虫。10级解锁。$B$B蝗虫群: 地穴领主释放一群20-40只(取决于地穴领主的等级)愤怒的蝗虫,啃咬并撕扯附近的敌方单位,减少他们的移动或攻击能力。当它们啃食敌人肉体时,它们将其转化为一种物质,当它们返回时恢复地穴领主的生命值。40级解锁。$B$B',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70201','zhCN','并不总是有人愿意为钱去卖命!',NULL,'我们的服务遍布艾泽拉斯的每个角落!',NULL,'很高兴为您服务,我这里有最能打的家伙!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70202','zhCN','雇佣兵向来十分抢手;以下是现在你能雇佣的人: ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70203','zhCN','雇佣兵向来十分抢手;以下是现在你能雇佣的人: ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70204','zhCN','看来现在没有空闲的雇佣兵,你过些时候再来看看吧。',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70300','zhCN','去死!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70301','zhCN','正在复活你!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70302','zhCN','正在复活 ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70303','zhCN','你的机器人',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70304','zhCN','的机器人',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70305','zhCN','我现在还不能制造魔法水',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70306','zhCN','我现在还不能制造魔法食物',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70307','zhCN','我还不能那样做',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70308','zhCN','给你...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70309','zhCN','已禁用',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70310','zhCN','还没有准备好',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70311','zhCN','无效的物品类型',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70312','zhCN','失败',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70313','zhCN','完成',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70314','zhCN','我没有变形',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70315','zhCN','我没有治疗石',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70316','zhCN','我还不能制造治疗石',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70317','zhCN','搞什么鬼,我没有锁可开!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70318','zhCN','我的技能等级还不够高',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70319','zhCN','正在将我的天赋切换为',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70320','zhCN','武器',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70321','zhCN','狂怒',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70322','zhCN','防护',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70323','zhCN','惩戒',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70324','zhCN','野兽控制',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70325','zhCN','射击',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70326','zhCN','生存',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70327','zhCN','刺杀',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70328','zhCN','战斗',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70329','zhCN','敏锐',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70330','zhCN','戒律',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70331','zhCN','神圣',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70332','zhCN','暗影',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70333','zhCN','鲜血',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70334','zhCN','冰霜',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70335','zhCN','邪恶',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70336','zhCN','元素',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70337','zhCN','增强',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70338','zhCN','恢复',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70339','zhCN','奥术',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70340','zhCN','火焰',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70341','zhCN','痛苦',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70342','zhCN','恶魔学识',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70343','zhCN','毁灭',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70344','zhCN','平衡',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70345','zhCN','野性战斗',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70346','zhCN','未知',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70347','zhCN','滚开! 懦夫。',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70348','zhCN',' 不方便。',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70349','zhCN','我不会把我宝贵的时间浪费在这些琐事上!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70350','zhCN','NIY',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70351','zhCN','NIY',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70352','zhCN','NIY',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70353','zhCN','我准备好了',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70354','zhCN','走开!我已经有雇主了!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70355','zhCN','未知',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70356','zhCN','在你身上!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70357','zhCN','在我身上!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70358','zhCN',' 对 ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70359','zhCN',' 已使用!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70360','zhCN','坦克机器人',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70361','zhCN','职业',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70362','zhCN','玩家',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70363','zhCN','雇主',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70364','zhCN','无',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70365','zhCN','级别',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70366','zhCN','天赋',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70367','zhCN','被动',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70368','zhCN','隐藏',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70369','zhCN','已知',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70370','zhCN','能力',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70371','zhCN','力量',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70372','zhCN','敏捷',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70373','zhCN','耐力',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70374','zhCN','智力',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70375','zhCN','精神',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70376','zhCN','未知属性',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70377','zhCN','共',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70378','zhCN','近战攻击强度',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70379','zhCN','远程攻击强度',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70380','zhCN','护甲',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70381','zhCN','暴击',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70382','zhCN','防御',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70383','zhCN','未命中',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70384','zhCN','躲闪',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70385','zhCN','招架',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70386','zhCN','格挡',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70387','zhCN','盾牌格挡值',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70388','zhCN','近战防御',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70389','zhCN','法术防御',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70390','zhCN','主手攻击伤害范围',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70391','zhCN','主手攻击伤害加成',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70392','zhCN','主手攻击速度',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70393','zhCN','副手攻击伤害范围',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70394','zhCN','副手攻击伤害加成',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70395','zhCN','副手攻击速度',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70396','zhCN','远程攻击伤害范围',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70397','zhCN','远程攻击伤害加成',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70398','zhCN','远程攻击速度',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70399','zhCN','最小',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70400','zhCN','最大',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70401','zhCN','输出',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70402','zhCN','基础生命值',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70403','zhCN','最大生命值',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70404','zhCN','基础法力值',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70405','zhCN','最大法力值',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70406','zhCN','当前法力值',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70407','zhCN','法术强度',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70408','zhCN','每5秒恢复生命',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70409','zhCN','非施法状态: 每5秒回复法力值',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70410','zhCN','施法状态: 每5秒回复法力值',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70411','zhCN','急速',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70412','zhCN','命中',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70413','zhCN','专精',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70414','zhCN','物理 护甲穿透',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70415','zhCN','法术 护甲穿透',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70416','zhCN','%',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70417','zhCN','神圣',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70418','zhCN','火焰',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70419','zhCN','自然',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70420','zhCN','冰霜',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70421','zhCN','暗影',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70422','zhCN','奥术',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70423','zhCN','抗性',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70424','zhCN','指令状态',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70425','zhCN','跟随',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70426','zhCN','攻击',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70427','zhCN','停留',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70428','zhCN','重置',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70429','zhCN','完全停止',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70430','zhCN','跟随距离',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70431','zhCN','天赋',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70432','zhCN','机器人主职务',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70433','zhCN','机器人材料采集',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70434','zhCN','PvP 杀敌',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70435','zhCN','玩家',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70436','zhCN','已死亡 ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70437','zhCN',' 次',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70438','zhCN','%s (机器人)渐渐冷静了下来。',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70439','zhCN','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70440','zhCN','你真的想冒险引起',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70441','zhCN','对你的注意?',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70442','zhCN','<请投币>',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70443','zhCN','你想吸引',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70444','zhCN','<试着发出邀请...>',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70445','zhCN','你想雇佣',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70446','zhCN','<雇佣机器人>',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70447','zhCN','机器人 装备 管理...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70448','zhCN','机器人 职责 管理...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70449','zhCN','机器人 队形 管理...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70450','zhCN','机器人 技能 管理...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70451','zhCN','机器人 天赋 管理...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70452','zhCN','使用消耗品、合剂等...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70453','zhCN','<创建队伍>',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70454','zhCN','<创建队伍(所有机器人)>',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70455','zhCN','<加入队伍>',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70456','zhCN','<将所有机器人加入队伍>',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70457','zhCN','<移出队伍>',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70458','zhCN','跟着我!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70459','zhCN','原地守候!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70460','zhCN','停下来,什么也别做!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70461','zhCN','给我一些食物',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70462','zhCN','给我一些水',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70463','zhCN','召唤一个魔法餐桌!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70464','zhCN','帮我开个锁',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70465','zhCN','给我一颗治疗石',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70466','zhCN','召唤一个灵魂之井!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70467','zhCN','给你的武器重新上毒...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70468','zhCN','<选择毒药 (主手武器)>',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70469','zhCN','<选择毒药 (副手武器)>',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70470','zhCN','元素武器附魔...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70471','zhCN','<选择附魔 (主手武器)>',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70472','zhCN','<选择附魔 (副手武器)>',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70473','zhCN','取消你的变形形态',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70474','zhCN','<选择宠物类型>',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70475','zhCN','你被解雇了!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70476','zhCN','你确定要解雇',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70477','zhCN','你可别后悔...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70478','zhCN','该死的,振作起来!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70479','zhCN','<职业介绍>',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70480','zhCN','没事了',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70481','zhCN','距离',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70482','zhCN','返回',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70483','zhCN','<自动>',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70484','zhCN','<解散宠物>',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70485','zhCN','召唤随机宠物 (狡诈型)',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70486','zhCN','召唤随机宠物 (残暴型)',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70487','zhCN','召唤随机宠物 (坚韧型)',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70488','zhCN','让我看看你的装备',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70489','zhCN','自动筛选可用装备...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70490','zhCN','主手武器...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70491','zhCN','副手武器...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70492','zhCN','远程武器...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70493','zhCN','圣物...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70494','zhCN','头部...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70495','zhCN','肩部...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70496','zhCN','胸部...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70497','zhCN','腰带...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70498','zhCN','腿部...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70499','zhCN','脚...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70500','zhCN','手腕...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70501','zhCN','手...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70502','zhCN','披风...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70503','zhCN','衬衣...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70504','zhCN','戒指1...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70505','zhCN','戒指2...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70506','zhCN','饰品1...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70507','zhCN','饰品2...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70508','zhCN','颈部...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70509','zhCN','卸下全部装备(退回到背包)',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70510','zhCN','刷新机器人外观',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70511','zhCN','只有外观,无实际效果',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70512','zhCN','已装备',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70513','zhCN','没有可供选择的装备',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70514','zhCN','使用你的旧装备',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70515','zhCN','卸下这件装备',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70516','zhCN','额...我没有适合你的装备了...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70517','zhCN','收集材料',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70518','zhCN','技能状态...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70519','zhCN','管理可用技能...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70520','zhCN','使用 ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70521','zhCN','刷新',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70522','zhCN','伤害类技能...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70523','zhCN','控制类技能...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70524','zhCN','治疗类技能...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70525','zhCN','其他技能...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70526','zhCN',' 发出了一阵摩擦声,并开始跟随着 ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70527','zhCN','在他的雇主把他解雇之前, %s不能加入你的队伍。',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70528','zhCN','在你到达60级以前, %s不会加入你们',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70529','zhCN','在你到达55级以前, %s不会加入你们',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70530','zhCN','在你到达40级以前, %s不会加入你们',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70531','zhCN','在你到达20级以前, %s不会加入你们',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70532','zhCN','你最多只能招募%u个机器人!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70533','zhCN','你没有足够的现金',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70534','zhCN','你最多只能招募%u位%u。',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70535','zhCN','无法退还装备 %u (%s)! 无法解雇机器人!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70536','zhCN','当前设定',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70537','zhCN','攻击距离',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70538','zhCN','最小远程攻击距离...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70539','zhCN','最大远程攻击距离...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70540','zhCN','设定攻击距离',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70541','zhCN','移除增益魔法...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70542','zhCN','修正属性...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70543','zhCN','因为某些原因,无法卸下 %s 这件装备将发到你的邮箱。',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70544','zhCN','坦克',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70545','zhCN','远程',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70546','zhCN','采矿',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70547','zhCN','草药',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70548','zhCN','剥皮',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70549','zhCN','工程学',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70550','zhCN','由于长时间未上线,已自动解除雇佣状态。',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70551','zhCN','机器人系统已被禁用,请联系管理员。',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70552','zhCN','%s 不能假如你的队伍,已经有雇主了: %s',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70553','zhCN','%s 正在传送中,不能加入你的队伍',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70554','zhCN','守护',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70555','zhCN','灵猴',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70556','zhCN','雄鹰守护',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70557','zhCN','猎豹守护',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70558','zhCN','蝰蛇守护',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70559','zhCN','野兽守护',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70560','zhCN','豹群守护',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70561','zhCN','野性守护',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70562','zhCN','龙鹰守护',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70563','zhCN','无守护',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70564','zhCN','光环',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70565','zhCN','虔诚',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70566','zhCN','专注',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70567','zhCN','火焰抗性',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70568','zhCN','冰霜抗性',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70569','zhCN','暗影抗性',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70570','zhCN','惩戒',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70571','zhCN','十字军',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70572','zhCN','无光环',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70573','zhCN','减速药膏',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70574','zhCN','速效药膏',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70575','zhCN','致命药膏',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70576','zhCN','致伤药膏',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70577','zhCN','麻痹药膏',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70578','zhCN','麻醉药膏',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70579','zhCN','无',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70580','zhCN','火舌武器',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70581','zhCN','冰封武器',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70582','zhCN','风怒武器',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70583','zhCN','大地生命武器',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70584','zhCN','我需要你的服务。',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70585','zhCN','你不能再雇佣更多的机器人了。',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70586','zhCN','你希望雇佣',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70587','zhCN','现在正在忙着,请稍后再试。',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70588','zhCN','很高兴和你进行交易。',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70589','zhCN','战士',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70590','zhCN','圣骑士',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70591','zhCN','法师',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70592','zhCN','牧师',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70593','zhCN','术士',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70594','zhCN','德鲁伊',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70595','zhCN','死亡骑士',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70596','zhCN','盗贼',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70597','zhCN','萨满',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70598','zhCN','猎人',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70599','zhCN','剑圣',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70600','zhCN','黑曜石毁灭者',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70601','zhCN','大法师',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70602','zhCN','恐惧魔王',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70603','zhCN','破法者',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70604','zhCN','黑暗游侠',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70605','zhCN','战士',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70606','zhCN','圣骑士',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70607','zhCN','法师',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70608','zhCN','牧师',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70609','zhCN','术士',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70610','zhCN','德鲁伊',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70611','zhCN','死亡骑士',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70612','zhCN','盗贼',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70613','zhCN','萨满',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70614','zhCN','猎人',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70615','zhCN','剑圣',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70616','zhCN','黑曜石毁灭者',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70617','zhCN','大法师',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70618','zhCN','恐惧魔王',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70619','zhCN','破法者',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70620','zhCN','黑暗游侠',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70621','zhCN','男',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70622','zhCN','女',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70623','zhCN','人类',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70624','zhCN','兽人',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70625','zhCN','矮人',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70626','zhCN','暗夜精灵',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70627','zhCN','亡灵',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70628','zhCN','牛头人',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70629','zhCN','侏儒',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70630','zhCN','地精',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70631','zhCN','血精灵',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70632','zhCN','德莱尼',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70633','zhCN','未知',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70634','zhCN','自动拾取',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70635','zhCN','|cff9d9d9d灰色|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70636','zhCN','|cffffffff白色|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70637','zhCN','|cff1eff00绿色|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70638','zhCN','|cff0070dd蓝色|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70639','zhCN','|cffa335ee紫色|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70640','zhCN','|cffff8000橙色|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70641','zhCN','参与行为',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70642','zhCN','|延迟攻击时间|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70643','zhCN','|延迟治疗时间|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70644','zhCN','|秒|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70645','zhCN','|副坦克|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70646','zhCN','|亡灵法师们|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70647','zhCN','|亡灵法师|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70648','zhCN','|攻击方向|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70649','zhCN','|正常|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70650','zhCN','|避免正面AOE|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70651','zhCN','|NIY|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70652','zhCN','|你确定这会奏效吗?最好是世界上最好的水....|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70653','zhCN','|看来你真的需要喝点淡水。|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70654','zhCN','|海女巫们|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70655','zhCN','|海女巫|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70656','zhCN','|每点伤害的法力|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70657','zhCN','|每点魔法的伤害|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70658','zhCN','幻化...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70659','zhCN','禁用战斗定位',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70660','zhCN','优先目标',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70661','zhCN','机器人装备银行...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70662','zhCN','存款项目...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70663','zhCN','撤回物品...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70664','zhCN','银行是空的',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70665','zhCN','前一页',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70666','zhCN','下一页',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70667','zhCN','你真的想花这么多钱让地穴领主再次动起来吗?',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70668','zhCN','我很怀疑你现在的状态,但我愿意带领你并帮助你恢复力量。',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70669','zhCN','地穴领主们|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70670','zhCN','|地穴领主|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70671','zhCN','反射',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70672','zhCN','蝗虫',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70673','zhCN','治疗目标生命阈值',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70674','zhCN','我需要传送门',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70675','zhCN','暴风城',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70676','zhCN','铁炉堡',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70677','zhCN','达纳苏斯',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70678','zhCN','埃索达',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70679','zhCN','奥格瑞玛',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70680','zhCN','幽暗城',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70681','zhCN','雷霆崖',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70682','zhCN','银月城',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70683','zhCN','沙塔斯城',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70684','zhCN','达拉然',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); \ No newline at end of file diff --git a/sql/Bots/locales/zhTW/npc_text_locale.sql b/sql/Bots/locales/zhTW/npc_text_locale.sql new file mode 100644 index 000000000..755337d83 --- /dev/null +++ b/sql/Bots/locales/zhTW/npc_text_locale.sql @@ -0,0 +1,410 @@ +DELETE FROM `npc_text_locale` WHERE `Locale`='zhTW' AND `ID` BETWEEN '70000' AND '71000'; +INSERT INTO `npc_text_locale` (`ID`, `Locale`, `Text0_0`, `Text0_1`, `Text1_0`, `Text1_1`, `Text2_0`, `Text2_1`, `Text3_0`, `Text3_1`, `Text4_0`, `Text4_1`, `Text5_0`, `Text5_1`, `Text6_0`, `Text6_1`, `Text7_0`, `Text7_1`) +VALUES +('70001','zhTW','我願意為你奉獻一切。',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70002','zhTW','你需要什麼嗎?',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70003','zhTW','凡人,通常情況下,我會立刻殺了你這混蛋!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70004','zhTW','<你看著這個黑曜石毀滅者。它看起來有點與眾不同,它並沒有因為你的傷害而消失,在仔細檢查過後,你注意到它的背部有一個小口。>',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70005','zhTW','<毀滅者盯著你,發出低沉的咆哮。>',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70006','zhTW','你驚訝嗎,凡人?作為一名納斯雷茲姆,我不得不尋求盟友,你看起來至少可以逗我開心。',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70007','zhTW','現在怎麼樣了,凡人?',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70008','zhTW','你就不能讓我一個人靜一靜嗎? <唉...>',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70009','zhTW','現在如何?',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70101','zhTW','|cffff3300劍聖|r$b|cffdd6600-=致敬魔獸爭霸III=-|r$B$B\\\"劍聖,以前是燃燒之刃家族的成員,現在是部落中的精銳戰士\".$B$B主要屬性:敏捷.$B$B疾風步.讓劍聖隱形,並在一段時間內移動得更快.當劍聖攻擊一個單位以打破隱身狀態時,他將造成額外的傷害.$B$B鏡像.製造劍聖幻象迷惑敵人,並驅散劍聖的所有魔法.$B$B爆擊(被動).有15%的機率在他的攻擊中造成致命的x2(x3,x4)倍的正常傷害.$B$B劍刃風暴. 給予魔法免疫並對周圍所有敵人造成傷害.$B$B',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70102','zhTW','|cff9900cc黑曜石毀滅者|r$b|cffdd6600-=致敬魔獸爭霸III=-|r$B$B\\\"黑曜石毀滅者,對魔法有著無法滿足的渴望\\\".$B$B高護甲,非常高的抵抗力,部分免疫魔法,隨著時間的推移失去法力\\並且不受益於被動法力再生效果,鎧甲,雙持魔杖,造成暗影法術傷害,沒有物理攻擊\\法術傷害加成:50%攻擊力+200% 智力增加魔杖傷害.$B$B吞噬魔法.從敵人身上驅散最多2個魔法效果,從盟友身上驅散最多2個魔法效果和最多2個詛咒,並在20碼範圍內傷害召喚單位.每次驅散效果回復20%法力和5%生命,7秒冷卻.$B$B暗影爆炸.強化攻擊,增加飛濺傷害.$B$B吸取法力.從隨機友方單位吸取所有法力(受施法者法力限制).$B$B補充法力.給周圍15碼範圍內的團隊和團隊成員通電,使其法力值達到最大值的1%,影響最多10個目標,冷卻3秒.$B$B再生光環.治療周圍15碼範圍內的團隊和團隊成員,使其生命值降低施法者法力上限的2%,最多影響10個目標,冷卻3秒.$B$B暗影護甲(被動).恢復相當於所受傷害百分比的法力值.$B$B',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70103','zhTW','|cff0000dd大魔導師|r$b|cffdd6600-=致敬魔獸爭霸III=-|r$B$B\\\"大法師,無法用言語來形容的暴雪親兒子\\\".$B$B法術傷害降低35%,部分免疫控制效果,布甲,造成火/冰法術傷害,無物理攻擊,法術能量加成:100%智力.主要屬性:智力.$B$B暴風雪!典型的暴風雪,只是稍微強大一點,6秒冷卻.召喚水元素攻擊大法師的敵人1分鐘,冷卻20秒.$B$B光輝光環.增加10%的法力上限,大大提高40碼範圍內己方和隊友的法力回復.$B$B大規模傳送.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70104','zhTW','|cff9900cc恐懼魔王|r$b|cffdd6600-=致敬魔獸爭霸III=-|r$B$B\\\"恐懼魔王,擁有黑暗力量和精神控制力的強大惡魔\\\".$B$B高護甲,高抵抗,部分免疫控制效果,所受傷害加速法術能量,鎧甲,造成近戰/暗影法術傷害,對癱瘓目標造成額外傷害,法術能量加成:200%力量.主要屬性:力量.$B$B腐蝕蟲群.派一大群蝙蝠與混亂魔法結合,對面前錐形範圍的敵人造成傷害,無法暴擊,冷卻10秒.$B$B沉睡.使敵方目標睡眠60秒,並允許下次對該目標的物理攻擊忽視護甲,造成的直接傷害將喚醒目標,冷卻6秒.$B$B吸血鬼的光環.增加5%的物理暴擊傷害,治療40碼範圍內的團隊和突襲成員受到近戰物理攻擊和腐肉群傷害的百分比(驚懼領主100%,其他人25%),沒有威脅.$B$B召喚地獄惡魔.召喚一個來自天空的惡魔,惡魔對魔法有很強的抵抗力並且持續180秒,180秒的冷卻時間.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70105','zhTW','|cff0000dd破法者|r$b|cffdd6600-=致敬魔獸爭霸III=-|r$B$B\\\"破法者,一個被訓練來破壞和扭曲魔法能量的精靈戰士\\\".$B$B法術傷害減少75%,部分免疫控制效果,護甲降低-30%,鎧甲,近戰/秘法傷害,法術能量加成:200%力量.主要屬性:力量.$B$B魔法偷取.從敵人身上偷取一個有益的法術並施於附近的盟友,或者從盟友身上移除一個消極的法術並施於附近的敵人,影響魔法和詛咒效果,3秒冷卻.$B$B能量竊取(被動).成功的近戰攻擊消耗目標的法力,相當於造成的傷害(由法術力增加),造成秘法傷害.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70106','zhTW','|cff9900cc黑暗遊俠|r$b|cffdd6600-=致敬魔獸爭霸III=-|r$B$B\\\"黑暗遊俠,強行起死回生的奎爾薩拉斯的前遊俠\\\".$B$B法術傷害減少35%,不死系,部分免疫控制效果,皮甲/布甲,造成物理/暗影法術傷害,不產生威脅,法術力量加成:50%智力.主要屬性:敏捷.$B$B沉默.使一個敵人和最多4個附近目標沉默8秒,15秒冷卻.$B$B黑箭.射出一支詛咒之箭,造成150%武器傷害和額外的暗影傷害.如果受影響的目標死於黑暗遊俠的傷害,黑暗僕從將從屍體中產生(最多5個僕從,持續時間80秒,只對人形、野獸和龍類有效).如果目標生命低於20%,造成5倍傷害.$B$B吸取生命.每秒吸取一個敵人的生命值,持續5秒,治療黑暗遊俠消耗生命值的200%.',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70107','zhTW','|cff9900cc死靈法師|r$b|cffdd6600-=魔獸爭霸III / 暗黑破壞神II致敬=-|r$B$B受到的法術傷害減少20%,部分免疫控制效果,布甲,造成暗影法術傷害,沒有物理攻擊,法術強度加成: 智力的100%。主屬性: 智力。$B$B復活死者: 從屍體上復活2個骷髏(最多6個骷髏,持續65秒,只對人型生物、野獸和龍類有效)。$B$B邪惡狂熱: 增加目標的近戰攻擊速度75%,但持續消耗生命。持續45秒。不能取消。30級解鎖。$B$B屍體爆炸。讓屍體爆炸,對周圍所有敵人造成相當於死亡單位最大生命值35%至75%的傷害(取決於死靈法師的等級)。這種傷害不產生威脅。40級解鎖。$B$B殘廢: 減少目標的移動速度、近戰攻擊速度和總力量50%,持續60秒。50級解鎖。$B$B',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70108','zhTW','|cff0000dd海巫|r$b|cffdd6600-=魔獸爭霸III致敬=-|r$B$B"海洋中的凶猛鱗皮居民,常與巨大風暴的到來聯繫在一起。"$B$B受到的法術傷害減少30%,部分免疫控制效果,布甲,造成物理/冰霜法術/風暴法術傷害,攻擊力加成: 敏捷x2,法術強度加成: 智力的200%。主屬性: 智力。$B$B分叉閃電: 召喚一道閃電錐,傷害敵人。從2到所有目標(取決於海巫的等級),使它們昏迷2秒。這種傷害不產生威脅。$B$B冰霜箭。用冰霜法術為箭矢增加額外傷害,減慢目標的移動、攻擊和施法速度30%至70%(取決於海巫的等級)。$B$B法力護盾: 創造一個護盾,使用海巫的法力吸收100%的(未減免的)進來傷害。效果範圍從每10法力吸收1傷害到每1法力吸收10傷害(取決於海巫的等級)。$B$B龍捲風: 召喚一個猛烈的龍捲風,傷害並減慢附近敵方單位的速度,有時完全使它們失去行動能力。龍捲風在戶外隨時間增長,增加傷害和影響區域,但在室內縮小,迅速消散。60級解鎖。$B$B娜迦(被動): 在水中游泳速度、傷害和躲避機率大幅提升。$B$B',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70109','zhTW','|cff9900cc地穴領主|r$b|cffdd6600-=魔獸爭霸III致敬=-|r$B$B"古老的龐然大物,曾是艾澤拉斯之王之一,現在是巫妖王麾下最強大的亡靈戰士之一。"$B$B高度護甲,增加的抗性,部分免疫控制效果,免疫基於毒素的效果,鎖甲/板甲,造成近戰/暗影法術傷害,法術強度加成: 力量的200%。主屬性: 力量。$B$B穿刺。地穴領主用他巨大的爪子猛擊地面,向前錐形射出尖刺,造成傷害並將敵方單位掀入空中,使他們暈眩。20級解鎖。$B$B刺甲: 地穴領主的幾丁質護甲增加傷害抗性,並將15%至50%的傷害反彈給近戰攻擊者。$B$B腐屍甲蟲。地穴領主從敵人的新鮮屍體上產生一個腐屍甲蟲來攻擊他的敵人。甲蟲是永久的,但不會恢復生命值,一次最多控制6個。更高等級允許地穴領主召喚更強大的甲蟲。10級解鎖。$B$B蝗蟲群: 地穴領主釋放一群20-40隻(取決於地穴領主的等級)憤怒的蝗蟲,啃咬並撕扯附近的敵方單位,減少他們的移動或攻擊能力。當它們啃食敵人肉體時,它們將其轉化為一種物質,當它們返回時恢復地穴領主的生命值。40級解鎖。$B$B',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70201','zhTW','並不總是有人願意為錢去賣命!',NULL,'我們的服務遍布艾澤拉斯的每個角落!',NULL,'很高興為您服務,我這里有最能打的傢伙!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70202','zhTW','雇傭兵向來十分搶手;以下是現在你能雇傭的人:',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70203','zhTW','雇傭兵向來十分搶手;以下是現在你能雇傭的人:',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70204','zhTW','看來現在沒有空閒的雇傭兵,你過些時候再來看看吧。',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70300','zhTW','去死!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70301','zhTW','正在復活你!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70302','zhTW','正在復活 ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70303','zhTW','你的機器人',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70304','zhTW','的機器人',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70305','zhTW','我現在還不能製造魔法水',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70306','zhTW','我現在還不能製造魔法食物',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70307','zhTW','我還不能那樣做',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70308','zhTW','給你...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70309','zhTW','已禁用',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70310','zhTW','還沒有準備好',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70311','zhTW','無效的物品類型',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70312','zhTW','失敗',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70313','zhTW','完成',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70314','zhTW','我沒有變形',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70315','zhTW','我沒有治療石',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70316','zhTW','我還不能製造治療石',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70317','zhTW','搞什麼鬼,我沒有鎖可開!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70318','zhTW','我的技能等級還不夠高',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70319','zhTW','正在將我的天賦切換為',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70320','zhTW','武器',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70321','zhTW','狂怒',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70322','zhTW','防護',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70323','zhTW','懲戒',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70324','zhTW','野獸控制',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70325','zhTW','射擊',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70326','zhTW','生存',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70327','zhTW','刺殺',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70328','zhTW','戰鬥',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70329','zhTW','敏銳',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70330','zhTW','戒律',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70331','zhTW','神聖',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70332','zhTW','暗影',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70333','zhTW','鮮血',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70334','zhTW','冰霜',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70335','zhTW','邪惡',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70336','zhTW','元素',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70337','zhTW','增強',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70338','zhTW','恢復',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70339','zhTW','秘法',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70340','zhTW','火焰',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70341','zhTW','痛苦',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70342','zhTW','惡魔學識',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70343','zhTW','毀滅',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70344','zhTW','平衡',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70345','zhTW','野性戰鬥',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70346','zhTW','未知',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70347','zhTW','滾開! 懦夫。',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70348','zhTW',' 不方便。',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70349','zhTW','我不會把我寶貴的時間浪費在這些瑣事上!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70350','zhTW','NIY',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70351','zhTW','NIY',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70352','zhTW','NIY',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70353','zhTW','我準備好了',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70354','zhTW','走開!我已經有雇主了!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70355','zhTW','未知',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70356','zhTW','在你身上!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70357','zhTW','在我身上!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70358','zhTW',' 對 ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70359','zhTW',' 已使用!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70360','zhTW','坦克機器人',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70361','zhTW','職業',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70362','zhTW','玩家',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70363','zhTW','雇主',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70364','zhTW','無',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70365','zhTW','級別',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70366','zhTW','天賦',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70367','zhTW','被動',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70368','zhTW','隱藏',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70369','zhTW','已知',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70370','zhTW','能力',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70371','zhTW','力量',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70372','zhTW','敏捷',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70373','zhTW','耐力',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70374','zhTW','智力',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70375','zhTW','精神',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70376','zhTW','未知屬性',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70377','zhTW','共',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70378','zhTW','近戰攻擊強度',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70379','zhTW','遠程攻擊強度',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70380','zhTW','護甲',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70381','zhTW','暴擊',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70382','zhTW','防禦',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70383','zhTW','未命中',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70384','zhTW','躲閃',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70385','zhTW','招架',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70386','zhTW','格擋',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70387','zhTW','盾牌格擋值',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70388','zhTW','近戰防禦',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70389','zhTW','法術防禦',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70390','zhTW','主手攻擊傷害範圍',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70391','zhTW','主手攻擊傷害加成',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70392','zhTW','主手攻擊速度',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70393','zhTW','副手攻擊傷害範圍',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70394','zhTW','副手攻擊傷害加成',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70395','zhTW','副手攻擊速度',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70396','zhTW','遠程攻擊傷害範圍',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70397','zhTW','遠程攻擊傷害加成',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70398','zhTW','遠程攻擊速度',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70399','zhTW','最小',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70400','zhTW','最大',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70401','zhTW','輸出',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70402','zhTW','基礎生命值',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70403','zhTW','最大生命值',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70404','zhTW','基礎法力值',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70405','zhTW','最大法力值',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70406','zhTW','當前法力值',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70407','zhTW','法術強度',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70408','zhTW','每5秒恢復生命',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70409','zhTW','非施法狀態:每5秒回復法力值',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70410','zhTW','施法狀態:每5秒回復法力值',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70411','zhTW','急速',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70412','zhTW','命中',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70413','zhTW','專精',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70414','zhTW','物理 護甲穿透',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70415','zhTW','法術 護甲穿透',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70416','zhTW','%',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70417','zhTW','神聖',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70418','zhTW','火焰',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70419','zhTW','自然',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70420','zhTW','冰霜',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70421','zhTW','暗影',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70422','zhTW','秘法',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70423','zhTW','抗性',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70424','zhTW','指令狀態',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70425','zhTW','跟隨',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70426','zhTW','攻擊',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70427','zhTW','停留',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70428','zhTW','重置',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70429','zhTW','完全停止',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70430','zhTW','跟隨距離',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70431','zhTW','天賦',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70432','zhTW','機器人主職務',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70433','zhTW','機器人材料採集',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70434','zhTW','PvP 殺敵',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70435','zhTW','玩家',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70436','zhTW','已死亡 ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70437','zhTW',' 次',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70438','zhTW','%s (機器人)漸漸冷靜了下來。',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70439','zhTW','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70440','zhTW','你真的想冒險引起',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70441','zhTW','對你的注意?',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70442','zhTW','<請投幣>',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70443','zhTW','你想吸引',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70444','zhTW','<試著發出邀請...>',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70445','zhTW','你想雇傭',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70446','zhTW','<雇傭機器人>',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70447','zhTW','機器人 裝備 管理...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70448','zhTW','機器人 職責 管理...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70449','zhTW','機器人 隊形 管理...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70450','zhTW','機器人 技能 管理...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70451','zhTW','機器人 天賦 管理...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70452','zhTW','使用消耗品、合劑等...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70453','zhTW','<創建隊伍>',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70454','zhTW','<創建隊伍(所有機器人)>',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70455','zhTW','<加入隊伍>',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70456','zhTW','<將所有機器人加入隊伍>',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70457','zhTW','<移出隊伍>',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70458','zhTW','跟著我!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70459','zhTW','原地守候!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70460','zhTW','停下來,什麼也別做!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70461','zhTW','給我一些食物',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70462','zhTW','給我一些水',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70463','zhTW','召喚一個魔法餐桌!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70464','zhTW','幫我開個鎖',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70465','zhTW','給我一顆治療石',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70466','zhTW','召喚一個靈魂之井!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70467','zhTW','給你的武器重新上毒...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70468','zhTW','<選擇毒藥 (主手武器)>',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70469','zhTW','<選擇毒藥 (副手武器)>',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70470','zhTW','元素武器附魔...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70471','zhTW','<選擇附魔 (主手武器)>',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70472','zhTW','<選擇附魔 (副手武器)>',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70473','zhTW','取消你的變形形態',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70474','zhTW','<選擇寵物類型>',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70475','zhTW','你被解雇了!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70476','zhTW','你確定要解雇',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70477','zhTW','你可別後悔...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70478','zhTW','該死的,振作起來!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70479','zhTW','<職業介紹>',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70480','zhTW','沒事了',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70481','zhTW','距離',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70482','zhTW','返回',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70483','zhTW','<自動>',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70484','zhTW','<解散寵物>',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70485','zhTW','召喚隨機寵物 (狡詐型)',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70486','zhTW','召喚隨機寵物 (殘暴型)',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70487','zhTW','召喚隨機寵物 (堅韌型)',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70488','zhTW','讓我看看你的裝備',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70489','zhTW','自動篩選可用裝備...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70490','zhTW','主手武器...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70491','zhTW','副手武器...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70492','zhTW','遠程武器...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70493','zhTW','聖物...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70494','zhTW','頭部...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70495','zhTW','肩部...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70496','zhTW','胸部...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70497','zhTW','腰帶...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70498','zhTW','腿部...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70499','zhTW','腳...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70500','zhTW','手腕...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70501','zhTW','手...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70502','zhTW','披風...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70503','zhTW','襯衣...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70504','zhTW','戒指1...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70505','zhTW','戒指2...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70506','zhTW','飾品1...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70507','zhTW','飾品2...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70508','zhTW','頸部...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70509','zhTW','卸下全部裝備(退回到背包)',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70510','zhTW','刷新機器人外觀',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70511','zhTW','只有外觀,無實際效果',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70512','zhTW','已裝備',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70513','zhTW','沒有可供選擇的裝備',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70514','zhTW','使用你的舊裝備',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70515','zhTW','卸下這件裝備',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70516','zhTW','額...我沒有適合你的裝備了...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70517','zhTW','收集材料',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70518','zhTW','技能狀態...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70519','zhTW','管理可用技能...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70520','zhTW','使用 ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70521','zhTW','刷新',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70522','zhTW','傷害類技能...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70523','zhTW','控制類技能...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70524','zhTW','治療類技能...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70525','zhTW','其他技能...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70526','zhTW',' 發出了一陣摩擦聲,並開始跟隨著 ',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70527','zhTW','在他的雇主把他解雇之前, %s不能加入你的隊伍。',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70528','zhTW','在你到達60級以前, %s不會加入你們',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70529','zhTW','在你到達55級以前, %s不會加入你們',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70530','zhTW','在你到達40級以前, %s不會加入你們',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70531','zhTW','在你到達20級以前, %s不會加入你們',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70532','zhTW','你最多只能招募%u個機器人!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70533','zhTW','你沒有足夠的現金',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70534','zhTW','你最多只能招募%u位%u。',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70535','zhTW','無法退還裝備 %u (%s)! 無法解雇機器人!',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70536','zhTW','當前設定',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70537','zhTW','攻擊距離',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70538','zhTW','最小遠程攻擊距離...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70539','zhTW','最大遠程攻擊距離...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70540','zhTW','設定攻擊距離',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70541','zhTW','移除增益魔法...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70542','zhTW','修正屬性...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70543','zhTW','因為某些原因,無法卸下 %s 這件裝備將發到你的郵箱。',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70544','zhTW','坦克',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70545','zhTW','遠程',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70546','zhTW','採礦',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70547','zhTW','草藥',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70548','zhTW','剝皮',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70549','zhTW','工程學',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70550','zhTW','由於長時間未上線,已自動解除雇傭狀態。',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70551','zhTW','機器人系統已被停用,請聯系管理員。',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70552','zhTW','%s 不能假如你的隊伍,已經有雇主了:%s',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70553','zhTW','%s 正在傳送中,不能加入你的隊伍',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70554','zhTW','守護',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70555','zhTW','靈猴',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70556','zhTW','雄鷹守護',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70557','zhTW','獵豹守護',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70558','zhTW','蝮蛇守護',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70559','zhTW','野獸守護',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70560','zhTW','豹群守護',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70561','zhTW','野性守護',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70562','zhTW','龍鷹守護',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70563','zhTW','無守護',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70564','zhTW','光環',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70565','zhTW','虔誠',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70566','zhTW','專注',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70567','zhTW','火焰抗性',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70568','zhTW','冰霜抗性',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70569','zhTW','暗影抗性',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70570','zhTW','懲戒',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70571','zhTW','十字軍',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70572','zhTW','無光環',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70573','zhTW','減速藥膏',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70574','zhTW','速效藥膏',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70575','zhTW','致命藥膏',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70576','zhTW','致傷藥膏',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70577','zhTW','麻痹藥膏',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70578','zhTW','麻醉藥膏',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70579','zhTW','無',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70580','zhTW','火舌武器',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70581','zhTW','冰封武器',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70582','zhTW','風怒武器',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70583','zhTW','大地生命武器',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70584','zhTW','我需要你的服務。',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70585','zhTW','你不能再雇傭更多的機器人了。',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70586','zhTW','你希望雇傭',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70587','zhTW','現在正在忙著,請稍後再試。',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70588','zhTW','很高興和你進行交易。',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70589','zhTW','戰士',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70590','zhTW','聖騎士',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70591','zhTW','法師',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70592','zhTW','牧師',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70593','zhTW','術士',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70594','zhTW','德魯伊',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70595','zhTW','死亡騎士',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70596','zhTW','盜賊',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70597','zhTW','薩滿',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70598','zhTW','獵人',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70599','zhTW','劍聖',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70600','zhTW','黑曜石毀滅者',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70601','zhTW','大法師',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70602','zhTW','恐懼魔王',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70603','zhTW','破法者',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70604','zhTW','黑暗遊俠',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70605','zhTW','戰士',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70606','zhTW','聖騎士',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70607','zhTW','法師',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70608','zhTW','牧師',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70609','zhTW','術士',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70610','zhTW','德魯伊',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70611','zhTW','死亡騎士',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70612','zhTW','盜賊',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70613','zhTW','薩滿',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70614','zhTW','獵人',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70615','zhTW','劍聖',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70616','zhTW','黑曜石毀滅者',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70617','zhTW','大法師',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70618','zhTW','恐懼魔王',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70619','zhTW','破法者',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70620','zhTW','黑暗遊俠',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70621','zhTW','男',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70622','zhTW','女',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70623','zhTW','人類',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70624','zhTW','獸人',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70625','zhTW','矮人',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70626','zhTW','夜精靈',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70627','zhTW','不死族',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70628','zhTW','牛頭人',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70629','zhTW','地精',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70630','zhTW','食人妖',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70631','zhTW','血精靈',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70632','zhTW','德萊尼',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70633','zhTW','未知',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70634','zhTW','自動拾取',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70635','zhTW','|cff9d9d9d灰色|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70636','zhTW','|cffffffff白色|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70637','zhTW','|cff1eff00綠色|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70638','zhTW','|cff0070dd藍色|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70639','zhTW','|cffa335ee紫色|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70640','zhTW','|cffff8000橙色|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70641','zhTW','參與行為',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70642','zhTW','|延遲攻擊時間|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70643','zhTW','|延遲治療時間|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70644','zhTW','|秒|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70645','zhTW','|副坦克|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70646','zhTW','|死靈法師們|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70647','zhTW','|死靈法師|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70648','zhTW','|攻擊方向|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70649','zhTW','|正常|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70650','zhTW','|避免正面AOE|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70651','zhTW','|NIY|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70652','zhTW','|你確定這會奏效嗎?最好是世界上最好的水....|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70653','zhTW','|看來你真的需要喝點淡水。|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70654','zhTW','|海女巫們|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70655','zhTW','|海女巫|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70656','zhTW','|每點傷害的法力|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70657','zhTW','|每點魔法的傷害|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70658','zhTW','幻化...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70659','zhTW','禁用戰鬥定位',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70660','zhTW','優先目標',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70661','zhTW','機器人裝備銀行...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70662','zhTW','存款項目...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70663','zhTW','撤回物品...',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70664','zhTW','銀行是空的',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70665','zhTW','前一頁',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70666','zhTW','下一頁',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70667','zhTW','你真的想花這麼多錢讓地窟領主再次動起來嗎?',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70668','zhTW','我很懷疑你現在的狀態,但我願意帶領你並幫助你恢復力量。',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70669','zhTW','地窟領主們|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70670','zhTW','|地窟領主|r',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70671','zhTW','反射',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70672','zhTW','蝗蟲',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70673','zhTW','治療目標生命閾值',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70674','zhTW','我需要傳送門',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70675','zhTW','暴風城',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70676','zhTW','鐵爐堡',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70677','zhTW','達納蘇斯',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70678','zhTW','艾克索達',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70679','zhTW','奧格瑞瑪',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70680','zhTW','幽暗城',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70681','zhTW','雷霆崖',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70682','zhTW','銀月城',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70683','zhTW','沙塔斯城',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +('70684','zhTW','達拉然',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); diff --git a/sql/Bots/merge_sqls_auth_unix.sh b/sql/Bots/merge_sqls_auth_unix.sh new file mode 100644 index 000000000..a237789bd --- /dev/null +++ b/sql/Bots/merge_sqls_auth_unix.sh @@ -0,0 +1,2 @@ +#!/bin/bash +cat updates/auth/*.sql > ALL_auth.sql diff --git a/sql/Bots/merge_sqls_auth_windows.bat b/sql/Bots/merge_sqls_auth_windows.bat new file mode 100644 index 000000000..1bc1c0145 --- /dev/null +++ b/sql/Bots/merge_sqls_auth_windows.bat @@ -0,0 +1 @@ +copy /a updates\auth\*.sql /b ALL_auth.sql diff --git a/sql/Bots/merge_sqls_characters_unix.sh b/sql/Bots/merge_sqls_characters_unix.sh new file mode 100644 index 000000000..40f062d93 --- /dev/null +++ b/sql/Bots/merge_sqls_characters_unix.sh @@ -0,0 +1,3 @@ +#!/bin/bash +cat characters_bots.sql > ALL_characters.sql +cat updates/characters/*.sql >> ALL_characters.sql diff --git a/sql/Bots/merge_sqls_characters_windows.bat b/sql/Bots/merge_sqls_characters_windows.bat new file mode 100644 index 000000000..45cf6a33e --- /dev/null +++ b/sql/Bots/merge_sqls_characters_windows.bat @@ -0,0 +1 @@ +copy /a characters_bots.sql + /a updates\characters\*.sql /b ALL_characters.sql diff --git a/sql/Bots/merge_sqls_world_unix.sh b/sql/Bots/merge_sqls_world_unix.sh new file mode 100644 index 000000000..ab7b2b1f1 --- /dev/null +++ b/sql/Bots/merge_sqls_world_unix.sh @@ -0,0 +1,3 @@ +#!/bin/bash +cat *world_*.sql > ALL_world.sql +cat updates/world/*.sql >> ALL_world.sql diff --git a/sql/Bots/merge_sqls_world_windows.bat b/sql/Bots/merge_sqls_world_windows.bat new file mode 100644 index 000000000..26b87e563 --- /dev/null +++ b/sql/Bots/merge_sqls_world_windows.bat @@ -0,0 +1 @@ +copy /a *world_*.sql + /a updates\world\*.sql /b ALL_world.sql diff --git a/sql/Bots/updates/auth/2021_09_14_00_rbac_permissions.sql b/sql/Bots/updates/auth/2021_09_14_00_rbac_permissions.sql new file mode 100644 index 000000000..5a9cb7309 --- /dev/null +++ b/sql/Bots/updates/auth/2021_09_14_00_rbac_permissions.sql @@ -0,0 +1,39 @@ +-- +SET @PERMISSION_START = 70001; +SET @PERMISSION_END = 70033; + +DELETE FROM `rbac_permissions` WHERE id BETWEEN @PERMISSION_START AND @PERMISSION_END; +INSERT INTO `rbac_permissions` (`id`,`name`) VALUES +('70001','Command: npcbot'), +('70002','Command: npcbot add'), +('70003','Command: npcbot remove'), +('70004','Command: npcbot spawn'), +('70005','Command: npcbot move'), +('70006','Command: npcbot delete'), +('70007','Command: npcbot lookup'), +('70008','Command: npcbot revive'), +('70009','Command: npcbot reloadconfig'), +('70010','Command: npcbot info'), +('70011','Command: npcbot hide'), +('70012','Command: npcbot unhide'), +('70013','Command: npcbot recall'), +('70014','Command: npcbot kill'), +('70015','Command: npcbot debug raid'), +('70016','Command: npcbot debug mount'), +('70017','Command: npcbot debug spellvisual'), +('70018','Command: npcbot debug states'), +('70019','Command: npcbot toggle flags'), +('70020','Command: npcbot set faction'), +('70021','Command: npcbot set owner'), +('70022','Command: npcbot set spec'), +('70023','Command: npcbot command standstill'), +('70024','Command: npcbot command stopfully'), +('70025','Command: npcbot command follow'), +('70026','Command: npcbot distance attack short'), +('70027','Command: npcbot distance attack long'), +('70028','Command: npcbot distance attack'), +('70029','Command: npcbot distance'), +('70030','Command: npcbot order cast'), +('70031','Command: npcbot vehicle eject'), +('70032','Command: npcbot dump load'), +('70033','Command: npcbot dump write'); diff --git a/sql/Bots/updates/auth/2021_09_14_01_rbac_linked_permissions.sql b/sql/Bots/updates/auth/2021_09_14_01_rbac_linked_permissions.sql new file mode 100644 index 000000000..555828ce0 --- /dev/null +++ b/sql/Bots/updates/auth/2021_09_14_01_rbac_linked_permissions.sql @@ -0,0 +1,39 @@ +-- +SET @PERMISSION_START = 70001; +SET @PERMISSION_END = 70033; + +DELETE FROM `rbac_linked_permissions` WHERE linkedId BETWEEN @PERMISSION_START AND @PERMISSION_END; +INSERT INTO `rbac_linked_permissions` (`id`,`linkedId`) VALUES +('199','70001'), +('197','70002'), +('197','70003'), +('197','70004'), +('197','70005'), +('197','70006'), +('197','70007'), +('197','70008'), +('197','70009'), +('199','70010'), +('199','70011'), +('199','70012'), +('199','70013'), +('199','70014'), +('197','70015'), +('197','70016'), +('197','70017'), +('197','70018'), +('197','70019'), +('197','70020'), +('197','70021'), +('197','70022'), +('199','70023'), +('199','70024'), +('199','70025'), +('199','70026'), +('199','70027'), +('199','70028'), +('199','70029'), +('199','70030'), +('199','70031'), +('196','70032'), +('196','70033'); diff --git a/sql/Bots/updates/auth/2021_12_29_00_rbac_permissions.sql b/sql/Bots/updates/auth/2021_12_29_00_rbac_permissions.sql new file mode 100644 index 000000000..30491065a --- /dev/null +++ b/sql/Bots/updates/auth/2021_12_29_00_rbac_permissions.sql @@ -0,0 +1,7 @@ +-- +SET @PERMISSION_START = 70034; +SET @PERMISSION_END = 70034; + +DELETE FROM `rbac_permissions` WHERE id BETWEEN @PERMISSION_START AND @PERMISSION_END; +INSERT INTO `rbac_permissions` (`id`,`name`) VALUES +('70034','Command: npcbot spawned'); diff --git a/sql/Bots/updates/auth/2021_12_29_01_rbac_linked_permissions.sql b/sql/Bots/updates/auth/2021_12_29_01_rbac_linked_permissions.sql new file mode 100644 index 000000000..5b960ef79 --- /dev/null +++ b/sql/Bots/updates/auth/2021_12_29_01_rbac_linked_permissions.sql @@ -0,0 +1,7 @@ +-- +SET @PERMISSION_START = 70034; +SET @PERMISSION_END = 70034; + +DELETE FROM `rbac_linked_permissions` WHERE linkedId BETWEEN @PERMISSION_START AND @PERMISSION_END; +INSERT INTO `rbac_linked_permissions` (`id`,`linkedId`) VALUES +('196','70034'); diff --git a/sql/Bots/updates/auth/2022_06_24_00_rbac_permissions.sql b/sql/Bots/updates/auth/2022_06_24_00_rbac_permissions.sql new file mode 100644 index 000000000..916767c49 --- /dev/null +++ b/sql/Bots/updates/auth/2022_06_24_00_rbac_permissions.sql @@ -0,0 +1,7 @@ +-- +SET @PERMISSION_START = 70035; +SET @PERMISSION_END = 70035; + +DELETE FROM `rbac_permissions` WHERE id BETWEEN @PERMISSION_START AND @PERMISSION_END; +INSERT INTO `rbac_permissions` (`id`,`name`) VALUES +('70035','Command: npcbot command walk'); diff --git a/sql/Bots/updates/auth/2022_06_24_01_rbac_linked_permissions.sql b/sql/Bots/updates/auth/2022_06_24_01_rbac_linked_permissions.sql new file mode 100644 index 000000000..a62f6925d --- /dev/null +++ b/sql/Bots/updates/auth/2022_06_24_01_rbac_linked_permissions.sql @@ -0,0 +1,7 @@ +-- +SET @PERMISSION_START = 70035; +SET @PERMISSION_END = 70035; + +DELETE FROM `rbac_linked_permissions` WHERE linkedId BETWEEN @PERMISSION_START AND @PERMISSION_END; +INSERT INTO `rbac_linked_permissions` (`id`,`linkedId`) VALUES +('199','70035'); diff --git a/sql/Bots/updates/auth/2022_07_04_00_rbac_permissions.sql b/sql/Bots/updates/auth/2022_07_04_00_rbac_permissions.sql new file mode 100644 index 000000000..deac7ed3e --- /dev/null +++ b/sql/Bots/updates/auth/2022_07_04_00_rbac_permissions.sql @@ -0,0 +1,7 @@ +-- +SET @PERMISSION_START = 70036; +SET @PERMISSION_END = 70036; + +DELETE FROM `rbac_permissions` WHERE id BETWEEN @PERMISSION_START AND @PERMISSION_END; +INSERT INTO `rbac_permissions` (`id`,`name`) VALUES +('70036','Command: npcbot createnew'); diff --git a/sql/Bots/updates/auth/2022_07_04_01_rbac_linked_permissions.sql b/sql/Bots/updates/auth/2022_07_04_01_rbac_linked_permissions.sql new file mode 100644 index 000000000..dac351bef --- /dev/null +++ b/sql/Bots/updates/auth/2022_07_04_01_rbac_linked_permissions.sql @@ -0,0 +1,7 @@ +-- +SET @PERMISSION_START = 70036; +SET @PERMISSION_END = 70036; + +DELETE FROM `rbac_linked_permissions` WHERE linkedId BETWEEN @PERMISSION_START AND @PERMISSION_END; +INSERT INTO `rbac_linked_permissions` (`id`,`linkedId`) VALUES +('196','70036'); diff --git a/sql/Bots/updates/auth/2022_11_30_00_rbac_permissions.sql b/sql/Bots/updates/auth/2022_11_30_00_rbac_permissions.sql new file mode 100644 index 000000000..9a02a53a5 --- /dev/null +++ b/sql/Bots/updates/auth/2022_11_30_00_rbac_permissions.sql @@ -0,0 +1,7 @@ +-- +SET @PERMISSION_START = 70037; +SET @PERMISSION_END = 70037; + +DELETE FROM `rbac_permissions` WHERE id BETWEEN @PERMISSION_START AND @PERMISSION_END; +INSERT INTO `rbac_permissions` (`id`,`name`) VALUES +('70037','Command: npcbot sendto'); diff --git a/sql/Bots/updates/auth/2022_11_30_rbac_linked_permissions.sql b/sql/Bots/updates/auth/2022_11_30_rbac_linked_permissions.sql new file mode 100644 index 000000000..840f9898f --- /dev/null +++ b/sql/Bots/updates/auth/2022_11_30_rbac_linked_permissions.sql @@ -0,0 +1,7 @@ +-- +SET @PERMISSION_START = 70037; +SET @PERMISSION_END = 70037; + +DELETE FROM `rbac_linked_permissions` WHERE linkedId BETWEEN @PERMISSION_START AND @PERMISSION_END; +INSERT INTO `rbac_linked_permissions` (`id`,`linkedId`) VALUES +('199','70037'); diff --git a/sql/Bots/updates/auth/2023_08_12_00_rbac_linked_permissions.sql b/sql/Bots/updates/auth/2023_08_12_00_rbac_linked_permissions.sql new file mode 100644 index 000000000..9957ad557 --- /dev/null +++ b/sql/Bots/updates/auth/2023_08_12_00_rbac_linked_permissions.sql @@ -0,0 +1,2 @@ +-- +UPDATE `rbac_linked_permissions` SET `id`='197' WHERE (`id`='196') AND (`linkedId`='70034'); diff --git a/sql/Bots/updates/characters/2020_05_15_characters_npcbot_NPCBots_4.7.8a-4.7.27a.sql b/sql/Bots/updates/characters/2020_05_15_characters_npcbot_NPCBots_4.7.8a-4.7.27a.sql new file mode 100644 index 000000000..ab44f6517 --- /dev/null +++ b/sql/Bots/updates/characters/2020_05_15_characters_npcbot_NPCBots_4.7.8a-4.7.27a.sql @@ -0,0 +1 @@ +ALTER TABLE `characters_npcbot` ADD `spec` tinyint(3) unsigned NOT NULL DEFAULT '1' AFTER `roles`; diff --git a/sql/Bots/updates/characters/2020_06_21_characters_npcbot_43fceb98.sql b/sql/Bots/updates/characters/2020_06_21_characters_npcbot_43fceb98.sql new file mode 100644 index 000000000..368e5a977 --- /dev/null +++ b/sql/Bots/updates/characters/2020_06_21_characters_npcbot_43fceb98.sql @@ -0,0 +1,172 @@ +DROP TEMPORARY TABLE IF EXISTS `npcbot_extras_temp`; + +CREATE TEMPORARY TABLE `npcbot_extras_temp` ( + `entry` MEDIUMINT(8) UNSIGNED NOT NULL, + `class` TINYINT(3) UNSIGNED NOT NULL DEFAULT '1', + PRIMARY KEY (`entry`) +) ENGINE=INNODB DEFAULT CHARSET=utf8; + +INSERT INTO `npcbot_extras_temp` (`entry`,`class`) VALUES +('70001','1'),('70002','1'),('70003','1'),('70004','1'),('70005','1'), +('70006','1'),('70007','1'),('70008','1'),('70009','1'),('70010','1'), +('70011','1'),('70012','1'),('70013','1'),('70014','1'),('70015','1'), +('70016','1'),('70017','1'),('70018','1'),('70019','1'),('70020','1'), +('70021','1'),('70022','1'),('70023','1'),('70024','1'),('70025','1'), +('70026','1'),('70027','1'),('70028','1'),('70029','1'),('70030','1'), +('70031','1'),('70032','1'),('70033','1'),('70034','1'),('70035','1'), +('70036','1'),('70037','1'),('70038','1'),('70051','2'),('70052','2'), +('70053','2'),('70054','2'),('70055','2'),('70056','2'),('70057','2'), +('70058','2'),('70059','2'),('70060','2'),('70061','2'),('70062','2'), +('70063','2'),('70064','2'),('70065','2'),('70066','2'),('70067','2'), +('70068','2'),('70069','2'),('70070','2'),('70071','2'),('70072','2'), +('70073','2'),('70074','2'),('70101','3'),('70102','3'),('70103','3'), +('70104','3'),('70105','3'),('70106','3'),('70107','3'),('70108','3'), +('70109','3'),('70110','3'),('70111','3'),('70112','3'),('70113','3'), +('70114','3'),('70115','3'),('70116','3'),('70117','3'),('70118','3'), +('70119','3'),('70120','3'),('70121','3'),('70122','3'),('70123','3'), +('70124','3'),('70125','3'),('70126','3'),('70127','3'),('70128','3'), +('70129','3'),('70130','3'),('70131','3'),('70132','3'),('70133','3'), +('70134','3'),('70135','3'),('70136','3'),('70137','3'),('70138','3'), +('70139','3'),('70151','4'),('70152','4'),('70153','4'),('70154','4'), +('70155','4'),('70156','4'),('70157','4'),('70158','4'),('70159','4'), +('70160','4'),('70161','4'),('70162','4'),('70163','4'),('70164','4'), +('70165','4'),('70166','4'),('70167','4'),('70168','4'),('70169','4'), +('70170','4'),('70171','4'),('70172','4'),('70173','4'),('70174','4'), +('70175','4'),('70176','4'),('70177','4'),('70178','4'),('70179','4'), +('70180','4'),('70181','4'),('70201','5'),('70202','5'),('70203','5'), +('70204','5'),('70205','5'),('70206','5'),('70207','5'),('70208','5'), +('70209','5'),('70210','5'),('70211','5'),('70212','5'),('70213','5'), +('70214','5'),('70215','5'),('70216','5'),('70217','5'),('70218','5'), +('70219','5'),('70220','5'),('70221','5'),('70222','5'),('70223','5'), +('70224','5'),('70225','5'),('70226','5'),('70227','5'),('70228','5'), +('70229','5'),('70230','5'),('70231','5'),('70232','5'),('70233','5'), +('70234','5'),('70235','5'),('70236','5'),('70237','5'),('70238','5'), +('70239','5'),('70240','5'),('70251','7'),('70252','7'),('70253','7'), +('70254','7'),('70255','7'),('70256','7'),('70257','7'),('70258','7'), +('70259','7'),('70260','7'),('70261','7'),('70265','7'),('70267','7'), +('70268','7'),('70301','8'),('70302','8'),('70303','8'),('70304','8'), +('70305','8'),('70306','8'),('70307','8'),('70308','8'),('70309','8'), +('70310','8'),('70311','8'),('70312','8'),('70313','8'),('70314','8'), +('70315','8'),('70316','8'),('70317','8'),('70318','8'),('70319','8'), +('70320','8'),('70321','8'),('70322','8'),('70323','8'),('70324','8'), +('70325','8'),('70326','8'),('70327','8'),('70328','8'),('70329','8'), +('70330','8'),('70331','8'),('70332','8'),('70333','8'),('70334','8'), +('70335','8'),('70336','8'),('70351','9'),('70352','9'),('70353','9'), +('70354','9'),('70355','9'),('70356','9'),('70357','9'),('70358','9'), +('70359','9'),('70360','9'),('70361','9'),('70362','9'),('70363','9'), +('70364','9'),('70365','9'),('70366','9'),('70367','9'),('70368','9'), +('70369','9'),('70370','9'),('70371','9'),('70372','9'),('70373','9'), +('70374','9'),('70375','9'),('70376','9'),('70377','9'),('70401','11'), +('70402','11'),('70403','11'),('70404','11'),('70405','11'),('70406','11'), +('70407','11'),('70408','11'),('70409','11'),('70410','11'),('70411','11'), +('70412','11'),('70413','11'),('70414','11'),('70415','11'),('70416','11'), +('70417','11'),('70418','11'),('70451','6'),('70452','6'),('70453','6'), +('70454','6'),('70455','6'),('70456','6'),('70457','6'),('70458','6'), +('70459','6'),('70460','6'),('70461','6'),('70462','6'),('70463','6'), +('70464','6'),('70465','6'),('70501','0'),('70502','0'),('70503','0'), +('70504','0'),('70505','0'),('70506','0'),('70507','0'),('70508','0'), +('70509','0'),('70510','0'),('70511','0'),('70512','0'),('70513','0'), +('70514','0'),('70515','0'),('70516','0'),('70517','0'),('70518','0'), +('70519','0'),('70520','0'),('70521','0'),('70522','0'),('70523','0'), +('70524','0'),('70525','0'),('70526','0'),('70527','0'),('70528','0'), +('70529','0'),('70530','0'),('70531','0'),('70532','0'),('70533','0'), +('70534','0'),('70535','0'),('70536','0'),('70537','0'),('70538','0'), +('70542','0'),('70543','0'),('70544','0'),('70545','0'),('70551','12'), +('70552','12'),('70553','13'),('70554','13'),('70555','14'),('70556','0'), +('70557','15'),('70558','15'),('70559','15'),('70560','15'),('70561','15'), +('70562','0'),('70563','16'),('70564','16'),('70565','16'),('70566','16'), +('70567','16'),('70568','17'),('70569','17'),('70570','17'),('70571','17'), +('70572','17'),('70573','0'),('70574','0'); + +/*!50003 DROP PROCEDURE IF EXISTS `sp__update_specs_2020_06_21`*/; + +DELIMITER ;; + +/*!50003 CREATE*/ +/*!50003 PROCEDURE `sp__update_specs_2020_06_21`() +BEGIN + +DECLARE CLASS_WARRIOR INT DEFAULT 1; +DECLARE CLASS_PALADIN INT DEFAULT 2; +DECLARE CLASS_HUNTER INT DEFAULT 3; +DECLARE CLASS_ROGUE INT DEFAULT 4; +DECLARE CLASS_PRIEST INT DEFAULT 5; +DECLARE CLASS_DEATH_KNIGHT INT DEFAULT 6; +DECLARE CLASS_SHAMAN INT DEFAULT 7; +DECLARE CLASS_MAGE INT DEFAULT 8; +DECLARE CLASS_WARLOCK INT DEFAULT 9; +DECLARE CLASS_DRUID INT DEFAULT 11; +DECLARE CLASS_BLADEMASTER INT DEFAULT 12; +DECLARE CLASS_SPHYNX INT DEFAULT 13; +DECLARE CLASS_ARCHMAGE INT DEFAULT 14; +DECLARE CLASS_DREADLORD INT DEFAULT 15; +DECLARE CLASS_SPELL_BREAKER INT DEFAULT 16; +DECLARE CLASS_DARK_RANGER INT DEFAULT 17; + +DECLARE SPEC_START_WARRIOR INT DEFAULT 1; +DECLARE SPEC_START_PALADIN INT DEFAULT 4; +DECLARE SPEC_START_HUNTER INT DEFAULT 7; +DECLARE SPEC_START_ROGUE INT DEFAULT 10; +DECLARE SPEC_START_PRIEST INT DEFAULT 13; +DECLARE SPEC_START_DEATH_KNIGHT INT DEFAULT 16; +DECLARE SPEC_START_SHAMAN INT DEFAULT 19; +DECLARE SPEC_START_MAGE INT DEFAULT 22; +DECLARE SPEC_START_WARLOCK INT DEFAULT 25; +DECLARE SPEC_START_DRUID INT DEFAULT 28; +DECLARE SPEC_DEFAULT INT DEFAULT 31; + +DECLARE NPCBOT_ENTRY_BEGIN INT DEFAULT 70001; +DECLARE NPCBOT_ENTRY_END INT DEFAULT 71000; + +DECLARE cur_pos INT; +DECLARE myclass INT; +DECLARE myspec INT; + +SET cur_pos = NPCBOT_ENTRY_BEGIN; +WHILE cur_pos < NPCBOT_ENTRY_END DO + SET myclass = (SELECT `class` FROM `npcbot_extras_temp` WHERE `entry` = cur_pos); + SET myspec = (SELECT `spec` FROM `characters_npcbot` WHERE `entry` = cur_pos); + + IF myclass != 0 AND myspec != 0 AND myspec < 4 THEN + + IF myclass = CLASS_WARRIOR THEN + SET myspec = myspec + SPEC_START_WARRIOR - 1; + ELSEIF myclass = CLASS_PALADIN THEN + SET myspec = myspec + SPEC_START_PALADIN - 1; + ELSEIF myclass = CLASS_HUNTER THEN + SET myspec = myspec + SPEC_START_HUNTER - 1; + ELSEIF myclass = CLASS_ROGUE THEN + SET myspec = myspec + SPEC_START_ROGUE - 1; + ELSEIF myclass = CLASS_PRIEST THEN + SET myspec = myspec + SPEC_START_PRIEST - 1; + ELSEIF myclass = CLASS_DEATH_KNIGHT THEN + SET myspec = myspec + SPEC_START_DEATH_KNIGHT - 1; + ELSEIF myclass = CLASS_SHAMAN THEN + SET myspec = myspec + SPEC_START_SHAMAN - 1; + ELSEIF myclass = CLASS_MAGE THEN + SET myspec = myspec + SPEC_START_MAGE - 1; + ELSEIF myclass = CLASS_WARLOCK THEN + SET myspec = myspec + SPEC_START_WARLOCK - 1; + ELSEIF myclass = CLASS_DRUID THEN + SET myspec = myspec + SPEC_START_DRUID - 1; + ELSE + SET myspec = SPEC_DEFAULT; + END IF; + + UPDATE `characters_npcbot` SET `spec` = myspec WHERE `entry` = cur_pos; + + END IF; + + SET cur_pos = cur_pos + 1; + +END WHILE; + +DROP TEMPORARY TABLE IF EXISTS `npcbot_extras_temp`; + +END*/;; + +DELIMITER ; + +CALL `sp__update_specs_2020_06_21`(); + +DROP PROCEDURE IF EXISTS `sp__update_specs_2020_06_21`; diff --git a/sql/Bots/updates/characters/2020_10_08_characters_npcbot_NPCBots_4.7.34a-4.7.39a.sql b/sql/Bots/updates/characters/2020_10_08_characters_npcbot_NPCBots_4.7.34a-4.7.39a.sql new file mode 100644 index 000000000..74e0e207c --- /dev/null +++ b/sql/Bots/updates/characters/2020_10_08_characters_npcbot_NPCBots_4.7.34a-4.7.39a.sql @@ -0,0 +1 @@ +ALTER TABLE `characters_npcbot` ADD `spells_disabled` longtext AFTER `equipNeck`; diff --git a/sql/Bots/updates/characters/2021_01_04_characters_npcbot.sql b/sql/Bots/updates/characters/2021_01_04_characters_npcbot.sql new file mode 100644 index 000000000..d553cc876 --- /dev/null +++ b/sql/Bots/updates/characters/2021_01_04_characters_npcbot.sql @@ -0,0 +1 @@ +ALTER TABLE `characters_npcbot` MODIFY COLUMN `roles` int(10) unsigned NOT NULL COMMENT 'bitmask: tank(1),dps(2),heal(4),ranged(8)'; diff --git a/sql/Bots/updates/characters/2021_01_24_characters_npcbot_stats.sql b/sql/Bots/updates/characters/2021_01_24_characters_npcbot_stats.sql new file mode 100644 index 000000000..4bad36544 --- /dev/null +++ b/sql/Bots/updates/characters/2021_01_24_characters_npcbot_stats.sql @@ -0,0 +1,32 @@ +DROP TABLE IF EXISTS `characters_npcbot_stats`; + +CREATE TABLE `characters_npcbot_stats` ( + `entry` int(10) unsigned NOT NULL DEFAULT '0', + `maxhealth` int(10) unsigned NOT NULL DEFAULT '0', + `maxpower` int(10) unsigned NOT NULL DEFAULT '0', + `strength` int(10) unsigned NOT NULL DEFAULT '0', + `agility` int(10) unsigned NOT NULL DEFAULT '0', + `stamina` int(10) unsigned NOT NULL DEFAULT '0', + `intellect` int(10) unsigned NOT NULL DEFAULT '0', + `spirit` int(10) unsigned NOT NULL DEFAULT '0', + `armor` int(10) unsigned NOT NULL DEFAULT '0', + `defense` int(10) unsigned NOT NULL DEFAULT '0', + `resHoly` int(10) unsigned NOT NULL DEFAULT '0', + `resFire` int(10) unsigned NOT NULL DEFAULT '0', + `resNature` int(10) unsigned NOT NULL DEFAULT '0', + `resFrost` int(10) unsigned NOT NULL DEFAULT '0', + `resShadow` int(10) unsigned NOT NULL DEFAULT '0', + `resArcane` int(10) unsigned NOT NULL DEFAULT '0', + `blockPct` float unsigned NOT NULL DEFAULT '0', + `dodgePct` float unsigned NOT NULL DEFAULT '0', + `parryPct` float unsigned NOT NULL DEFAULT '0', + `critPct` float unsigned NOT NULL DEFAULT '0', + `attackPower` int(10) unsigned NOT NULL DEFAULT '0', + `spellPower` int(10) unsigned NOT NULL DEFAULT '0', + `spellPen` int(10) unsigned NOT NULL DEFAULT '0', + `hastePct` float unsigned NOT NULL DEFAULT '0', + `hitBonusPct` float unsigned NOT NULL DEFAULT '0', + `expertise` int(10) unsigned NOT NULL DEFAULT '0', + `armorPenPct` float unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`entry`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; diff --git a/sql/Bots/updates/characters/2021_08_27_characters_npcbot.sql b/sql/Bots/updates/characters/2021_08_27_characters_npcbot.sql new file mode 100644 index 000000000..2744daddf --- /dev/null +++ b/sql/Bots/updates/characters/2021_08_27_characters_npcbot.sql @@ -0,0 +1,2 @@ +-- An update for shifted roles +UPDATE `characters_npcbot` SET `roles` = (roles & 0x1) | ((roles & ~0x1) << 1); diff --git a/sql/Bots/updates/characters/2022_11_15_characters_npcbot_transmog.sql b/sql/Bots/updates/characters/2022_11_15_characters_npcbot_transmog.sql new file mode 100644 index 000000000..c8985a957 --- /dev/null +++ b/sql/Bots/updates/characters/2022_11_15_characters_npcbot_transmog.sql @@ -0,0 +1,10 @@ +-- +DROP TABLE IF EXISTS `characters_npcbot_transmog`; +CREATE TABLE `characters_npcbot_transmog` ( + `entry` int(10) unsigned NOT NULL, + `slot` tinyint(3) unsigned NOT NULL, + `item_id` int(10) unsigned NOT NULL DEFAULT '0', + `fake_id` int(10) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`entry`,`slot`), + CONSTRAINT `bot_id` FOREIGN KEY (`entry`) REFERENCES `characters_npcbot` (`entry`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; diff --git a/sql/Bots/updates/characters/2022_11_27_characters_npcbot_group_member.sql b/sql/Bots/updates/characters/2022_11_27_characters_npcbot_group_member.sql new file mode 100644 index 000000000..3e7688bb1 --- /dev/null +++ b/sql/Bots/updates/characters/2022_11_27_characters_npcbot_group_member.sql @@ -0,0 +1,10 @@ +-- +DROP TABLE IF EXISTS `characters_npcbot_group_member`; +CREATE TABLE `characters_npcbot_group_member` ( + `guid` int(10) unsigned NOT NULL, + `entry` int(10) unsigned NOT NULL, + `memberFlags` tinyint(3) unsigned NOT NULL DEFAULT '0', + `subgroup` tinyint(3) unsigned NOT NULL DEFAULT '0', + `roles` tinyint(3) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`entry`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; diff --git a/sql/Bots/updates/characters/2023_05_16_00_characters_npcbot_gear_storage.sql b/sql/Bots/updates/characters/2023_05_16_00_characters_npcbot_gear_storage.sql new file mode 100644 index 000000000..32e9f8952 --- /dev/null +++ b/sql/Bots/updates/characters/2023_05_16_00_characters_npcbot_gear_storage.sql @@ -0,0 +1,11 @@ +-- +SET FOREIGN_KEY_CHECKS=0; +DROP TABLE IF EXISTS `characters_npcbot_gear_storage`; +CREATE TABLE `characters_npcbot_gear_storage` ( + `guid` int(10) unsigned NOT NULL DEFAULT '0', + `item_guid` int(10) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`item_guid`), + KEY `existing_player` (`guid`), + CONSTRAINT `characters_npcbot_gear_storage_ibfk_1` FOREIGN KEY (`item_guid`) REFERENCES `item_instance` (`guid`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `existing_player` FOREIGN KEY (`guid`) REFERENCES `characters` (`guid`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Bot item storage system'; diff --git a/sql/Bots/updates/characters/2023_05_26_00_characters_npcbot_transmog.sql b/sql/Bots/updates/characters/2023_05_26_00_characters_npcbot_transmog.sql new file mode 100644 index 000000000..a037563ba --- /dev/null +++ b/sql/Bots/updates/characters/2023_05_26_00_characters_npcbot_transmog.sql @@ -0,0 +1,2 @@ +-- +ALTER TABLE `characters_npcbot_transmog` MODIFY `fake_id` int(11) NOT NULL DEFAULT '-1' AFTER `item_id`; diff --git a/sql/Bots/updates/characters/2024_03_12_00_characters_npcbot.sql b/sql/Bots/updates/characters/2024_03_12_00_characters_npcbot.sql new file mode 100644 index 000000000..220cc7b57 --- /dev/null +++ b/sql/Bots/updates/characters/2024_03_12_00_characters_npcbot.sql @@ -0,0 +1,20 @@ +-- +/*!50003 DROP PROCEDURE IF EXISTS `sp__drop_column_if_exists`*/; +DELIMITER ;; +/*!50003 CREATE*/ +/*!50003 PROCEDURE `sp__drop_column_if_exists`(`@TABLE` varchar(100), `@COLUMN` varchar(100)) +BEGIN +DECLARE `@EXISTS` INT DEFAULT 0; +SELECT COUNT(*) INTO `@EXISTS` FROM `information_schema`.`columns`WHERE `TABLE_SCHEMA` = DATABASE() AND `TABLE_NAME` = `@TABLE` AND `COLUMN_NAME` = `@COLUMN`; +IF (`@EXISTS` > 0) THEN + ALTER TABLE `characters_npcbot` DROP COLUMN `hire_time`; +END IF; +END */;; + +DELIMITER ; + +CALL `sp__drop_column_if_exists`('characters_npcbot', 'hire_time'); + +DROP PROCEDURE IF EXISTS `sp__drop_column_if_exists`; + +ALTER TABLE `characters_npcbot` ADD `hire_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP AFTER `faction`; diff --git a/sql/Bots/updates/characters/2024_05_29_00_characters_npcbot_logs.sql b/sql/Bots/updates/characters/2024_05_29_00_characters_npcbot_logs.sql new file mode 100644 index 000000000..61b872eee --- /dev/null +++ b/sql/Bots/updates/characters/2024_05_29_00_characters_npcbot_logs.sql @@ -0,0 +1,19 @@ +-- +SET FOREIGN_KEY_CHECKS=0; +DROP TABLE IF EXISTS `characters_npcbot_logs`; +CREATE TABLE `characters_npcbot_logs` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT, + `entry` int unsigned NOT NULL DEFAULT '0', + `owner` int NOT NULL DEFAULT '-1', + `mapid` int NOT NULL DEFAULT '-1', + `inmap` tinyint NOT NULL DEFAULT '-1', + `inworld` tinyint NOT NULL DEFAULT '-1', + `type` smallint unsigned NOT NULL DEFAULT '0', + `param1` varchar(51) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, + `param2` varchar(51) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, + `param3` varchar(51) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, + `param4` varchar(51) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, + `param5` varchar(51) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, + `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; diff --git a/sql/Bots/updates/world/2020_07_08_creature_template_npcbot_extras.sql b/sql/Bots/updates/world/2020_07_08_creature_template_npcbot_extras.sql new file mode 100644 index 000000000..2e67192b3 --- /dev/null +++ b/sql/Bots/updates/world/2020_07_08_creature_template_npcbot_extras.sql @@ -0,0 +1,2 @@ +-- Zandine race fix +UPDATE `creature_template_npcbot_extras` SET `race`='10' WHERE (`entry`='70135'); diff --git a/sql/Bots/updates/world/2020_09_25_creature_template.sql b/sql/Bots/updates/world/2020_09_25_creature_template.sql new file mode 100644 index 000000000..e714bcb46 --- /dev/null +++ b/sql/Bots/updates/world/2020_09_25_creature_template.sql @@ -0,0 +1,2 @@ +-- Marion modelId fix +UPDATE `creature_template` SET `modelid1`='1603' WHERE (`entry`='70158'); diff --git a/sql/Bots/updates/world/2020_10_14_creature_classlevelstats.sql b/sql/Bots/updates/world/2020_10_14_creature_classlevelstats.sql new file mode 100644 index 000000000..22639e986 --- /dev/null +++ b/sql/Bots/updates/world/2020_10_14_creature_classlevelstats.sql @@ -0,0 +1,611 @@ +-- +SET @CLASS_HUNTER = 3; +SET @CLASS_PRIEST = 5; +SET @CLASS_DK = 6; +SET @CLASS_SHAMAN = 7; +SET @CLASS_WARLOCK = 9; +SET @CLASS_DRUID = 11; + +-- actual values are irrelevant, but hp and mana must be > 1 +DELETE FROM `creature_classlevelstats` WHERE `class` IN (@CLASS_HUNTER,@CLASS_PRIEST,@CLASS_DK,@CLASS_SHAMAN,@CLASS_WARLOCK,@CLASS_DRUID) AND `level` BETWEEN '1' AND '100'; +INSERT INTO `creature_classlevelstats` (`level`,`class`,`basehp0`,`basehp1`,`basehp2`,`basemana`,`basearmor`,`attackpower`,`rangedattackpower`,`damage_base`,`damage_exp1`,`damage_exp2`,`comment`) VALUES +('1', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('1', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('1', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('1', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('1', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('1', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('2', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('2', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('2', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('2', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('2', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('2', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('3', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('3', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('3', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('3', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('3', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('3', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('4', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('4', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('4', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('4', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('4', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('4', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('5', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('5', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('5', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('5', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('5', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('5', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('6', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('6', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('6', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('6', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('6', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('6', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('7', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('7', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('7', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('7', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('7', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('7', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('8', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('8', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('8', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('8', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('8', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('8', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('9', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('9', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('9', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('9', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('9', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('9', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('10', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('10', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('10', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('10', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('10', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('10', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('11', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('11', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('11', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('11', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('11', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('11', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('12', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('12', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('12', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('12', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('12', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('12', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('13', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('13', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('13', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('13', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('13', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('13', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('14', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('14', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('14', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('14', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('14', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('14', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('15', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('15', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('15', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('15', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('15', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('15', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('16', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('16', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('16', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('16', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('16', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('16', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('17', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('17', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('17', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('17', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('17', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('17', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('18', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('18', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('18', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('18', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('18', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('18', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('19', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('19', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('19', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('19', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('19', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('19', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('20', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('20', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('20', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('20', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('20', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('20', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('21', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('21', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('21', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('21', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('21', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('21', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('22', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('22', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('22', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('22', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('22', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('22', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('23', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('23', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('23', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('23', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('23', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('23', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('24', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('24', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('24', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('24', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('24', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('24', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('25', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('25', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('25', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('25', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('25', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('25', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('26', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('26', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('26', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('26', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('26', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('26', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('27', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('27', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('27', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('27', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('27', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('27', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('28', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('28', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('28', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('28', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('28', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('28', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('29', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('29', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('29', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('29', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('29', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('29', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('30', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('30', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('30', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('30', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('30', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('30', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('31', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('31', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('31', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('31', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('31', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('31', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('32', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('32', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('32', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('32', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('32', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('32', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('33', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('33', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('33', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('33', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('33', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('33', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('34', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('34', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('34', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('34', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('34', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('34', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('35', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('35', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('35', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('35', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('35', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('35', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('36', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('36', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('36', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('36', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('36', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('36', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('37', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('37', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('37', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('37', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('37', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('37', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('38', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('38', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('38', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('38', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('38', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('38', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('39', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('39', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('39', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('39', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('39', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('39', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('40', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('40', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('40', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('40', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('40', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('40', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('41', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('41', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('41', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('41', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('41', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('41', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('42', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('42', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('42', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('42', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('42', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('42', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('43', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('43', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('43', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('43', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('43', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('43', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('44', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('44', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('44', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('44', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('44', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('44', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('45', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('45', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('45', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('45', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('45', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('45', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('46', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('46', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('46', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('46', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('46', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('46', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('47', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('47', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('47', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('47', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('47', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('47', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('48', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('48', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('48', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('48', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('48', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('48', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('49', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('49', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('49', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('49', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('49', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('49', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('50', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('50', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('50', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('50', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('50', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('50', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('51', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('51', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('51', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('51', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('51', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('51', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('52', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('52', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('52', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('52', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('52', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('52', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('53', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('53', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('53', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('53', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('53', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('53', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('54', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('54', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('54', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('54', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('54', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('54', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('55', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('55', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('55', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('55', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('55', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('55', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('56', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('56', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('56', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('56', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('56', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('56', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('57', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('57', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('57', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('57', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('57', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('57', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('58', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('58', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('58', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('58', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('58', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('58', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('59', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('59', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('59', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('59', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('59', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('59', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('60', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('60', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('60', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('60', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('60', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('60', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('61', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('61', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('61', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('61', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('61', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('61', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('62', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('62', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('62', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('62', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('62', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('62', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('63', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('63', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('63', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('63', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('63', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('63', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('64', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('64', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('64', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('64', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('64', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('64', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('65', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('65', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('65', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('65', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('65', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('65', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('66', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('66', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('66', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('66', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('66', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('66', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('67', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('67', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('67', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('67', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('67', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('67', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('68', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('68', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('68', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('68', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('68', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('68', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('69', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('69', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('69', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('69', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('69', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('69', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('70', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('70', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('70', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('70', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('70', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('70', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('71', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('71', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('71', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('71', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('71', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('71', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('72', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('72', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('72', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('72', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('72', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('72', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('73', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('73', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('73', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('73', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('73', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('73', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('74', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('74', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('74', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('74', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('74', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('74', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('75', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('75', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('75', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('75', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('75', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('75', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('76', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('76', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('76', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('76', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('76', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('76', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('77', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('77', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('77', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('77', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('77', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('77', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('78', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('78', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('78', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('78', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('78', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('78', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('79', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('79', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('79', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('79', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('79', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('79', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('80', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('80', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('80', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('80', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('80', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('80', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('81', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('81', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('81', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('81', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('81', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('81', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('82', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('82', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('82', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('82', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('82', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('82', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('83', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('83', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('83', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('83', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('83', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('83', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('84', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('84', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('84', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('84', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('84', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('84', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('85', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('85', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('85', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('85', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('85', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('85', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('86', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('86', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('86', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('86', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('86', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('86', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('87', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('87', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('87', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('87', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('87', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('87', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('88', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('88', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('88', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('88', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('88', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('88', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('89', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('89', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('89', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('89', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('89', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('89', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('90', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('90', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('90', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('90', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('90', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('90', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('91', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('91', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('91', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('91', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('91', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('91', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('92', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('92', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('92', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('92', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('92', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('92', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('93', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('93', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('93', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('93', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('93', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('93', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('94', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('94', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('94', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('94', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('94', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('94', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('95', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('95', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('95', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('95', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('95', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('95', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('96', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('96', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('96', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('96', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('96', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('96', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('97', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('97', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('97', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('97', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('97', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('97', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('98', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('98', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('98', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('98', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('98', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('98', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('99', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('99', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('99', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('99', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('99', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('99', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('100', @CLASS_HUNTER, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('100', @CLASS_PRIEST, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('100', @CLASS_DK, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('100', @CLASS_SHAMAN, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('100', @CLASS_WARLOCK,'2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL), +('100', @CLASS_DRUID, '2', '2', '2', '2', '0', '0', '0', '0.1', '0', '0', NULL); diff --git a/sql/Bots/updates/world/2020_10_14_creature_template.sql b/sql/Bots/updates/world/2020_10_14_creature_template.sql new file mode 100644 index 000000000..4580de48b --- /dev/null +++ b/sql/Bots/updates/world/2020_10_14_creature_template.sql @@ -0,0 +1,50 @@ +-- +SET @CLASS_WARRIOR = 1; +SET @CLASS_PALADIN = 2; +SET @CLASS_HUNTER = 3; +SET @CLASS_ROGUE = 4; +SET @CLASS_PRIEST = 5; +SET @CLASS_DK = 6; +SET @CLASS_SHAMAN = 7; +SET @CLASS_MAGE = 8; +SET @CLASS_WARLOCK = 9; +SET @CLASS_DRUID = 11; +SET @CLASS_BM = 12; +SET @CLASS_SPHYNX = 13; +SET @CLASS_ARCHMAGE = 14; +SET @CLASS_DREADLORD = 15; +SET @CLASS_SPELLBREAKER = 16; +SET @CLASS_DARK_RANGER = 17; + +UPDATE `creature_template` SET `unit_class`:=@CLASS_DK +WHERE `entry` IN (SELECT `entry` FROM `creature_template_npcbot_extras` WHERE `class`=@CLASS_DK); +UPDATE `creature_template` SET `unit_class`:=@CLASS_DRUID +WHERE `entry` IN (SELECT `entry` FROM `creature_template_npcbot_extras` WHERE `class`=@CLASS_DRUID); +UPDATE `creature_template` SET `unit_class`:=@CLASS_HUNTER +WHERE `entry` IN (SELECT `entry` FROM `creature_template_npcbot_extras` WHERE `class`=@CLASS_HUNTER); +UPDATE `creature_template` SET `unit_class`:=@CLASS_MAGE +WHERE `entry` IN (SELECT `entry` FROM `creature_template_npcbot_extras` WHERE `class`=@CLASS_MAGE); +UPDATE `creature_template` SET `unit_class`:=@CLASS_PALADIN +WHERE `entry` IN (SELECT `entry` FROM `creature_template_npcbot_extras` WHERE `class`=@CLASS_PALADIN); +UPDATE `creature_template` SET `unit_class`:=@CLASS_PRIEST +WHERE `entry` IN (SELECT `entry` FROM `creature_template_npcbot_extras` WHERE `class`=@CLASS_PRIEST); +UPDATE `creature_template` SET `unit_class`:=@CLASS_ROGUE +WHERE `entry` IN (SELECT `entry` FROM `creature_template_npcbot_extras` WHERE `class`=@CLASS_ROGUE); +UPDATE `creature_template` SET `unit_class`:=@CLASS_SHAMAN +WHERE `entry` IN (SELECT `entry` FROM `creature_template_npcbot_extras` WHERE `class`=@CLASS_SHAMAN); +UPDATE `creature_template` SET `unit_class`:=@CLASS_WARLOCK +WHERE `entry` IN (SELECT `entry` FROM `creature_template_npcbot_extras` WHERE `class`=@CLASS_WARLOCK); +UPDATE `creature_template` SET `unit_class`:=@CLASS_WARRIOR +WHERE `entry` IN (SELECT `entry` FROM `creature_template_npcbot_extras` WHERE `class`=@CLASS_WARRIOR); +UPDATE `creature_template` SET `unit_class`:=@CLASS_WARRIOR +WHERE `entry` IN (SELECT `entry` FROM `creature_template_npcbot_extras` WHERE `class`=@CLASS_BM); +UPDATE `creature_template` SET `unit_class`:=@CLASS_WARLOCK +WHERE `entry` IN (SELECT `entry` FROM `creature_template_npcbot_extras` WHERE `class`=@CLASS_SPHYNX); +UPDATE `creature_template` SET `unit_class`:=@CLASS_MAGE +WHERE `entry` IN (SELECT `entry` FROM `creature_template_npcbot_extras` WHERE `class`=@CLASS_ARCHMAGE); +UPDATE `creature_template` SET `unit_class`:=@CLASS_WARLOCK +WHERE `entry` IN (SELECT `entry` FROM `creature_template_npcbot_extras` WHERE `class`=@CLASS_DREADLORD); +UPDATE `creature_template` SET `unit_class`:=@CLASS_PALADIN +WHERE `entry` IN (SELECT `entry` FROM `creature_template_npcbot_extras` WHERE `class`=@CLASS_SPELLBREAKER); +UPDATE `creature_template` SET `unit_class`:=@CLASS_DK +WHERE `entry` IN (SELECT `entry` FROM `creature_template_npcbot_extras` WHERE `class`=@CLASS_DARK_RANGER); diff --git a/sql/Bots/updates/world/2020_10_15_creature_template.sql b/sql/Bots/updates/world/2020_10_15_creature_template.sql new file mode 100644 index 000000000..9cb16889d --- /dev/null +++ b/sql/Bots/updates/world/2020_10_15_creature_template.sql @@ -0,0 +1,2 @@ +-- +UPDATE `creature_template` SET `unit_flags2`='16416' WHERE `entry` IN ('70551','70552'); diff --git a/sql/Bots/updates/world/2020_11_07_npc_text.sql b/sql/Bots/updates/world/2020_11_07_npc_text.sql new file mode 100644 index 000000000..94a866a9e --- /dev/null +++ b/sql/Bots/updates/world/2020_11_07_npc_text.sql @@ -0,0 +1,329 @@ +-- +SET @LOCALIZED_STRINGS_START = 70300; +SET @LOCALIZED_STRINGS_END = 70799; + +-- LOCALIZATION STRING -- + +DELETE FROM `npc_text` WHERE ID BETWEEN @LOCALIZED_STRINGS_START and @LOCALIZED_STRINGS_END; +INSERT INTO `npc_text` (`ID`,`text0_0`,`VerifiedBuild`) VALUES +('70300','Die!','-1'), +('70301','Rezzing You','-1'), +('70302','Rezzing ','-1'), +('70303','your bot','-1'), +('70304','\'s bot','-1'), +('70305','I can\'t conjure water yet','-1'), +('70306','I can\'t conjure food yet','-1'), +('70307','I can\'t do it right now','-1'), +('70308','Here you go...','-1'), +('70309','Disabled','-1'), +('70310','Not ready yet','-1'), +('70311','Invalid object type','-1'), +('70312','Failed','-1'), +('70313','Done','-1'), +('70314','I am not shapeshifted','-1'), +('70315','I don\'t have a healthstone','-1'), +('70316','I can\'t create healthstones yet!','-1'), +('70317','WTF I don\'t have lockpicking!','-1'), +('70318','My skill level in not high enough','-1'), +('70319','Changing my spec to ','-1'), +('70320','Arms','-1'), +('70321','Fury','-1'), +('70322','Protection','-1'), +('70323','Retribution','-1'), +('70324','Beast Mastery','-1'), +('70325','Marksmanship','-1'), +('70326','Survival','-1'), +('70327','Assassination','-1'), +('70328','Combat','-1'), +('70329','Subtlety','-1'), +('70330','Discipline','-1'), +('70331','Holy','-1'), +('70332','Shadow','-1'), +('70333','Blood','-1'), +('70334','Frost','-1'), +('70335','Unholy','-1'), +('70336','Elemental','-1'), +('70337','Enhancement','-1'), +('70338','Restoration','-1'), +('70339','Arcane','-1'), +('70340','Fire','-1'), +('70341','Affliction','-1'), +('70342','Demonology','-1'), +('70343','Destruction','-1'), +('70344','Balance','-1'), +('70345','Feral Combat','-1'), +('70346','Unknown','-1'), +('70347','Go away, weakling','-1'), +('70348',' is not convinced','-1'), +('70349','I am not going to waste my time on just anything','-1'), +('70350','NIY','-1'), +('70351','NIY','-1'), +('70352','NIY','-1'), +('70353','I am ready','-1'), +('70354','Go away. I serve my master ','-1'), +('70355','unknown','-1'), +('70356',' on You!','-1'), +('70357',' on myself!','-1'), +('70358',' on ','-1'), +('70359',' used!','-1'), +('70360','bot tank','-1'), +('70361','class','-1'), +('70362','player','-1'), +('70363','master','-1'), +('70364','none','-1'), +('70365','Rank','-1'), +('70366','talent','-1'), +('70367','passive','-1'), +('70368','hidden','-1'), +('70369','known','-1'), +('70370','ability','-1'), +('70371','str','-1'), +('70372','agi','-1'), +('70373','sta','-1'), +('70374','int','-1'), +('70375','spi','-1'), +('70376','unk stat','-1'), +('70377','total','-1'), +('70378','Melee AP','-1'), +('70379','Ranged AP','-1'), +('70380','armor','-1'), +('70381','crit','-1'), +('70382','defense','-1'), +('70383','miss','-1'), +('70384','dodge','-1'), +('70385','parry','-1'), +('70386','block','-1'), +('70387','block value','-1'), +('70388','Damage taken melee','-1'), +('70389','Damage taken spell','-1'), +('70390','Damage range mainhand','-1'), +('70391','Damage mult mainhand','-1'), +('70392','Attack time mainhand','-1'), +('70393','Damage range offhand','-1'), +('70394','Damage mult offhand','-1'), +('70395','Attack time offhand','-1'), +('70396','Damage range ranged','-1'), +('70397','Damage mult ranged','-1'), +('70398','Attack time ranged','-1'), +('70399','min','-1'), +('70400','max','-1'), +('70401','DPS','-1'), +('70402','base hp','-1'), +('70403','total hp','-1'), +('70404','base mana','-1'), +('70405','total mana','-1'), +('70406','current mana','-1'), +('70407','spell power','-1'), +('70408','health regen_5 bonus','-1'), +('70409','mana regen_5 no cast','-1'), +('70410','mana regen_5 casting','-1'), +('70411','haste','-1'), +('70412','hit','-1'), +('70413','expertise','-1'), +('70414','armor penetration','-1'), +('70415','spell penetration','-1'), +('70416','pct','-1'), +('70417','holy','-1'), +('70418','fire','-1'), +('70419','nature','-1'), +('70420','frost','-1'), +('70421','shadow','-1'), +('70422','arcane','-1'), +('70423','Resistance','-1'), +('70424','Command states','-1'), +('70425','Follow','-1'), +('70426','Attack','-1'), +('70427','Stay','-1'), +('70428','Reset','-1'), +('70429','FullStop','-1'), +('70430','Follow distance','-1'), +('70431','Spec','-1'), +('70432','Bot roles mask main','-1'), +('70433','Bot roles mask gathering','-1'), +('70434','PvP kills','-1'), +('70435','players','-1'), +('70436','Died ','-1'), +('70437',' times','-1'), +('70438','%s (bot) calms down','-1'), +('70439','','-1'), +('70440','Are you sure you want to risk drawing ','-1'), +('70441','\'s attention?','-1'), +('70442','','-1'), +('70443','Do you want to entice ','-1'), +('70444','','-1'), +('70445','Do you wish to hire ','-1'), +('70446','','-1'), +('70447','Manage equipment...','-1'), +('70448','Manage roles...','-1'), +('70449','Manage formation...','-1'), +('70450','Manage abilities...','-1'), +('70451','Manage talents...','-1'), +('70452','Give consumable...','-1'), +('70453','','-1'), +('70454','','-1'), +('70455','','-1'), +('70456','','-1'), +('70457','','-1'), +('70458','Follow me','-1'), +('70459','Hold your position','-1'), +('70460','Stay here and don\'t do anything','-1'), +('70461','I need food','-1'), +('70462','I need water','-1'), +('70463','I need a refreshment table','-1'), +('70464','Help me pick a lock','-1'), +('70465','I need your your healthstone','-1'), +('70466','I need a soulwell','-1'), +('70467','I need you to refresh poisons','-1'), +('70468','','-1'), +('70469','','-1'), +('70470','I need you to refresh enchants','-1'), +('70471','','-1'), +('70472','','-1'), +('70473','I need you to remove shapeshift','-1'), +('70474','','-1'), +('70475','You are dismissed','-1'), +('70476','Are you going to abandon ','-1'), +('70477','You may regret it...','-1'), +('70478','Pull yourself together, damnit','-1'), +('70479','','-1'), +('70480','Nevermind','-1'), +('70481','dist','-1'), +('70482','BACK','-1'), +('70483','','-1'), +('70484','','-1'), +('70485','Random (Cunning)','-1'), +('70486','Random (Ferocity)','-1'), +('70487','Random (Tenacity)','-1'), +('70488','Show me your inventory','-1'), +('70489','Auto-equip...','-1'), +('70490','Main hand','-1'), +('70491','Off-hand','-1'), +('70492','Ranged','-1'), +('70493','Relic','-1'), +('70494','Head','-1'), +('70495','Shoulders','-1'), +('70496','Chest','-1'), +('70497','Waist','-1'), +('70498','Legs','-1'), +('70499','Feet','-1'), +('70500','Wrist','-1'), +('70501','Hands','-1'), +('70502','Back','-1'), +('70503','Shirt','-1'), +('70504','Finger1','-1'), +('70505','Finger2','-1'), +('70506','Trinket1','-1'), +('70507','Trinket2','-1'), +('70508','Neck','-1'), +('70509','Unequip all','-1'), +('70510','Update visual','-1'), +('70511','visual only','-1'), +('70512','Equipped','-1'), +('70513','nothing','-1'), +('70514','Use your old equipment','-1'), +('70515','Unequip it','-1'), +('70516','Hm... I have nothing to give you','-1'), +('70517','Gathering','-1'), +('70518','Abilities status','-1'), +('70519','Manage allowed abilities','-1'), +('70520','Use ','-1'), +('70521','Update','-1'), +('70522','Damage','-1'), +('70523','Control','-1'), +('70524','Heal','-1'), +('70525','Other','-1'), +('70526',' makes a grinding sound and begins to follow ','-1'), +('70527','%s will not join you until dismissed by the owner','-1'), +('70528','%s will not join you until you are level 60','-1'), +('70529','%s will not join you until you are level 55','-1'), +('70530','%s will not join you until you are level 40','-1'), +('70531','%s will not join you until you are level 20','-1'), +('70532','You exceed max npcbots (%u)','-1'), +('70533','You don\'t have enough money','-1'), +('70534','You cannot have more bots of that class! %u of %u','-1'), +('70535','Cannot reset equipment in slot %u (%s)! Cannot dismiss bot!','-1'), +('70536','current','-1'), +('70537','Attack distance','-1'), +('70538','Short range attacks','-1'), +('70539','Long range attacks','-1'), +('70540','Exact','-1'), +('70541','Remove buff','-1'), +('70542','Fix your power type','-1'), +('70543','Cannot unequip %s for some stupid reason! Sending through mail','-1'), +('70544','Tank','-1'), +('70545','Ranged','-1'), +('70546','Miner','-1'), +('70547','Herbalist','-1'), +('70548','Skinner','-1'), +('70549','Engineer','-1'), +('70550','Bot ownership expired due to inactivity','-1'), +('70551','NpcBot system is currently disabled. Please contact administration.','-1'), +('70552','%s will not join you, already has master: %s','-1'), +('70553','%s cannot join you while about to teleport','-1'), +('70554','Aspect','-1'), +('70555','Monkey','-1'), +('70556','Hawk','-1'), +('70557','Cheetah','-1'), +('70558','Viper','-1'), +('70559','Beast','-1'), +('70560','Pack','-1'), +('70561','Wild','-1'), +('70562','Dragonhawk','-1'), +('70563','No Aspect','-1'), +('70564','Aura','-1'), +('70565','Devotion','-1'), +('70566','Concentration','-1'), +('70567','Fire Resistance','-1'), +('70568','Frost Resistance','-1'), +('70569','Shadow Resistance','-1'), +('70570','Retribution','-1'), +('70571','Crusader','-1'), +('70572','No Aura','-1'), +('70573','Crippling','-1'), +('70574','Instant','-1'), +('70575','Deadly','-1'), +('70576','Wound','-1'), +('70577','Mind-Numbing','-1'), +('70578','Anesthetic','-1'), +('70579','Nothing','-1'), +('70580','Flametongue','-1'), +('70581','Frostbrand','-1'), +('70582','Windfury','-1'), +('70583','Earthliving','-1'), +('70584','I need your services','-1'), +('70585','You have too many bots','-1'), +('70586','Do you wish to hire ','-1'), +('70587',' is a bit busy at the moment, try again later.','-1'), +('70588','Pleasure doing business with you','-1'), +('70589','Warriors','-1'), +('70590','Paladins','-1'), +('70591','Mages','-1'), +('70592','Priests','-1'), +('70593','Warlocks','-1'), +('70594','Druids','-1'), +('70595','Death Knights','-1'), +('70596','Rogues','-1'), +('70597','Shamans','-1'), +('70598','Hunters','-1'), +('70599','Blademasters','-1'), +('70600','Destroyers','-1'), +('70601','Archmagi','-1'), +('70602','Dreadlords','-1'), +('70603','Spell Breakers','-1'), +('70604','Dark Rangers','-1'), +('70605','Warrior','-1'), +('70606','Paladin','-1'), +('70607','Mage','-1'), +('70608','Priest','-1'), +('70609','Warlock','-1'), +('70610','Druid','-1'), +('70611','Death Knight','-1'), +('70612','Rogue','-1'), +('70613','Shaman','-1'), +('70614','Hunter','-1'), +('70615','Blademaster','-1'), +('70616','Destroyer','-1'), +('70617','Archmage','-1'), +('70618','Dreadlord','-1'), +('70619','Spell Breaker','-1'), +('70620','Dark Ranger','-1'); diff --git a/sql/Bots/updates/world/2020_12_11_npc_text.sql b/sql/Bots/updates/world/2020_12_11_npc_text.sql new file mode 100644 index 000000000..f9c08b521 --- /dev/null +++ b/sql/Bots/updates/world/2020_12_11_npc_text.sql @@ -0,0 +1,4 @@ +-- +DELETE FROM `npc_text` WHERE ID = 70550; +INSERT INTO `npc_text` (`ID`,`text0_0`,`VerifiedBuild`) VALUES +('70550','Bot ownership expired','-1'); diff --git a/sql/Bots/updates/world/2021_01_04_npc_text.sql b/sql/Bots/updates/world/2021_01_04_npc_text.sql new file mode 100644 index 000000000..75681a46c --- /dev/null +++ b/sql/Bots/updates/world/2021_01_04_npc_text.sql @@ -0,0 +1,19 @@ +-- +SET @LOCALIZED_STRINGS_START = 70621; +SET @LOCALIZED_STRINGS_END = 70633; + +DELETE FROM `npc_text` WHERE ID BETWEEN @LOCALIZED_STRINGS_START and @LOCALIZED_STRINGS_END; +INSERT INTO `npc_text` (`ID`,`text0_0`,`VerifiedBuild`) VALUES +('70621','Male','-1'), +('70622','Female','-1'), +('70623','Human','-1'), +('70624','Orc','-1'), +('70625','Dwarf','-1'), +('70626','Night Elf','-1'), +('70627','Undead','-1'), +('70628','Tauren','-1'), +('70629','Gnome','-1'), +('70630','Troll','-1'), +('70631','Blood Elf','-1'), +('70632','Draenei','-1'), +('70633','Unknown','-1'); diff --git a/sql/Bots/updates/world/2021_01_05_npc_text.sql b/sql/Bots/updates/world/2021_01_05_npc_text.sql new file mode 100644 index 000000000..26352ce0a --- /dev/null +++ b/sql/Bots/updates/world/2021_01_05_npc_text.sql @@ -0,0 +1,13 @@ +-- +SET @LOCALIZED_STRINGS_START = 70634; +SET @LOCALIZED_STRINGS_END = 70640; + +DELETE FROM `npc_text` WHERE ID BETWEEN @LOCALIZED_STRINGS_START and @LOCALIZED_STRINGS_END; +INSERT INTO `npc_text` (`ID`,`text0_0`,`VerifiedBuild`) VALUES +('70634','Looting','-1'), +('70635','|cff9d9d9dPoor|r','-1'), +('70636','|cffffffffCommon|r','-1'), +('70637','|cff1eff00Uncommon|r','-1'), +('70638','|cff0070ddRare|r','-1'), +('70639','|cffa335eeEpic|r','-1'), +('70640','|cffff8000Legendary|r','-1'); diff --git a/sql/Bots/updates/world/2021_01_08_npc_text.sql b/sql/Bots/updates/world/2021_01_08_npc_text.sql new file mode 100644 index 000000000..6f8781ecd --- /dev/null +++ b/sql/Bots/updates/world/2021_01_08_npc_text.sql @@ -0,0 +1,9 @@ +-- +SET @LOCALIZED_STRINGS_START = 70641; +SET @LOCALIZED_STRINGS_END = 70643; + +DELETE FROM `npc_text` WHERE ID BETWEEN @LOCALIZED_STRINGS_START and @LOCALIZED_STRINGS_END; +INSERT INTO `npc_text` (`ID`,`text0_0`,`VerifiedBuild`) VALUES +('70641','Grab on ','-1'), +('70642','my ','-1'), +('70643','','-1'); diff --git a/sql/Bots/updates/world/2021_02_01_npc_text.sql b/sql/Bots/updates/world/2021_02_01_npc_text.sql new file mode 100644 index 000000000..b1a2de0ce --- /dev/null +++ b/sql/Bots/updates/world/2021_02_01_npc_text.sql @@ -0,0 +1,2 @@ +-- +UPDATE `npc_text` SET `text0_0`='Auto-equip' WHERE (`ID`='70489'); diff --git a/sql/Bots/updates/world/2021_03_29_npc_text.sql b/sql/Bots/updates/world/2021_03_29_npc_text.sql new file mode 100644 index 000000000..d198943eb --- /dev/null +++ b/sql/Bots/updates/world/2021_03_29_npc_text.sql @@ -0,0 +1,10 @@ +-- +SET @LOCALIZED_STRINGS_START = 70641; +SET @LOCALIZED_STRINGS_END = 70644; + +DELETE FROM `npc_text` WHERE ID BETWEEN @LOCALIZED_STRINGS_START and @LOCALIZED_STRINGS_END; +INSERT INTO `npc_text` (`ID`,`text0_0`,`VerifiedBuild`) VALUES +('70641','Engage behavior','-1'), +('70642','Delay attack by','-1'), +('70643','Delay healing by','-1'), +('70644','s','-1'); diff --git a/sql/Bots/updates/world/2021_08_20_creature_template_npcbot_extras.sql b/sql/Bots/updates/world/2021_08_20_creature_template_npcbot_extras.sql new file mode 100644 index 000000000..e6d28d4d2 --- /dev/null +++ b/sql/Bots/updates/world/2021_08_20_creature_template_npcbot_extras.sql @@ -0,0 +1,2 @@ +-- Kerra race fix +UPDATE `creature_template_npcbot_extras` SET `race`='10' WHERE (`entry`='70038'); diff --git a/sql/Bots/updates/world/2021_08_27_npc_text.sql b/sql/Bots/updates/world/2021_08_27_npc_text.sql new file mode 100644 index 000000000..7835ea5a8 --- /dev/null +++ b/sql/Bots/updates/world/2021_08_27_npc_text.sql @@ -0,0 +1,4 @@ +-- +DELETE FROM `npc_text` WHERE ID = 70645; +INSERT INTO `npc_text` (`ID`,`text0_0`,`VerifiedBuild`) VALUES +('70645','Off-Tank','-1'); diff --git a/sql/Bots/updates/world/2021_10_12_creature_template.sql b/sql/Bots/updates/world/2021_10_12_creature_template.sql new file mode 100644 index 000000000..29a1bd018 --- /dev/null +++ b/sql/Bots/updates/world/2021_10_12_creature_template.sql @@ -0,0 +1,8 @@ +-- +SET @BOT_START = 70001; +SET @BOT_END = 71000; + +SET @U2_MIRROR_IMAGE = 16; -- 0x00000010 - UNIT_FLAG2_MIRROR_IMAGE +SET @U2_ENABLE_ENEMY_INTERACT = 16384; -- 0x00004000 - UNIT_FLAG2_ALLOW_ENEMY_INTERACT + +UPDATE `creature_template` SET `unit_flags2` = `unit_flags2`&~(@U2_MIRROR_IMAGE|@U2_ENABLE_ENEMY_INTERACT) WHERE `entry` BETWEEN @BOT_START AND @BOT_END; diff --git a/sql/Bots/updates/world/2022_01_02_00_creature_template_npcbot_extras.sql b/sql/Bots/updates/world/2022_01_02_00_creature_template_npcbot_extras.sql new file mode 100644 index 000000000..d5f02d0b6 --- /dev/null +++ b/sql/Bots/updates/world/2022_01_02_00_creature_template_npcbot_extras.sql @@ -0,0 +1,12 @@ +-- +SET @BOT_START = 70575; +SET @BOT_END = 70580; + +DELETE FROM `creature_template_npcbot_extras` WHERE `entry` BETWEEN @BOT_START AND @BOT_END; +INSERT INTO `creature_template_npcbot_extras` (`entry`,`class`,`race`) VALUES +('70575', '18', '1'), +('70576', '18', '1'), +('70577', '18', '1'), +('70578', '18', '1'), +('70579', '18', '1'), +('70580', '0', '15'); diff --git a/sql/Bots/updates/world/2022_01_02_01_generate_equips_necromancer.sql b/sql/Bots/updates/world/2022_01_02_01_generate_equips_necromancer.sql new file mode 100644 index 000000000..6fe25bd2f --- /dev/null +++ b/sql/Bots/updates/world/2022_01_02_01_generate_equips_necromancer.sql @@ -0,0 +1,57 @@ +/*!50003 DROP PROCEDURE IF EXISTS `sp__generate_necromancer_equips`*/; + +DELIMITER ;; + +/*!50003 CREATE*/ +/*!50003 PROCEDURE `sp__generate_necromancer_equips`() +BEGIN + +DECLARE CLASS_NECROMANCER INT DEFAULT 18; + +DECLARE NPCBOT_ENTRY_BEGIN INT DEFAULT 70575; +DECLARE NPCBOT_ENTRY_END INT DEFAULT 71000; + +DECLARE NPCBOT_ENTRY_PET_NECROSKELETON INT DEFAULT 70580; + +DECLARE cur_pos INT DEFAULT 0; +DECLARE myclass INT; +DECLARE myrace INT; +DECLARE item1 INT DEFAULT 0; +DECLARE item2 INT DEFAULT 0; +DECLARE item3 INT DEFAULT 0; + +DELETE FROM `creature_equip_template` WHERE `CreatureID` BETWEEN NPCBOT_ENTRY_BEGIN AND NPCBOT_ENTRY_END; + +SET cur_pos = NPCBOT_ENTRY_BEGIN; +WHILE cur_pos < NPCBOT_ENTRY_END DO + SET myclass = (SELECT `class` FROM `creature_template_npcbot_extras` WHERE `entry` = cur_pos); + SET myrace = (SELECT `race` FROM `creature_template_npcbot_extras` WHERE `entry` = cur_pos); + + IF myclass != 0 AND myrace != 0 THEN + + IF myclass = CLASS_NECROMANCER THEN + SET item1 = 13937; -- staff + END IF; + + INSERT INTO `creature_equip_template` (`CreatureID`,`ID`,`itemID1`,`itemID2`,`itemID3`,`VerifiedBuild`) VALUES (cur_pos,1,item1,item2,item3,-1); + + ELSEIF cur_pos = NPCBOT_ENTRY_PET_NECROSKELETON THEN + SET item1 = 3935; + SET item2 = 15648; + SET item3 = 0; + + INSERT INTO `creature_equip_template` (`CreatureID`,`ID`,`itemID1`,`itemID2`,`itemID3`,`VerifiedBuild`) VALUES (cur_pos,1,item1,item2,item3,-1); + + END IF; + + SET cur_pos = cur_pos + 1; + +END WHILE; + +END */;; + +DELIMITER ; + +CALL `sp__generate_necromancer_equips`(); + +DROP PROCEDURE IF EXISTS `sp__generate_necromancer_equips`; diff --git a/sql/Bots/updates/world/2022_01_02_02_creature_template.sql b/sql/Bots/updates/world/2022_01_02_02_creature_template.sql new file mode 100644 index 000000000..3f3327f5b --- /dev/null +++ b/sql/Bots/updates/world/2022_01_02_02_creature_template.sql @@ -0,0 +1,21 @@ +-- +SET @BOT_START = 70575; +SET @BOT_END = 70580; + +DELETE FROM `creature_template` WHERE `entry` BETWEEN @BOT_START AND @BOT_END; + +INSERT INTO `creature_template` +(`entry`,`difficulty_entry_1`,`difficulty_entry_2`,`difficulty_entry_3`,`KillCredit1`,`KillCredit2`, +`modelid1`,`modelid2`,`modelid3`,`modelid4`,`name`,`subname`,`IconName`,`gossip_menu_id`,`minlevel`,`maxlevel`,`exp`, +`faction`,`npcflag`,`speed_walk`,`speed_run`,`scale`,`rank`,`dmgschool`,`BaseAttackTime`,`RangeAttackTime`, +`unit_class`,`unit_flags`,`unit_flags2`,`dynamicflags`,`family`,`type`,`type_flags`,`lootid`, +`pickpocketloot`,`skinloot`,`PetSpellDataId`,`VehicleId`,`mingold`,`maxgold`,`AIName`,`MovementType`, +`HoverHeight`,`HealthModifier`,`ManaModifier`,`ArmorModifier`,`RacialLeader`,`movementId`,`RegenHealth`, +`mechanic_immune_mask`,`flags_extra`,`ScriptName`,`VerifiedBuild`) +VALUES +('70575','0','0','0','0','0','23277','0','0','0','Prakar','Necromancer Bot','','0','82','82','2','35','1','1.1','1.1','1','1','0','1800','1800','9','0','32','0','0','7','4096','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','necromancer_bot','-1'), +('70576','0','0','0','0','0','23277','0','0','0','Rothik','Necromancer Bot','','0','82','82','2','35','1','1.1','1.1','1','1','0','1800','1800','9','0','32','0','0','7','4096','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','necromancer_bot','-1'), +('70577','0','0','0','0','0','23277','0','0','0','Hexir','Necromancer Bot','','0','82','82','2','35','1','1.1','1.1','1','1','0','1800','1800','9','0','32','0','0','7','4096','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','necromancer_bot','-1'), +('70578','0','0','0','0','0','23277','0','0','0','Fikhar','Necromancer Bot','','0','82','82','2','35','1','1.1','1.1','1','1','0','1800','1800','9','0','32','0','0','7','4096','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','necromancer_bot','-1'), +('70579','0','0','0','0','0','23277','0','0','0','Drothum','Necromancer Bot','','0','82','82','2','35','1','1.1','1.1','1','1','0','1800','1800','9','0','32','0','0','7','4096','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','68157552','necromancer_bot','-1'), +('70580','0','0','0','0','0','200','0','0','0','Skeleton',NULL,'','0','82','82','2','35','0','1.2','1.3','1.1','0','0','2000','2000','1','0','0','0','0','6','4096','0','0','0','0','0','0','0','','0','1','1','1','1','0','0','0','0','135266400','necromancer_pet_bot','-1'); diff --git a/sql/Bots/updates/world/2022_01_02_03_npc_text.sql b/sql/Bots/updates/world/2022_01_02_03_npc_text.sql new file mode 100644 index 000000000..28fb9d0ab --- /dev/null +++ b/sql/Bots/updates/world/2022_01_02_03_npc_text.sql @@ -0,0 +1,15 @@ +-- +SET @LOCALIZED_STRINGS_START = 70107; +SET @LOCALIZED_STRINGS_END = 70107; + +DELETE FROM `npc_text` WHERE ID BETWEEN @LOCALIZED_STRINGS_START and @LOCALIZED_STRINGS_END; +INSERT INTO `npc_text` (`ID`,`text0_0`,`VerifiedBuild`) VALUES +('70107','|cff9900ccNecromancer|r$b|cffdd6600-=Warcraft III / Diablo II tribute=-|r$B$BSpell damage taken reduced by 20%, partially immune to control effects, cloth armor, deals spellshadow damage, no physical attack, spell power bonus: 100% intellect. Main attribute: Intellect.$B$BRaise Dead. Raises 2 Skeletons from a corpse (maximum 6 Skeletons, 65 seconds duration, only works on humanoids, beasts and dragonkin).$B$BUnholy Frenzy. Increases target\'s melee attack speed by 75%, but constantly drains health. Lasts 45 seconds. Cannot be cancelled. Unlocked at level 30.$B$BCorpse Explosion. Causes a corpse to explode, dealing damage equal to 35% to 75% of dead unit\'s maximum health (depends on Necromancer\'s level) to all surrounding enemies. This damage generates no threat. Unlocked at level 40.$B$BCripple. Reduces target\'s movement speed, melee attack speed and total strength by 50% for 60 seconds. Unlocked at level 50.','-1'); + +SET @LOCALIZED_STRINGS_START = 70646; +SET @LOCALIZED_STRINGS_END = 70647; + +DELETE FROM `npc_text` WHERE ID BETWEEN @LOCALIZED_STRINGS_START and @LOCALIZED_STRINGS_END; +INSERT INTO `npc_text` (`ID`,`text0_0`,`VerifiedBuild`) VALUES +('70646','Necromancers','-1'), +('70647','Necromancer','-1'); diff --git a/sql/Bots/updates/world/2022_01_31_npc_text.sql b/sql/Bots/updates/world/2022_01_31_npc_text.sql new file mode 100644 index 000000000..c33bf5d74 --- /dev/null +++ b/sql/Bots/updates/world/2022_01_31_npc_text.sql @@ -0,0 +1,10 @@ +-- + +SET @LOCALIZED_STRINGS_START = 70648; +SET @LOCALIZED_STRINGS_END = 70650; + +DELETE FROM `npc_text` WHERE ID BETWEEN @LOCALIZED_STRINGS_START and @LOCALIZED_STRINGS_END; +INSERT INTO `npc_text` (`ID`,`text0_0`,`VerifiedBuild`) VALUES +('70648','Attack angle','-1'), +('70649','Normal','-1'), +('70650','Avoid frontal AOE','-1'); diff --git a/sql/Bots/updates/world/2022_06_15_00_creature_template_npcbot_extras.sql b/sql/Bots/updates/world/2022_06_15_00_creature_template_npcbot_extras.sql new file mode 100644 index 000000000..12b9362cc --- /dev/null +++ b/sql/Bots/updates/world/2022_06_15_00_creature_template_npcbot_extras.sql @@ -0,0 +1,2 @@ +-- Maldryn race fix +UPDATE `creature_template_npcbot_extras` SET `race`='4' WHERE (`entry`='70413'); diff --git a/sql/Bots/updates/world/2022_06_17_npc_text.sql b/sql/Bots/updates/world/2022_06_17_npc_text.sql new file mode 100644 index 000000000..4e8ebf0b9 --- /dev/null +++ b/sql/Bots/updates/world/2022_06_17_npc_text.sql @@ -0,0 +1,3 @@ +-- + +UPDATE `npc_text` SET `text0_0`='|cff9900ccObsidian Destroyer|r$b|cffdd6600-=Warcraft III tribute=-|r$B$B\"An obsidian winged monstrocity with insatiable hunger for magic\".$B$BHigh armor, very high resistances, partially immune to magic, loses mana over time and doesn\'t benefit from passive mana regeneration effects, mail/plate armor, dual-wielding wands, deals spellshadow damage, no physical attack, cannot attack enemies not in front while moving, spell power bonus: 50% attack power + 200% intellect + wands damage.$B$BDevour Magic. Dispels up to 2 magic effects from enemies, up to 2 magic effects and up to 2 curses from allies and damaging summoned units in 20 yards area. Every dispelled effect restores 20% mana and 5% health, 7 seconds cooldown.$B$BShadow Blast. Empowered attack that deals increased splash damage.$B$BDrain Mana. Drains all mana (limited by caster\'s mana pool) from a random friendly unit.$B$BReplenish Mana. Energizes surrounding party and raid members within 25 yards for 3% of their maximum mana nullifying caster\'s mana, 3 seconds cooldown.$B$BRegenerating Aura. Heals surrounding party and raid members within 25 yards for 3% of their maximum health nullifying caster\'s mana, 3 seconds cooldown.$B$BShadow Armor (passive). Restores mana equal to a percentage of damage taken.$B$B' WHERE (`ID`='70102'); diff --git a/sql/Bots/updates/world/2022_06_22_00_creature_template_npcbot_extras.sql b/sql/Bots/updates/world/2022_06_22_00_creature_template_npcbot_extras.sql new file mode 100644 index 000000000..8dc2d4ba8 --- /dev/null +++ b/sql/Bots/updates/world/2022_06_22_00_creature_template_npcbot_extras.sql @@ -0,0 +1,11 @@ +-- +SET @BOT_START = 70581; +SET @BOT_END = 70585; + +DELETE FROM `creature_template_npcbot_extras` WHERE `entry` BETWEEN @BOT_START AND @BOT_END; +INSERT INTO `creature_template_npcbot_extras` (`entry`,`class`,`race`) VALUES +('70581', '19', '13'), +('70582', '19', '13'), +('70583', '19', '13'), +('70584', '19', '13'), +('70585', '19', '13'); diff --git a/sql/Bots/updates/world/2022_06_22_01_generate_equips_sea_witch.sql b/sql/Bots/updates/world/2022_06_22_01_generate_equips_sea_witch.sql new file mode 100644 index 000000000..766a70b40 --- /dev/null +++ b/sql/Bots/updates/world/2022_06_22_01_generate_equips_sea_witch.sql @@ -0,0 +1,50 @@ +/*!50003 DROP PROCEDURE IF EXISTS `sp__generate_seawitch_equips`*/; + +DELIMITER ;; + +/*!50003 CREATE*/ +/*!50003 PROCEDURE `sp__generate_seawitch_equips`() +BEGIN + +DECLARE CLASS_SEA_WITCH INT DEFAULT 19; + +DECLARE NPCBOT_ENTRY_BEGIN INT DEFAULT 70581; +DECLARE NPCBOT_ENTRY_END INT DEFAULT 70585; + +DECLARE cur_pos INT DEFAULT 0; +DECLARE myclass INT; +DECLARE myrace INT; +DECLARE item1 INT DEFAULT 0; +DECLARE item2 INT DEFAULT 0; +DECLARE item3 INT DEFAULT 0; + +DELETE FROM `creature_equip_template` WHERE `CreatureID` BETWEEN NPCBOT_ENTRY_BEGIN AND NPCBOT_ENTRY_END; + +SET cur_pos = NPCBOT_ENTRY_BEGIN; +WHILE cur_pos <= NPCBOT_ENTRY_END DO + SET myclass = (SELECT `class` FROM `creature_template_npcbot_extras` WHERE `entry` = cur_pos); + SET myrace = (SELECT `race` FROM `creature_template_npcbot_extras` WHERE `entry` = cur_pos); + + IF myclass != 0 AND myrace != 0 THEN + + IF myclass = CLASS_SEA_WITCH THEN + SET item1 = 20852; -- dagger + SET item2 = 20852; -- dagger + SET item3 = 17069; -- bow + END IF; + + INSERT INTO `creature_equip_template` (`CreatureID`,`ID`,`itemID1`,`itemID2`,`itemID3`,`VerifiedBuild`) VALUES (cur_pos,1,item1,item2,item3,-1); + + END IF; + + SET cur_pos = cur_pos + 1; + +END WHILE; + +END */;; + +DELIMITER ; + +CALL `sp__generate_seawitch_equips`(); + +DROP PROCEDURE IF EXISTS `sp__generate_seawitch_equips`; diff --git a/sql/Bots/updates/world/2022_06_22_02_creature_template.sql b/sql/Bots/updates/world/2022_06_22_02_creature_template.sql new file mode 100644 index 000000000..99477625f --- /dev/null +++ b/sql/Bots/updates/world/2022_06_22_02_creature_template.sql @@ -0,0 +1,21 @@ +-- +SET @BOT_START = 70581; +SET @BOT_END = 70586; + +DELETE FROM `creature_template` WHERE `entry` BETWEEN @BOT_START AND @BOT_END; + +INSERT INTO `creature_template` +(`entry`,`difficulty_entry_1`,`difficulty_entry_2`,`difficulty_entry_3`,`KillCredit1`,`KillCredit2`, +`modelid1`,`modelid2`,`modelid3`,`modelid4`,`name`,`subname`,`IconName`,`gossip_menu_id`,`minlevel`,`maxlevel`,`exp`, +`faction`,`npcflag`,`speed_walk`,`speed_run`,`scale`,`rank`,`dmgschool`,`BaseAttackTime`,`RangeAttackTime`, +`BaseVariance`,`RangeVariance`,`unit_class`,`unit_flags`,`unit_flags2`,`dynamicflags`,`family`,`type`,`type_flags`,`lootid`, +`pickpocketloot`,`skinloot`,`PetSpellDataId`,`VehicleId`,`mingold`,`maxgold`,`AIName`,`MovementType`, +`HoverHeight`,`HealthModifier`,`ManaModifier`,`ArmorModifier`,`DamageModifier`,`ExperienceModifier`,`RacialLeader`,`movementId`,`RegenHealth`, +`mechanic_immune_mask`,`spell_school_immune_mask`,`flags_extra`,`ScriptName`,`VerifiedBuild`) +VALUES +('70581','0','0','0','0','0','20748','0','0','0','Kondra','Sea Witch Bot','','0','83','83','2','35','1','1.05','1.05','0.5','2','0','1900','1900','1','1','8','0','32','0','0','7','4096','0','0','0','0','0','0','0','','0','1','1','1','1','1','1','0','0','0','0','0','68157552','sea_witch_bot','-1'), +('70582','0','0','0','0','0','20748','0','0','0','Serpentra','Sea Witch Bot','','0','83','83','2','35','1','1.05','1.05','0.5','2','0','1900','1900','1','1','8','0','32','0','0','7','4096','0','0','0','0','0','0','0','','0','1','1','1','1','1','1','0','0','0','0','0','68157552','sea_witch_bot','-1'), +('70583','0','0','0','0','0','20748','0','0','0','Serena','Sea Witch Bot','','0','83','83','2','35','1','1.05','1.05','0.5','2','0','1900','1900','1','1','8','0','32','0','0','7','4096','0','0','0','0','0','0','0','','0','1','1','1','1','1','1','0','0','0','0','0','68157552','sea_witch_bot','-1'), +('70584','0','0','0','0','0','20748','0','0','0','Asprah','Sea Witch Bot','','0','83','83','2','35','1','1.05','1.05','0.5','2','0','1900','1900','1','1','8','0','32','0','0','7','4096','0','0','0','0','0','0','0','','0','1','1','1','1','1','1','0','0','0','0','0','68157552','sea_witch_bot','-1'), +('70585','0','0','0','0','0','20748','0','0','0','Charib\'dishal','Sea Witch Bot','','0','83','83','2','35','1','1.05','1.05','0.5','2','0','1900','1900','1','1','8','0','32','0','0','7','4096','0','0','0','0','0','0','0','','0','1','1','1','1','1','1','0','0','0','0','0','68157552','sea_witch_bot','-1'), +('70586','0','0','0','0','0','20211','0','0','0','Tornado','','','0','83','83','2','35','0','0.9','0.9','3','1','0','2000','2000','1','1','1','33554432','32768','0','0','4','33554432','0','0','0','0','0','0','0','','0','1','1','1','1','1','1','0','0','0','0','0','671096832','sea_witch_pet_bot','-1'); diff --git a/sql/Bots/updates/world/2022_06_22_03_npc_text.sql b/sql/Bots/updates/world/2022_06_22_03_npc_text.sql new file mode 100644 index 000000000..b10c1c9cd --- /dev/null +++ b/sql/Bots/updates/world/2022_06_22_03_npc_text.sql @@ -0,0 +1,26 @@ +-- +SET @LOCALIZED_STRINGS_START = 70010; +SET @LOCALIZED_STRINGS_END = 70011; + +DELETE FROM `npc_text` WHERE ID BETWEEN @LOCALIZED_STRINGS_START and @LOCALIZED_STRINGS_END; +INSERT INTO `npc_text` (`ID`,`text0_0`,`VerifiedBuild`) VALUES +('70010','$B$BS-s-s-spare the words, mortal...','-1'), +('70011','Did I mess-s-s-s up my hair again? $B...No, I didn\'t. Then what is it?','-1'); + +SET @LOCALIZED_STRINGS_START = 70108; +SET @LOCALIZED_STRINGS_END = 70108; + +DELETE FROM `npc_text` WHERE ID BETWEEN @LOCALIZED_STRINGS_START and @LOCALIZED_STRINGS_END; +INSERT INTO `npc_text` (`ID`,`text0_0`,`VerifiedBuild`) VALUES +('70108','|cff0000ddSea Witch|r$b|cffdd6600-=Warcraft III tribute=-|r$B$B"A vicious scaly denizen of ocean often associated with the coming of prodigious storms".$B$BSpell damage taken reduced by 30%, partially immune to control effects, cloth armor, deals physical/spellfrost/spellstorm damage, attack power bonus: agility x2, spell power bonus: 200% intellect. Main attribute: Intellect.$B$BForked Lightning. Calls forth a cone of lightning to damage enemies. Hits from 2 to all targets (depending on Sea Witch\'s level), stunning them for 2 seconds. This damage generates no threat.$B$BFrost Arrows. Imbues arrow with spellfrost for extra damage, slowing target\'s movement, attack and casting speed by 30% to 70% (depending on Sea Witch\'s level).$B$BMana Shield. Creates a shield that absorbs 100% incoming (non-mitigated) damage by using Sea Witch\'s mana. Absorbs 2 to 10 damage per point of mana (depending on Sea Witch\'s level).$B$BTornado. Summons a fierce tornado that damages and slows nearby enemy units, sometimes incapacitating them completely. Tornado grows over time oudoors, increasing damage dealt and area of effect, but shrinks indoors, dissipating quickly. Unlocked at level 60.$B$BNaga (Passive). Swim speed increased by 200%, damage and dodge chance are greatly increased while in water.','-1'); + +SET @LOCALIZED_STRINGS_START = 70651; +SET @LOCALIZED_STRINGS_END = 70655; + +DELETE FROM `npc_text` WHERE ID BETWEEN @LOCALIZED_STRINGS_START and @LOCALIZED_STRINGS_END; +INSERT INTO `npc_text` (`ID`,`text0_0`,`VerifiedBuild`) VALUES +('70651','NIY','-1'), +('70652','Are you sure this is gonna work? It\'s better be the best water in the world...','-1'), +('70653','Seems like you could really use a drink of fresh water.','-1'), +('70654','Sea Witches','-1'), +('70655','Sea Witch','-1'); diff --git a/sql/Bots/updates/world/2022_06_23_00_command.sql b/sql/Bots/updates/world/2022_06_23_00_command.sql new file mode 100644 index 000000000..559db7f78 --- /dev/null +++ b/sql/Bots/updates/world/2022_06_23_00_command.sql @@ -0,0 +1,39 @@ +-- +DELETE FROM `command` WHERE `name` LIKE 'npcbot %'; +INSERT INTO `command` (`name`) VALUES +('npcbot add'), +('npcbot command'), +('npcbot command follow'), +('npcbot command standstill'), +('npcbot command stopfully'), +('npcbot delete'), +('npcbot distance'), +('npcbot distance attack'), +('npcbot distance attack long'), +('npcbot distance attack short'), +('npcbot dump'), +('npcbot dump load'), +('npcbot dump write'), +('npcbot hide'), +('npcbot info'), +('npcbot kill'), +('npcbot lookup'), +('npcbot move'), +('npcbot order'), +('npcbot order cast'), +('npcbot recall'), +('npcbot recall teleport'), +('npcbot reloadconfig'), +('npcbot remove'), +('npcbot revive'), +('npcbot set'), +('npcbot set faction'), +('npcbot set owner'), +('npcbot set spec'), +('npcbot show'), +('npcbot spawn'), +('npcbot spawned'), +('npcbot suicide'), +('npcbot unhide'), +('npcbot vehicle'), +('npcbot vehicle eject'); diff --git a/sql/Bots/updates/world/2022_06_24_00_npc_text.sql b/sql/Bots/updates/world/2022_06_24_00_npc_text.sql new file mode 100644 index 000000000..d0142c2e0 --- /dev/null +++ b/sql/Bots/updates/world/2022_06_24_00_npc_text.sql @@ -0,0 +1,7 @@ +-- +SET @LOCALIZED_STRINGS_START = 70108; +SET @LOCALIZED_STRINGS_END = 70108; + +DELETE FROM `npc_text` WHERE ID BETWEEN @LOCALIZED_STRINGS_START and @LOCALIZED_STRINGS_END; +INSERT INTO `npc_text` (`ID`,`text0_0`,`VerifiedBuild`) VALUES +('70108','|cff0000ddSea Witch|r$b|cffdd6600-=Warcraft III tribute=-|r$B$B"A vicious scaly denizen of ocean often associated with the coming of prodigious storms".$B$BSpell damage taken reduced by 30%, partially immune to control effects, cloth armor, deals physical/spellfrost/spellstorm damage, attack power bonus: agility x2, spell power bonus: 200% intellect. Main attribute: Intellect.$B$BForked Lightning. Calls forth a cone of lightning to damage enemies. Hits from 2 to all targets (depending on Sea Witch\'s level), stunning them for 2 seconds. This damage generates no threat.$B$BFrost Arrows. Imbues arrow with spellfrost for extra damage, slowing target\'s movement, attack and casting speed by 30% to 70% (depending on Sea Witch\'s level).$B$BMana Shield. Creates a shield that absorbs 100% incoming (non-mitigated) damage by using Sea Witch\'s mana. Effect ranges from 1 damage per 10 mana to 10 damage per 1 mana (depending on Sea Witch\'s level).$B$BTornado. Summons a fierce tornado that damages and slows nearby enemy units, sometimes incapacitating them completely. Tornado grows over time oudoors, increasing damage dealt and area of effect, but shrinks indoors, dissipating quickly. Unlocked at level 60.$B$BNaga (Passive). Swim speed increased by 200%, damage and dodge chance are greatly increased while in water.','-1'); diff --git a/sql/Bots/updates/world/2022_06_24_01_npc_text.sql b/sql/Bots/updates/world/2022_06_24_01_npc_text.sql new file mode 100644 index 000000000..7fd7be96a --- /dev/null +++ b/sql/Bots/updates/world/2022_06_24_01_npc_text.sql @@ -0,0 +1,8 @@ +-- +SET @LOCALIZED_STRINGS_START = 70656; +SET @LOCALIZED_STRINGS_END = 70657; + +DELETE FROM `npc_text` WHERE ID BETWEEN @LOCALIZED_STRINGS_START and @LOCALIZED_STRINGS_END; +INSERT INTO `npc_text` (`ID`,`text0_0`,`VerifiedBuild`) VALUES +('70656','Mana per damage','-1'), +('70657','Damage per mana','-1'); diff --git a/sql/Bots/updates/world/2022_06_24_02_command.sql b/sql/Bots/updates/world/2022_06_24_02_command.sql new file mode 100644 index 000000000..33e5d791d --- /dev/null +++ b/sql/Bots/updates/world/2022_06_24_02_command.sql @@ -0,0 +1,4 @@ +-- +DELETE FROM `command` WHERE `name`='npcbot command walk'; +INSERT INTO `command` (`name`) VALUES +('npcbot command walk'); diff --git a/sql/Bots/updates/world/2022_07_04_00_command.sql b/sql/Bots/updates/world/2022_07_04_00_command.sql new file mode 100644 index 000000000..eef20dd21 --- /dev/null +++ b/sql/Bots/updates/world/2022_07_04_00_command.sql @@ -0,0 +1,4 @@ +-- +DELETE FROM `command` WHERE `name`='npcbot createnew'; +INSERT INTO `command` (`name`) VALUES +('npcbot createnew'); diff --git a/sql/Bots/updates/world/2022_07_25_00_npc_text.sql b/sql/Bots/updates/world/2022_07_25_00_npc_text.sql new file mode 100644 index 000000000..9eee94a9d --- /dev/null +++ b/sql/Bots/updates/world/2022_07_25_00_npc_text.sql @@ -0,0 +1,7 @@ +-- +UPDATE `npc_text` SET `text0_0`='|cff0000ddArchmage|r$b|cffdd6600-=Warcraft III tribute=-|r$B$BSpell damage taken reduced by 35%, partially immune to control effects, cloth armor, deals spellsfire/spellfrost damage, no physical attack, spell power bonus: 100% intellect. Main attribute: Intellect.$B$BBlizzard. Your typical blizzard, just a little more powerful, 6 seconds cooldown.$B$BSummon Water Elemental. Summons a water elemental to attack archmage\'s enemies for 1 min, 20 seconds cooldown.$B$BBrilliance Aura. Increases maximum mana by 10% and greatly increases mana regeneration of party and raid members within 40 yards.$B$BMass Teleport. NIY.$B$B' WHERE (`ID`='70103'); +UPDATE `npc_text` SET `text0_0`='|cff9900ccDreadlord|r$b|cffdd6600-=Warcraft III tribute=-|r$B$B\"Incredibly powerful demon who wields power of darkness and mental domination\".$B$BHigh armor, high resistances, partially immune to control effects, damage taken speeds up spells recharge, plate armor, deals melee/spellshadow damage, bonus damage against incapacitated targets, spell power bonus: 200% strength. Main attribute: Strength.$B$BCarrion Swarm. Sends a horde of bats combined with chaotic magic to damage enemies in a very large frontal cone, cannot crit, 10 seconds cooldown.$B$BSleep. Puts the enemy target to sleep for 60 seconds and allows next physical attack on that target to bypass armor, direct damage caused will awaken the target, 6 seconds cooldown.$B$BVampiric Aura. Increases physical critical damage by 5% and heals party and raid members within 40 yards for a percentage (100% for Dreadlord and 25% for everyone else) of damage done by melee physical attacks and Carrion Swarm, no threat.$B$BSummon Infernal Servant. Calls an infernal down from the sky dealing damage and stunning enemy units, infernal is very resistant to magic and lasts 180 seconds, 180 seconds cooldown.$B$B' WHERE (`ID`='70104'); +UPDATE `npc_text` SET `text0_0`='|cff0000ddSpell Breaker|r$b|cffdd6600-=Warcraft III tribute=-|r$B$B\"An elven warrior trained to disrupt and contort magical energies\".$B$BSpell damage taken reduced by 75%, partially immune to control effects, armor penalty -30%, mail/plate armor, deals melee/arcane damage, spell power bonus: 200% strength. Main attribute: Strength.$B$BSteal Magic (Spellsteal). Steals a benefical spell from an enemy and applies it to a nearby ally or removes a negative spell from an ally and applies it to a nearby enemy, affects magic and curse effects, 2 seconds cooldown.$B$BFeedback (passive). Successful melee attacks burn target\'s mana equal to damage caused (increased by spellpower) dealing arcane damage. If target\'s mana is depleted, Spell Breaker\'s melee attacks will do triple damage with increased critical strike chance. If target does not have mana, Spell Breaker will gain mana equal to 25% of damage dealt.$B$B' WHERE (`ID`='70105'); +UPDATE `npc_text` SET `text0_0`='|cff9900ccDark Ranger|r$b|cffdd6600-=Warcraft III tribute=-|r$B$B\"A former ranger of Quel\'thalas forcibly raised from the dead\".$B$BSpell damage taken reduced by 35%, undead, partially immune to control effects, leather/cloth armor, deals physical/spellshadow damage, stick to shadows and generates no threat, spell power bonus: 50% intellect. Main attribute: Agility.$B$BSilence. Silences an enemy and up to 4 nearby targets for 8 seconds, 15 seconds cooldown.$B$BBlack Arrow. Fires a cursed arrow dealing 150% weapon damage and additional spellshadow damage over time. If affected target dies from Dark Ranger\'s damage, Dark Minion will spawn from the corpse (maximum 5 Minions, 80 seconds duration, only works on humanoids, beasts and dragonkin). Deals five times more damage if target is under 20% health.$B$BDrain Life. Drains health from an enemy every second for 5 seconds, healing Dark Ranger for 200% of the drained amount.$B$B' WHERE (`ID`='70106'); +UPDATE `npc_text` SET `text0_0`='|cff9900ccNecromancer|r$b|cffdd6600-=Warcraft III / Diablo II tribute=-|r$B$BSpell damage taken reduced by 20%, partially immune to control effects, cloth armor, deals spellshadow damage, no physical attack, spell power bonus: 100% intellect. Main attribute: Intellect.$B$BRaise Dead. Raises 2 Skeletons from a corpse (maximum 6 Skeletons, 65 seconds duration, only works on humanoids, beasts and dragonkin).$B$BUnholy Frenzy. Increases target\'s melee attack speed by 75%, but constantly drains health. Lasts 45 seconds. Cannot be cancelled. Unlocked at level 30.$B$BCorpse Explosion. Causes a corpse to explode, dealing damage equal to 35% to 75% of dead unit\'s maximum health (depends on Necromancer\'s level) to all surrounding enemies. This damage generates no threat. Unlocked at level 40.$B$BCripple. Reduces target\'s movement speed, melee attack speed and total strength by 50% for 60 seconds. Unlocked at level 50.$B$B' WHERE (`ID`='70107'); +UPDATE `npc_text` SET `text0_0`='|cff0000ddSea Witch|r$b|cffdd6600-=Warcraft III tribute=-|r$B$B\"A vicious scaly denizen of ocean often associated with the coming of prodigious storms\".$B$BSpell damage taken reduced by 30%, partially immune to control effects, cloth armor, deals physical/spellfrost/spellstorm damage, attack power bonus: agility x2, spell power bonus: 200% intellect. Main attribute: Intellect.$B$BForked Lightning. Calls forth a cone of lightning to damage enemies. Hits from 2 to all targets (depending on Sea Witch\'s level), stunning them for 2 seconds. This damage generates no threat.$B$BFrost Arrows. Imbues arrow with spellfrost for extra damage, slowing target\'s movement, attack and casting speed by 30% to 70% (depending on Sea Witch\'s level).$B$BMana Shield. Creates a shield that absorbs 100% incoming (non-mitigated) damage by using Sea Witch\'s mana. Effect ranges from 1 damage per 10 mana to 10 damage per 1 mana (depending on Sea Witch\'s level).$B$BTornado. Summons a fierce tornado that damages and slows nearby enemy units, sometimes incapacitating them completely. Tornado grows over time outdoors, increasing damage dealt and area of effect, but shrinks indoors, dissipating quickly. Unlocked at level 60.$B$BNaga (Passive). Swim speed, damage and dodge chance are greatly increased while in water.$B$B' WHERE (`ID`='70108'); diff --git a/sql/Bots/updates/world/2022_11_15_00_npc_text.sql b/sql/Bots/updates/world/2022_11_15_00_npc_text.sql new file mode 100644 index 000000000..9385da803 --- /dev/null +++ b/sql/Bots/updates/world/2022_11_15_00_npc_text.sql @@ -0,0 +1,7 @@ +-- +SET @LOCALIZED_STRINGS_START = 70658; +SET @LOCALIZED_STRINGS_END = 70658; + +DELETE FROM `npc_text` WHERE ID BETWEEN @LOCALIZED_STRINGS_START and @LOCALIZED_STRINGS_END; +INSERT INTO `npc_text` (`ID`,`text0_0`) VALUES +('70658','Transmogrification...'); diff --git a/sql/Bots/updates/world/2022_11_30_00_command.sql b/sql/Bots/updates/world/2022_11_30_00_command.sql new file mode 100644 index 000000000..774534e3f --- /dev/null +++ b/sql/Bots/updates/world/2022_11_30_00_command.sql @@ -0,0 +1,4 @@ +-- +DELETE FROM `command` WHERE `name`='npcbot sendto'; +INSERT INTO `command` (`name`) VALUES +('npcbot sendto'); diff --git a/sql/Bots/updates/world/2022_12_08_00_npc_text.sql b/sql/Bots/updates/world/2022_12_08_00_npc_text.sql new file mode 100644 index 000000000..e39b014b1 --- /dev/null +++ b/sql/Bots/updates/world/2022_12_08_00_npc_text.sql @@ -0,0 +1,8 @@ +-- +SET @BOT_START = 70659; +SET @BOT_END = 70659; + +DELETE FROM `npc_text` WHERE `ID` BETWEEN @BOT_START AND @BOT_END AND `ID` != 70100; + +INSERT INTO `npc_text` (`ID`,`text0_0`,`VerifiedBuild`) VALUES +(@BOT_START+0, "DISABLE combat positioning", -1); diff --git a/sql/Bots/updates/world/2022_12_17_00_command.sql b/sql/Bots/updates/world/2022_12_17_00_command.sql new file mode 100644 index 000000000..ad173b9c8 --- /dev/null +++ b/sql/Bots/updates/world/2022_12_17_00_command.sql @@ -0,0 +1,7 @@ +-- +DELETE FROM `command` WHERE `name` IN ('npcbot list spawned','npcbot list spawned free','npcbot delete free','npcbot delete id'); +INSERT INTO `command` (`name`) VALUES +('npcbot list spawned'), +('npcbot list spawned free'), +('npcbot delete free'), +('npcbot delete id'); diff --git a/sql/Bots/updates/world/2022_12_17_01_command.sql b/sql/Bots/updates/world/2022_12_17_01_command.sql new file mode 100644 index 000000000..61e785282 --- /dev/null +++ b/sql/Bots/updates/world/2022_12_17_01_command.sql @@ -0,0 +1,2 @@ +-- +DELETE FROM `command` WHERE `name` = 'npcbot spawned'; diff --git a/sql/Bots/updates/world/2022_12_23_00_command.sql b/sql/Bots/updates/world/2022_12_23_00_command.sql new file mode 100644 index 000000000..658014fa8 --- /dev/null +++ b/sql/Bots/updates/world/2022_12_23_00_command.sql @@ -0,0 +1,6 @@ +-- +DELETE FROM `command` WHERE `name` IN ('npcbot command nogossip','npcbot command rebind','npcbot command unbind'); +INSERT INTO `command` (`name`) VALUES +('npcbot command nogossip'), +('npcbot command rebind'), +('npcbot command unbind'); diff --git a/sql/Bots/updates/world/2023_01_02_00_npc_text.sql b/sql/Bots/updates/world/2023_01_02_00_npc_text.sql new file mode 100644 index 000000000..44625a44f --- /dev/null +++ b/sql/Bots/updates/world/2023_01_02_00_npc_text.sql @@ -0,0 +1,8 @@ +-- +SET @BOT_START = 70660; +SET @BOT_END = 70660; + +DELETE FROM `npc_text` WHERE `ID` BETWEEN @BOT_START AND @BOT_END; + +INSERT INTO `npc_text` (`ID`,`text0_0`,`VerifiedBuild`) VALUES +(@BOT_START+0, "Priority target", -1); diff --git a/sql/Bots/updates/world/2023_01_10_00_command.sql b/sql/Bots/updates/world/2023_01_10_00_command.sql new file mode 100644 index 000000000..83198f423 --- /dev/null +++ b/sql/Bots/updates/world/2023_01_10_00_command.sql @@ -0,0 +1,4 @@ +-- +DELETE FROM `command` WHERE `name` = 'npcbot sendto last'; +INSERT INTO `command` (`name`) VALUES +('npcbot sendto last'); diff --git a/sql/Bots/updates/world/2023_01_11_00_command.sql b/sql/Bots/updates/world/2023_01_11_00_command.sql new file mode 100644 index 000000000..f72d41fcc --- /dev/null +++ b/sql/Bots/updates/world/2023_01_11_00_command.sql @@ -0,0 +1,5 @@ +-- +DELETE FROM `command` WHERE `name` IN ('npcbot sendto point','npcbot sendto point set'); +INSERT INTO `command` (`name`) VALUES +('npcbot sendto point'), +('npcbot sendto point set'); diff --git a/sql/Bots/updates/world/2023_03_10_00_command.sql b/sql/Bots/updates/world/2023_03_10_00_command.sql new file mode 100644 index 000000000..410df3645 --- /dev/null +++ b/sql/Bots/updates/world/2023_03_10_00_command.sql @@ -0,0 +1,4 @@ +-- +DELETE FROM `command` WHERE `name` = 'npcbot go'; +INSERT INTO `command` (`name`) VALUES +('npcbot go'); diff --git a/sql/Bots/updates/world/2023_03_10_01_command.sql b/sql/Bots/updates/world/2023_03_10_01_command.sql new file mode 100644 index 000000000..56e22c9bc --- /dev/null +++ b/sql/Bots/updates/world/2023_03_10_01_command.sql @@ -0,0 +1,4 @@ +-- +DELETE FROM `command` WHERE `name` = 'npcbot recall spawns'; +INSERT INTO `command` (`name`) VALUES +('npcbot recall spawns'); diff --git a/sql/Bots/updates/world/2023_03_12_00_creature_template_npcbot_wander_nodes.sql b/sql/Bots/updates/world/2023_03_12_00_creature_template_npcbot_wander_nodes.sql new file mode 100644 index 000000000..d6589a6e9 --- /dev/null +++ b/sql/Bots/updates/world/2023_03_12_00_creature_template_npcbot_wander_nodes.sql @@ -0,0 +1,18 @@ +-- +DROP TABLE IF EXISTS `creature_template_npcbot_wander_nodes`; +CREATE TABLE `creature_template_npcbot_wander_nodes` ( + `id` int(10) unsigned NOT NULL, + `name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'RENAME_ME', + `mapid` smallint(5) unsigned NOT NULL DEFAULT '0', + `zoneid` int(10) unsigned NOT NULL DEFAULT '0', + `areaid` int(10) unsigned NOT NULL DEFAULT '0', + `minlevel` tinyint(3) unsigned NOT NULL DEFAULT '0', + `maxlevel` tinyint(3) unsigned NOT NULL DEFAULT '0', + `flags` int(10) unsigned NOT NULL DEFAULT '0', + `x` float NOT NULL DEFAULT '0', + `y` float NOT NULL DEFAULT '0', + `z` float NOT NULL DEFAULT '0', + `o` float NOT NULL DEFAULT '0', + `links` mediumtext COLLATE utf8mb4_unicode_ci, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Bot Wander Map'; diff --git a/sql/Bots/updates/world/2023_03_14_00_creature_wander_nodes.sql b/sql/Bots/updates/world/2023_03_14_00_creature_wander_nodes.sql new file mode 100644 index 000000000..ebd3093bb --- /dev/null +++ b/sql/Bots/updates/world/2023_03_14_00_creature_wander_nodes.sql @@ -0,0 +1,2 @@ +-- +DROP TABLE IF EXISTS `creature_wander_nodes`; diff --git a/sql/Bots/updates/world/2023_03_21_00_creature_template_npcbot_wander_nodes.sql b/sql/Bots/updates/world/2023_03_21_00_creature_template_npcbot_wander_nodes.sql new file mode 100644 index 000000000..57d005227 --- /dev/null +++ b/sql/Bots/updates/world/2023_03_21_00_creature_template_npcbot_wander_nodes.sql @@ -0,0 +1,964 @@ +-- +DROP TABLE IF EXISTS `creature_template_npcbot_wander_nodes`; + +CREATE TABLE `creature_template_npcbot_wander_nodes` ( + `id` int(10) unsigned NOT NULL, + `name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'RENAME_ME', + `mapid` smallint(5) unsigned NOT NULL DEFAULT '0', + `zoneid` int(10) unsigned NOT NULL DEFAULT '0', + `areaid` int(10) unsigned NOT NULL DEFAULT '0', + `minlevel` tinyint(3) unsigned NOT NULL DEFAULT '0', + `maxlevel` tinyint(3) unsigned NOT NULL DEFAULT '0', + `flags` int(10) unsigned NOT NULL DEFAULT '0', + `x` float NOT NULL DEFAULT '0', + `y` float NOT NULL DEFAULT '0', + `z` float NOT NULL DEFAULT '0', + `o` float NOT NULL DEFAULT '0', + `links` mediumtext COLLATE utf8mb4_unicode_ci, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Bot Wander Map'; + +INSERT INTO `creature_template_npcbot_wander_nodes`(`id`,`name`,`mapid`,`zoneid`,`areaid`,`minlevel`,`maxlevel`,`flags`,`x`,`y`,`z`,`o`,`links`) VALUES +(1,'Anvilmar',0,1,132,1,5,2,-6077.84,384.826,393.598,4.63263,'542:0 '), +(2,'Brill',0,85,159,1,10,5,2249.85,278.414,34.1142,5.11546,'18:0 277:0 278:0 279:0 281:0 '), +(3,'Darkshire',0,10,42,18,30,3,-10560.6,-1182.34,27.9637,3.1803,'783:0 784:0 787:0 788:0 789:0 '), +(4,'Deathknell',0,85,154,1,10,5,1879.83,1588.2,90.1725,5.25172,'295:0 296:0 '), +(5,'Dun Algaz',0,11,836,18,30,0,-4245.13,-2356.42,204.034,3.9477,'483:0 '), +(6,'Dun Modr',0,11,205,18,30,0,-2610.26,-2350.56,81.5918,1.1263,'444:0 '), +(7,'Goldshire',0,12,87,1,10,3,-9480.09,63.5218,56.1755,3.4173,'88:0 71:0 16:0 42:0 72:0 710:0 709:0 714:0 717:0 '), +(8,'Ironforge',0,1,809,1,10,2,-5023.64,-831.425,495.319,5.37056,'559:0 560:0 '), +(9,'Kharanos',0,1,131,1,10,3,-5501.22,-472.604,408.453,2.4002,'537:0 545:0 557:0 '), +(10,'Lakeshire',0,44,69,13,25,3,-9235.12,-2145.54,71.2121,5.71383,'665:0 666:0 '), +(11,'Menethil Harbor',0,11,150,18,30,3,-3672.7,-828.455,9.89925,3.18444,'476:0 477:0 478:0 '), +(12,'Moonbrook',0,40,20,8,20,2,-11017.1,1510.17,43.1667,2.58627,'737:0 738:0 736:0 747:0 '), +(13,'Northshire Abbey',0,12,9,1,10,3,-8900.51,-116.199,81.8499,0.369434,'710:0 70:0 73:0 '), +(14,'Raven Hill',0,10,94,18,30,0,-10805.5,291.025,30.9282,3.92422,'751:0 753:0 754:0 755:0 766:0 '), +(15,'Sentinel Hill',0,40,108,8,20,3,-10509.5,1047.74,60.519,5.20046,'741:0 742:0 745:0 746:0 736:0 750:0 '), +(16,'Stormwind City',0,12,12,1,10,2,-9153.77,364.057,90.151,5.16709,'42:0 710:0 712:0 7:0 '), +(17,'The Sepulcher',0,130,228,8,20,5,470.768,1589.82,126.632,5.16292,'305:0 '), +(18,'Undercity',0,85,153,1,10,5,1885.94,236.924,58.0313,3.1016,'2:0 285:0 '), +(19,'Thelsamar',0,38,144,8,20,3,-5334.7,-3015.26,324.2,1.73643,'503:0 504:0 '), +(20,'Southshore',0,267,271,18,30,3,-803.031,-531.727,15.9656,1.39359,'362:0 363:0 376:0 384:0 385:0 356:0 '), +(21,'Tarren Mill',0,267,272,18,30,5,-27.0354,-900.562,55.9602,4.57301,'378:0 380:0 379:0 384:0 '), +(22,'Refuge Pointe',0,45,320,28,40,3,-1280.81,-2466.89,34.7175,5.16384,'78:0 416:0 419:0 '), +(23,'Hammerfall',0,45,321,28,40,5,-955.494,-3540.82,56.7101,3.23882,'438:0 '), +(24,'Kargath',0,3,340,33,45,5,-6625,-2152.82,249.141,4.5492,'594:0 598:0 '), +(25,'Angor Fortress',0,3,338,33,45,0,-6392.65,-3158,299.765,4.68826,'608:0 620:0 '), +(26,'Bloodhoof Village',1,215,222,1,10,0,-2323.92,-394.981,-9.12757,0.10888,''), +(27,'Camp Narache',1,215,221,1,10,0,-2906.49,-253.607,52.941,5.73627,''), +(28,'Razor Hill',1,14,362,1,10,0,312.659,-4745.52,9.54734,1.10362,'66:0 '), +(29,'The Den',1,14,363,1,10,0,-604.098,-4202.92,38.7281,1.10189,''), +(30,'Orgrimmar',1,14,14,1,10,0,1381.77,-4371.16,26.023,2.19128,''), +(31,'Senjin Village',1,14,367,1,10,0,-819.492,-4918.24,18.3507,2.67153,''), +(32,'The Crossroads',1,17,380,8,20,0,-455.9,-2652.15,95.5864,4.41391,''), +(33,'Aldrassil',1,141,256,1,10,0,10462.7,805.993,1312.66,0.945149,'61:0 62:0 63:0 65:0 '), +(34,'Dolanaar',1,141,186,1,10,0,9787.99,949.897,1306.73,0.734179,'63:0 '), +(35,'Ratchet',1,17,392,8,20,0,-951.364,-3680.07,8.04046,0.798474,''), +(36,'Booty Bay',0,33,35,33,45,0,-14308.8,440.139,25.5878,0.828079,'959:0 960:0 961:0 '), +(37,'Gromgol Base Camp',0,33,117,33,45,5,-12416.5,185.125,1.83125,6.17782,'904:0 900:0 908:0 '), +(38,'Astranaar',1,331,415,18,30,0,2720.43,-382.391,107.089,1.24727,''), +(39,'Stonetalon Peak',1,406,467,13,25,0,2658.78,1449.71,226.135,5.85107,'155:0 '), +(40,'Thalanaar',1,357,489,38,50,0,-4510.03,-779.474,-41.5367,0.513916,''), +(41,'Freewind Post',1,400,484,23,35,0,-5454.07,-2445.5,90.1122,6.10667,'119:0 120:0 '), +(42,'Mirror Lake',0,12,92,1,10,2,-9389.26,458.427,38.2852,2.4529,'72:0 16:0 88:0 7:0 '), +(44,'Drywhisker Gorge',0,45,318,28,40,0,-1014.22,-3827.39,145.41,2.59534,'440:0 '), +(45,'Sun Rock Retreat',1,406,460,13,25,0,936.308,910.974,104.44,0.864181,'151:0 '), +(46,'Temple of Zin-Malor',1,16,1223,43,54,0,3549.15,-5359.12,188.348,1.22969,''), +(47,'Hetaeras Clutch',1,16,1222,43,54,0,3556.64,-6232.79,44.0085,4.90175,'141:0 195:0 '), +(48,'Uthers Tomb',0,28,196,48,56,0,1021.5,-1809.43,77.1448,3.5376,'271:0 '), +(49,'Scholomance',0,139,2057,53,60,0,1262.19,-2579.41,123.746,4.56181,''), +(50,'Whitereach Post',1,400,2237,23,35,0,-4917.35,-1375.5,-52.612,3.05417,''), +(51,'Stonard',0,8,75,33,45,5,-10445,-3261.11,20.179,5.57373,'828:0 829:0 830:0 '), +(52,'Gadgetzan',1,440,976,38,50,0,-7139.15,-3752.11,8.53951,2.41012,''), +(53,'Camp Mojache',1,357,1099,38,50,0,-4394.98,215.611,25.4138,0.668987,'136:0 137:0 '), +(54,'Theramore Isle',1,15,513,33,45,0,-3680.17,-4388.51,10.5544,2.126,'109:0 '), +(55,'Aerie Peak',0,47,348,38,50,0,170.251,-2085.14,112.706,5.73674,'82:0 386:0 387:0 388:0 '), +(56,'Everlook',1,618,2255,53,60,0,6723.46,-4662.5,720.986,4.0489,'143:0 147:0 '), +(57,'Shadowprey Village',1,405,2408,28,40,0,-1657.85,3097.92,30.5002,4.53043,'127:0 '), +(58,'Feathermoon Stronghold',1,357,1116,38,50,0,-4434.99,3276.74,11.7782,1.86721,''), +(59,'Auberdine',1,148,442,8,20,0,6439.33,411.951,10.8632,4.98592,''), +(60,'Nijels Point',1,405,608,28,40,0,202.521,1308.24,190.237,3.56577,''), +(61,'Aldrassil',1,141,256,1,10,0,10479.6,812.281,1322.74,1.7975,'33:0 62:0 63:0 65:0 '), +(62,'Shadowthread Cave',1,141,257,1,10,0,10756.2,921.301,1338.56,2.9431,'33:0 61:0 65:0 '), +(63,'Fel Rock',1,141,258,1,10,0,10050.6,1031.2,1329.95,1.85839,'33:0 34:0 61:0 65:0 '), +(64,'Banethil Barrow Den',1,141,736,1,10,0,9864.17,1557.41,1328.3,4.60699,''), +(65,'The Cleft',1,141,263,1,10,0,10316.4,1200.62,1458.08,3.62911,'33:0 61:0 62:0 63:0 '), +(66,'Razorwind Canyon',1,14,410,1,10,0,636.963,-4534.04,8.82068,1.74237,'28:0 67:0 '), +(67,'Torkren Farm',1,14,2979,1,10,0,726.297,-4242.41,17.2915,1.86203,'66:0 '), +(69,'Ironbands Compound',0,1,716,1,10,2,-5858.7,-2004.1,401.648,0.478922,'523:0 528:0 '), +(70,'Echo Ridge Mine',0,12,34,1,10,2,-8691.38,-113.152,89.09,5.86134,'73:0 13:0 711:0 '), +(71,'Maclure Vineyards',0,12,64,1,10,2,-9948.39,69.1568,33.3005,5.49869,'72:0 7:0 714:0 716:0 '), +(72,'Stonefield Farm',0,12,63,1,10,2,-9901.14,378.19,35.2801,5.75613,'71:0 7:0 42:0 88:0 713:0 714:0 717:0 '), +(73,'Northshire Vineyards',0,12,59,1,10,2,-9067.35,-333.965,73.4519,1.09469,'70:0 13:0 '), +(74,'Tower of Ilgalar',0,44,96,18,25,0,-9282.21,-3330.43,115.604,1.41034,'683:0 685:0 686:0 '), +(75,'Renders Rock',0,44,998,13,25,0,-8677.89,-2302.91,155.917,1.18325,'661:0 662:0 '), +(76,'Spirit Den',0,33,1742,33,45,0,-13751.4,-18.3165,44.0002,0.851192,'964:0 '), +(77,'Rethban Caverns',0,44,98,13,25,2,-8976.38,-2016.83,136.063,1.26142,'670:0 671:0 '), +(78,'Boulderfist Outpost',0,45,1858,28,40,0,-1183.46,-2180.67,55.5524,1.6849,'22:0 415:0 416:0 419:0 '), +(79,'Groshgok Compound',0,41,2937,50,60,0,-11094.7,-2311.06,117.13,4.77931,'800:0 803:0 804:0 '), +(80,'Stagalbog Cave',0,8,1817,33,45,0,-10775.6,-3747.85,22.347,3.74768,'186:0 825:0 824:0 826:0 828:0 832:0 '), +(81,'The Tower of Arathor',0,45,324,28,40,0,-1760.68,-1537.05,64.8696,2.02383,'425:0 '), +(82,'Wildhammer Keep',0,47,349,38,50,0,257.234,-2208.21,142.103,6.19816,'55:0 387:0 388:0 '), +(83,'Night Webs Hollow',0,85,155,1,10,4,2053.22,1802.16,99.8006,1.83635,'295:0 '), +(84,'Terrorweb Tunnel',0,139,2626,53,60,0,3035.33,-2773.9,100.473,5.05065,'215:0 243:0 '), +(85,'Browman Mill',0,139,2269,53,60,0,2483.98,-5183.79,76.1135,3.20045,'234:0 211:0 '), +(86,'Uldaman',0,3,1337,33,45,0,-6092.01,-3179.35,255.852,5.9237,'621:0 '), +(87,'Dustbelch Grotto',0,3,347,33,45,0,-7299.86,-2270.26,244.599,3.07308,'595:0 '), +(88,'Westbrook Garrison',0,12,120,1,10,2,-9671.96,690.134,36.5414,5.31336,'72:0 42:0 713:0 7:0 717:0 '), +(89,'Jasperlode Mine',0,12,54,1,10,0,-9194.18,-610.205,60.7892,0.426731,'701:0 702:0 707:0 708:0 '), +(91,'Algaz Station',0,38,925,8,20,0,-4880.26,-2723,328.908,0.873682,'485:0 486:0 487:0 500:0 502:0 '), +(92,'Grim Batol',0,11,1037,61,70,0,-4130,-3468.16,259.76,0.014411,'454:0 '), +(93,'Venture Co. Mine',1,215,215,1,10,0,-1501.49,-1036.19,151.717,1.01756,''), +(94,'Blackwood Den',1,148,455,8,20,0,4619.42,25.6126,69.7834,4.80597,'95:0 '), +(95,'Wildbend River',1,148,454,8,20,0,5056.04,220.755,22.9081,5.20884,'94:0 '), +(96,'Cliffspring Falls',1,148,445,8,20,0,6870.57,-662.396,82.9506,0.302464,''), +(97,'Fray Island',1,17,720,8,20,0,-1668.51,-4329.2,3.26569,0.222455,''), +(98,'Wailing Caverns',1,17,718,8,20,0,-837.968,-2037,80.467,1.58062,''), +(99,'Shady Rest Inn',1,15,403,33,45,0,-3723.63,-2535.18,69.5651,1.00004,'103:0 '), +(100,'Darkmist Cavern',1,15,499,33,45,0,-2829.79,-2722.81,36.7883,2.20851,'102:0 187:0 '), +(101,'North Point Tower',1,15,504,33,45,0,-2884.79,-3429.99,39.357,3.86048,'102:0 110:0 '), +(102,'Bluefen',1,15,507,33,45,0,-2685.32,-3087.58,41.4492,4.02905,'100:0 101:0 187:0 '), +(103,'Lost Point',1,15,506,33,45,0,-3925.5,-2862.15,46.4791,4.55427,'99:0 108:0 '), +(104,'Tidefury Cove',1,15,517,33,45,0,-4300.98,-4062.71,-10.5964,2.84709,'106:0 '), +(105,'Stonemaul Ruins',1,15,508,33,45,0,-4346.02,-3321.15,34.2542,6.16796,'106:0 233:0 '), +(106,'Onyxias Lair',1,15,511,33,45,0,-4698.06,-3720.58,47.1529,0.469848,'104:0 105:0 107:0 '), +(107,'Emberstrifes Den',1,15,2158,33,45,0,-4987.73,-3832.99,44.1305,3.70876,'106:0 '), +(108,'Bloodfen Burrow',1,15,498,33,45,0,-4335.04,-2639.53,38.0638,1.30845,'103:0 '), +(109,'Sentry Point',1,15,503,33,45,0,-3476.59,-4109.08,17.1041,4.05466,'54:0 '), +(110,'Swamplight Manor',1,15,497,33,45,0,-2949.47,-3893.5,35.0303,1.23141,'101:0 '), +(111,'Marshals Refuge',1,490,541,46,56,0,-6143.21,-1078.89,-198.367,5.66637,''), +(112,'Fungal Rock',1,490,542,46,56,0,-6370.15,-1836.52,-260.084,0.958035,''), +(113,'Wavestrider Beach',1,440,988,38,50,0,-7693.45,-4878.96,0.661957,4.5132,''), +(114,'Uldum',1,440,989,38,50,0,-9635.41,-2787.2,7.85617,5.44167,''), +(115,'Darkcloud Pinnacle',1,400,2097,23,35,0,-5086.21,-1919.44,88.1806,6.15461,'116:0 119:0 '), +(116,'Roguefeather Den',1,400,487,23,35,0,-5466.71,-1636.27,29.0246,3.60035,'115:0 '), +(117,'Mirage Raceway',1,400,2240,23,35,0,-6239.42,-3973.12,-58.7501,5.06259,'122:0 123:0 '), +(118,'The Rustmaul Digsite',1,400,479,23,35,0,-6490.61,-3449.15,-58.7821,3.58106,'123:0 '), +(119,'Splithoof Hold',1,400,1557,23,35,0,-5065.61,-2367.61,-53.6725,4.0206,'41:0 115:0 120:0 '), +(120,'The Weathered Nook',1,400,2303,23,35,0,-5213.85,-2794.53,-7.83752,2.24538,'41:0 119:0 '), +(121,'Ironstone Camp',1,400,3037,23,35,0,-5848.05,-3412.38,-51.0548,0.373431,'122:0 '), +(122,'Weazels Crater',1,400,3038,23,35,0,-5799.94,-3899.53,-96.7429,1.92693,'117:0 121:0 '), +(123,'Tahonda Ruins',1,400,3039,23,35,0,-6569.91,-3894.97,-58.7495,0.100017,'117:0 118:0 '), +(124,'Rolands Doom',0,10,2161,24,30,0,-11045.3,-1130.98,38.585,3.65547,'780:0 781:0 '), +(126,'Bolgans Hole',1,405,600,28,40,0,-2281.95,2499.6,73.7525,5.34105,''), +(127,'Maraudon',1,405,2100,28,40,0,-1422.62,2918.45,136.147,4.46498,'57:0 '), +(128,'Ghost Walker Post',1,405,597,28,40,0,-1224.06,1736.67,90.0219,0.112112,'129:0 '), +(129,'Scrabblescrews Camp',1,405,2617,28,40,0,-1407.87,1493.13,60.8875,4.75641,'128:0 '), +(130,'Valley of Bones',1,405,2657,28,40,0,-2251.19,1513.55,63.5889,6.25387,''), +(131,'Rage Scar Hold',1,357,1115,38,50,0,-3842.71,1738.94,142.708,2.30123,''), +(132,'Ruins of Solarsal',1,357,1117,38,50,0,-4852.91,3616.63,16.4186,4.11504,''), +(133,'Ravaged Twilight Camp',1,1377,3100,53,60,0,-6206.25,1766.95,17.464,4.2566,''), +(134,'Twilight Post',1,1377,3098,53,60,0,-6740.04,1636.32,15.2836,4.04162,'189:0 202:0 '), +(135,'Twilight Outpost',1,1377,3099,53,60,0,-7929.11,1833.28,4.86506,0.733634,''), +(136,'Woodpaw Hills',1,357,2519,38,50,0,-4915.81,199.275,52.2959,6.13256,'53:0 '), +(137,'Lariss Pavillion',1,357,2518,38,50,0,-4052.72,81.5525,77.1755,5.22503,'53:0 '), +(138,'Slither Rock',0,46,2419,48,56,0,-7653.86,-2991.1,135.917,1.62615,'648:0 650:0 652:0 '), +(139,'Flame Crest',0,46,251,48,56,0,-7486.64,-2184.45,166.505,5.87909,'646:0 '), +(140,'Shalzarus Lair',1,357,3117,38,50,0,-5492.37,3622.79,1.62975,3.27216,''), +(141,'Scalebeards Cave',1,16,3140,43,54,0,3705.1,-6043.45,2.50768,0.191079,'47:0 195:0 '), +(142,'Timbermaw Hold',1,618,618,53,60,0,6900.63,-2298.18,588.52,1.94812,'148:0 '), +(143,'The Ruins of KelTheril',1,618,2252,53,60,0,6426.76,-4304.99,664.687,3.04093,'56:0 194:0 '), +(144,'Dun Mandarr',1,618,2248,53,60,0,5718.83,-4507.31,761.083,4.26757,''), +(145,'Talrendis Point',1,16,3137,43,54,0,2707.72,-3869.25,104.208,5.79411,''), +(146,'Rethress Sanctum',1,16,3138,43,54,0,2195.67,-6438.33,2.1301,4.39206,''), +(147,'Moon Horror Den',1,618,3139,53,60,0,7123.02,-4631.57,639.68,2.0052,'56:0 '), +(148,'Timbermaw Hold',1,361,1769,46,56,0,6817.78,-2097.68,624.839,2.56313,'142:0 '), +(149,'Irontree Cavern',1,361,1768,46,56,0,6481.87,-1571,438.965,0.859127,''), +(150,'Bloodvenom Post',1,361,1997,46,56,0,5111.98,-353.403,357.231,5.23802,''), +(151,'Sishir Canyon',1,406,2541,13,25,0,515.845,624.05,68.1596,5.69462,'45:0 '), +(152,'Cragpool Lake',1,406,463,13,25,0,1543.57,70.1338,-10.3835,0.632571,''), +(153,'Windshear Mine',1,406,461,13,25,0,981.949,-358.9,14.3123,3.71479,''), +(154,'The Talondeep Path',1,406,1277,13,25,0,1531.94,-576.57,67.9212,5.18318,'168:0 '), +(155,'The Talon Den',1,406,468,13,25,0,2416.89,1792.39,393.641,3.36062,'39:0 '), +(156,'The Ruins of OrdilAran',1,331,412,18,30,0,3493.89,-119.601,0.714987,2.44963,'157:0 '), +(157,'Bathrans Haunt',1,331,411,18,30,0,3827.37,-161.305,-0.60768,3.24941,'156:0 '), +(158,'Zoramgar Outpost',1,331,2897,18,30,0,3362.22,1010.16,3.59814,2.49558,''), +(159,'Falfarren River',1,331,433,18,30,0,2231.21,-2222.91,94.4116,6.0491,'163:0 175:0 '), +(160,'Xavian',1,331,429,18,30,0,2936.56,-2822.67,212.731,0.0776333,'161:0 '), +(161,'Forest Song',1,331,2358,18,30,0,2880.33,-3287.54,156.204,1.1163,'160:0 '), +(162,'Demon Fall Canyon',1,331,435,18,30,0,1702.45,-3150.99,93.5217,3.37253,'164:0 '), +(163,'Silverwing Outpost',1,331,2360,18,30,0,1776.64,-2065.15,102.283,2.23373,'159:0 169:0 182:0 '), +(164,'The DorDanil Barrow Den',1,331,432,18,30,0,1776.23,-2678.36,111.666,5.44343,'162:0 169:0 175:0 '), +(165,'Silverwind Refuge',1,331,420,18,30,0,2130.27,-1190.25,99.3456,1.77657,'166:0 168:0 '), +(166,'Greenpaw Village',1,331,2359,18,30,0,2291.29,-1444.96,87.8806,1.08665,'165:0 '), +(167,'Bloodtooth Camp',1,331,2357,18,30,0,1612.52,-1465.1,157.023,5.78447,'182:0 '), +(168,'The Talondeep Path',1,406,1277,13,25,0,1930.26,-732.677,114.534,3.7975,'154:0 165:0 '), +(169,'Warsong Labor Camp',1,331,3177,18,30,0,1575.91,-2463.94,98.549,2.24558,'163:0 164:0 '), +(170,'Aridens Camp',0,41,2560,50,60,0,-10443.3,-2141.1,90.7796,5.92189,'801:0 '), +(171,'Deadwind Ravine',0,41,2558,50,60,0,-10607.9,-1904.89,117.201,2.66188,'798:0 799:0 '), +(172,'Sleeping Gorge',0,41,2938,50,60,0,-10740.7,-1951.45,121.127,3.69664,'800:0 801:0 '), +(173,'Chillwind Camp',0,28,3197,48,56,1,940.955,-1419.2,66.7723,0.796556,'271:0 272:0 334:0 '), +(174,'Camp Taurajo',1,17,378,8,20,0,-2352.66,-1921.67,95.7825,1.62324,''), +(175,'Splintertree Post',1,331,431,18,30,0,2286.41,-2564.67,105.481,4.11999,'159:0 164:0 '), +(176,'The Swarming Pillar',1,1377,3097,53,60,0,-7066.79,731.852,67.2655,1.04864,'177:0 188:0 189:0 205:0 '), +(177,'Bones of Grakkarond',1,1377,3257,53,60,0,-7234.61,874.224,0.285286,1.72442,'176:0 188:0 189:0 205:0 '), +(178,'Woodpaw Den',1,357,2520,38,50,0,-4835.2,863.048,137.048,3.8637,''), +(179,'Revantusk Village',0,47,3317,38,50,5,-573.459,-4590.51,10.4122,3.48476,'407:0 '), +(180,'Thorium Point',0,51,1446,43,54,0,-6521.12,-1190.02,309.255,4.39589,'590:0 '), +(181,'Morshan Base Camp',1,17,1599,8,20,0,1035.13,-2113.34,122.947,5.1989,'182:0 '), +(182,'Silverwing Grove',1,331,3319,18,30,0,1462.48,-1858.93,123.55,1.74032,'163:0 167:0 181:0 '), +(183,'The Weeping Cave',0,28,198,48,56,0,2249.61,-2389.63,59.8017,5.27414,'244:0 255:0 256:0 '), +(184,'Valors Rest',1,1377,3077,53,60,0,-6404.33,-292.647,1.04401,3.06251,'190:0 191:0 '), +(185,'Yojamba Isle',0,33,3357,33,45,0,-11838,1268.18,1.74176,4.79629,'894:0 887:0 895:0 896:0 '), +(186,'Misty Reed Post',0,8,1978,33,45,4,-10854.8,-4093.43,21.7429,5.03063,'825:0 80:0 826:0 '), +(187,'Brackenwall Village',1,15,496,33,45,0,-3132.98,-2880.71,34.718,4.58324,'100:0 102:0 '), +(188,'Cenarion Hold',1,1377,3425,53,60,0,-6886.15,718.398,42.798,6.10305,'176:0 177:0 189:0 '), +(189,'Twilight Base Camp',1,1377,2739,53,60,0,-6996.15,1195.01,11.4959,1.16226,'134:0 176:0 177:0 188:0 202:0 204:0 '), +(190,'Staghelm Point',1,1377,3426,53,60,0,-6517.49,97.5554,126.126,1.85794,'184:0 191:0 '), +(191,'Twilights Run',1,1377,3446,53,60,0,-6310.14,136.591,15.4595,2.53339,'184:0 190:0 '), +(192,'Ortells Hideout',1,1377,2744,53,60,0,-7586.17,226.396,10.8663,1.24673,'205:0 '), +(193,'Bronzebeard Encampment',1,1377,3427,53,60,0,-8021.76,1105.84,3.54927,1.43333,'203:0 '), +(194,'Under Attack - Winterspring',1,618,2251,53,60,0,6519.57,-4117.1,661.535,4.32283,'143:0 '), +(195,'Under Attack - Azshara',1,16,1222,43,54,0,3366.79,-6226.81,-15.3194,1.50275,'47:0 141:0 '), +(196,'BLC1',0,4,4,43,54,0,-11202.2,-3034.46,6.232,1.13173,'839:0 841:0 845:0 852:0 854:0 '), +(198,'Under Attack - Tanaris Desert',1,440,440,38,50,0,-8277.12,-3484.09,17.7284,1.51601,''), +(199,'Under Attack - Eastern Plaguelands',0,139,2258,53,60,0,2492.32,-3803.41,177.692,4.09879,'248:0 '), +(200,'Ivars Patch',0,130,239,8,20,4,1233.05,1214.28,52.5845,3.07876,'201:0 298:0 299:0 303:0 '), +(201,'Valgans Field',0,130,227,8,20,4,908.754,1255.63,45.9684,0.575633,'299:0 302:0 303:0 200:0 '), +(202,'Sandworm! - A Silithyst beast has surfaced',1,1377,2742,53,60,0,-6548.72,1150.51,-1.23794,4.6252,'134:0 189:0 '), +(203,'Sandworm! - A Silithyst beast has surfaced',1,1377,1377,53,60,0,-7786.01,978.815,-2.90318,5.53981,'193:0 '), +(204,'Sandworm! - A Silithyst beast has surfaced',1,1377,2743,53,60,0,-7463.3,1411.7,3.70656,4.14577,'189:0 '), +(205,'Sandworm! - A Silithyst beast has surfaced',1,1377,2738,53,60,0,-7377.08,447.046,4.53578,2.59898,'176:0 177:0 192:0 '), +(211,'Eastwall Tower - Horde',0,139,2271,53,60,0,2550.77,-4783.57,109.501,5.47479,'85:0 235:0 237:0 250:0 '), +(215,'Plaguewood Tower - Horde, Progressing',0,139,4067,53,60,0,2991.34,-3045.27,119.143,2.65173,'84:0 241:0 242:0 '), +(220,'Northpass Tower - Horde, Contested',0,139,2275,53,60,0,3168.41,-4356.58,138.976,4.89792,'236:0 237:0 238:0 '), +(230,'Crown Guard Tower - Alliance',0,139,2263,53,60,0,1861.6,-3701.08,160.834,4.34226,'248:0 247:0 249:0 '), +(233,'Mudsprocket',1,15,4010,33,45,0,-4590.83,-3182.5,34.9064,2.13752,'105:0 '), +(234,'LightsHopeChapel',0,139,2268,53,60,1,2273.52,-5332.92,88.0978,1.44413,'85:0 252:0 '), +(235,'BlackwoodLake',0,139,2624,53,60,0,2464.31,-4192.32,86.7625,5.25334,'211:0 236:0 250:0 '), +(236,'EPL_hubN1',0,139,139,53,60,0,2895.84,-4274.37,91.0774,2.94428,'220:0 235:0 237:0 240:0 '), +(237,'Northdale',0,139,2272,53,60,0,2939.45,-4922.8,110.201,2.76758,'236:0 211:0 220:0 238:0 '), +(238,'ZulMasharEntrance',0,139,2273,53,60,0,3243.57,-4728.42,157.177,1.67982,'237:0 220:0 239:0 '), +(239,'MazraAlor',0,139,2274,53,60,0,3446.77,-4987,196.046,5.53613,'238:0 '), +(240,'PlaguewoodEast',0,139,2277,53,60,0,3031.09,-3786.79,119.967,4.37961,'236:0 241:0 242:0 '), +(241,'PlaguewoodSouth',0,139,2277,53,60,0,2764.18,-3442.19,97.1331,4.24216,'215:0 240:0 242:0 '), +(242,'PlaguewoodCenter',0,139,2277,53,60,0,3137.26,-3403.95,139.517,0.937594,'215:0 240:0 241:0 '), +(243,'TerrorweTunnelWest',0,139,2619,53,60,0,2724.41,-2453.2,66.836,5.42496,'244:0 84:0 '), +(244,'ThondorilRiverSouth',0,139,2619,53,60,0,2417.75,-2469.65,72.4504,0.00296164,'183:0 243:0 245:0 '), +(245,'EPL_hubSW',0,139,139,53,60,0,2076.2,-2853.69,86.3391,0.879086,'244:0 246:0 248:0 255:0 '), +(246,'EPL_hubSWRoad',0,139,139,53,60,0,1811.3,-3046.98,75.4985,1.09939,'245:0 247:0 255:0 '), +(247,'Undercroft',0,139,2261,53,60,0,1600.37,-3283.08,91.4858,0.567285,'246:0 230:0 '), +(248,'EPL_hubSW2',0,139,139,53,60,0,2175.83,-3487.76,123.024,5.90406,'199:0 230:0 245:0 '), +(249,'EPL_SRoad',0,139,139,53,60,0,1841.76,-4088.5,101.961,5.05584,'230:0 250:0 '), +(250,'CorinsCrossing',0,139,2264,53,60,0,2074.45,-4556.23,73.5772,0.745981,'211:0 235:0 249:0 251:0 252:0 '), +(251,'ScarletBaseCamp',0,139,2265,53,60,0,1656.05,-4826.35,87.7738,0.384695,'250:0 252:0 253:0 '), +(252,'PestilentScar',0,139,2622,53,60,0,2013.6,-4964.85,73.6116,5.58402,'234:0 250:0 251:0 253:0 '), +(253,'TyrshandEntrance',0,139,2266,53,60,0,1690.77,-5201.46,74.6226,4.83788,'251:0 252:0 254:0 '), +(254,'Tyrshand',0,139,2266,53,60,0,1609.91,-5528.27,111.168,4.2076,'253:0 '), +(255,'ThondorilRiverBridge',0,139,2619,53,60,0,1924.72,-2608.2,62.8109,4.6761,'183:0 245:0 246:0 256:0 '), +(256,'GahrronWithering',0,28,201,48,56,0,1768.07,-2280.14,59.7087,0.010807,'183:0 255:0 257:0 258:0 '), +(257,'WritingHaunt',0,28,202,48,56,0,1506.64,-1862.76,59.0986,5.271,'256:0 258:0 259:0 266:0 269:0 '), +(258,'WPL_hubC2',0,28,28,48,56,0,1760.12,-1779.45,64.8245,4.87437,'256:0 257:0 259:0 260:0 266:0 269:0 '), +(259,'DalsonTears',0,28,200,48,56,0,1864.12,-1558.81,59.2668,3.44495,'257:0 258:0 260:0 266:0 267:0 269:0 '), +(260,'WPL_hubN1',0,28,28,48,56,0,2122.47,-1665.81,64.0458,5.36013,'258:0 259:0 261:0 262:0 '), +(261,'NorthridgeLumberCamp',0,28,192,48,56,0,2421.88,-1647.26,103.541,5.77835,'260:0 '), +(262,'WPL_hubN2',0,28,28,48,56,0,2425.99,-1947.45,109.098,4.71217,'260:0 263:0 '), +(263,'HearthglenTower',0,28,28,48,56,0,2701.43,-1944.23,107.238,0.832291,'262:0 265:0 '), +(264,'Hearthglen',0,28,203,48,56,0,2924.12,-1426.31,150.782,1.09736,'265:0 '), +(265,'HearthglenEntrance',0,28,190,48,56,0,2782.62,-1612.54,129.551,0.826396,'264:0 263:0 '), +(266,'WPL_hubC1',0,28,28,48,56,0,1680.03,-1358.64,69.8578,5.02985,'268:0 257:0 258:0 259:0 267:0 269:0 270:0 274:0 '), +(267,'FelstoneField',0,28,199,48,56,0,1795.08,-1188.53,59.8914,5.54821,'268:0 259:0 266:0 269:0 273:0 274:0 '), +(268,'AndorhalW',0,28,193,48,56,0,1336.99,-1272.01,57.8614,2.26525,'266:0 267:0 269:0 270:0 272:0 '), +(269,'AndorhalNE',0,28,193,48,56,0,1540.11,-1606.19,65.1216,5.937,'257:0 258:0 259:0 266:0 267:0 268:0 270:0 '), +(270,'AndorhalEntranceSE',0,28,197,48,56,0,1294.98,-1678.78,62.5727,3.83801,'268:0 266:0 269:0 271:0 '), +(271,'SorrowHillCR',0,28,197,48,56,0,1162.78,-1758.3,60.6308,3.04279,'48:0 173:0 270:0 '), +(272,'AndorhalEntranceSW',0,28,28,48,56,0,1214.59,-1145.63,60.8962,5.48342,'268:0 173:0 '), +(273,'Bulwark',0,28,813,10,56,4,1718.17,-802.509,57.5466,1.68044,'267:0 274:0 275:0 276:0 '), +(274,'WPL_hubE',0,28,813,48,56,0,1663.85,-956.731,69.3084,0.651574,'266:0 267:0 273:0 '), +(275,'TG_hubSE',0,85,85,1,10,4,1806.63,-369.504,32.3876,6.02566,'273:0 276:0 277:0 '), +(276,'BalnirFarmstead',0,85,165,6,10,4,2029.54,-432.459,35.4011,5.30269,'273:0 275:0 277:0 '), +(277,'TG_hubC2',0,85,85,1,10,4,2029.86,-87.8105,35.5077,4.49765,'2:0 275:0 276:0 '), +(278,'BrillEntrance',0,85,159,1,10,4,2296.05,429.197,35.7333,5.74799,'2:0 279:0 285:0 297:0 '), +(279,'GarrensHauntGraves',0,85,85,4,10,4,2599.87,521.024,17.2769,5.97968,'2:0 278:0 280:0 281:0 '), +(280,'GarrensHaunt',0,85,164,6,10,4,2883.52,376.589,25.8553,5.87914,'279:0 281:0 284:0 '), +(281,'BrightwaterLake',0,85,85,1,10,4,2634.44,79.5244,31.089,6.13244,'2:0 279:0 280:0 282:0 284:0 '), +(282,'NorthCoastE',0,85,168,7,10,4,3032.54,-341.266,5.44748,2.52746,'281:0 283:0 '), +(283,'NorthCoastC',0,85,168,1,10,4,2935.15,41.7046,6.92148,4.82869,'282:0 284:0 '), +(284,'NorthCoastW',0,85,168,1,10,4,2984.21,388.32,7.96326,4.49688,'280:0 281:0 283:0 '), +(285,'ColdHearthManor',0,85,166,1,10,4,2107.98,617.879,35.0405,0.497239,'278:0 18:0 286:0 287:0 297:0 '), +(286,'TG_hubC1',0,85,85,1,10,0,2204.7,1063.28,28.6853,4.1572,'285:0 287:0 288:0 292:0 294:0 '), +(287,'CrusadersOutpost',0,85,85,1,10,0,1797.65,703.831,48.147,1.54771,'286:0 285:0 297:0 '), +(288,'TH_hubNE',0,85,85,1,10,4,2446.9,1082.07,58.5203,0.911539,'286:0 289:0 292:0 293:0 294:0 '), +(289,'AgamandMillsEntrance',0,85,157,4,10,4,2701.33,937.571,110.912,0.173269,'288:0 290:0 291:0 '), +(290,'AgamandMillsW',0,85,157,6,10,4,2889.98,1065.92,105.434,5.38438,'289:0 291:0 '), +(291,'AgamandMillsE',0,85,157,6,10,4,2973.71,619.932,93.8373,1.31013,'289:0 290:0 '), +(292,'SollidenFarmstead',0,85,156,1,10,4,2329.03,1407.79,33.3337,0.622907,'286:0 288:0 293:0 294:0 '), +(293,'CrusadersOutpost',0,85,85,1,10,4,2430.28,1585.02,37.0619,0.587566,'288:0 292:0 '), +(294,'UndeadStartExit',0,85,85,5,10,4,2197.51,1192.39,31.5497,5.11342,'288:0 286:0 292:0 295:0 '), +(295,'UndeadStartEnd',0,85,154,1,10,4,2061.91,1418.99,63.8379,5.30978,'294:0 83:0 4:0 296:0 '), +(296,'UndeadStartE',0,85,154,1,10,4,1805.3,1351.42,87.1354,0.232168,'295:0 4:0 '), +(297,'TH_exitS',0,85,85,7,12,4,1642.09,555.658,33.4922,2.88878,'278:0 285:0 287:0 298:0 '), +(298,'ShiningStrandN',0,130,927,8,14,4,1331.69,679.903,40.5373,1.02387,'297:0 200:0 '), +(299,'DeadField',0,130,240,8,20,4,1076.06,1545.35,28.6412,4.79771,'201:0 300:0 301:0 302:0 303:0 200:0 '), +(300,'SkitteringDark',0,130,226,8,20,4,1271.34,1974.49,17.8502,4.31862,'299:0 301:0 '), +(301,'NorthTidesHollow',0,130,305,8,20,4,833.25,1880.5,21.9868,5.06279,'299:0 300:0 '), +(302,'SPF_hubC1',0,130,130,8,20,4,867.729,1518.72,35.2045,0.160734,'201:0 299:0 303:0 304:0 '), +(303,'SPF_hubC2',0,130,130,8,20,4,934.532,1356.51,43.5579,3.0887,'201:0 299:0 302:0 200:0 305:0 '), +(304,'SPF_hubC3',0,130,130,8,20,4,736.764,1449.26,64.4284,0.457609,'302:0 305:0 306:0 '), +(305,'SPF_hubC4',0,130,130,8,20,4,587.903,1352.93,90.6192,0.359403,'303:0 304:0 306:0 17:0 313:0 '), +(306,'DecrepitFerry',0,130,237,13,20,4,664.305,1021.48,45.3265,5.10911,'304:0 305:0 307:0 '), +(307,'FenrisIsle',0,130,172,15,20,4,704.961,674.359,43.3624,1.57874,'306:0 308:0 311:0 '), +(308,'FenrisIsleSE',0,130,172,15,20,4,653.059,335.097,35.0481,1.33722,'307:0 309:0 '), +(309,'FenrisIsleE',0,130,232,16,20,4,866.03,78.8803,34.2361,2.23258,'308:0 310:0 '), +(310,'DawningIsles',0,130,232,17,22,4,1199.57,370.42,34.3257,3.13185,'309:0 '), +(311,'FenrisKeep',0,130,172,8,20,4,1013.6,734.689,59.2651,3.23986,'307:0 '), +(312,'DeepElemMineFork',0,130,213,8,20,4,271.231,1110.33,80.2136,5.99661,'313:0 314:0 317:0 318:0 '), +(313,'SPF_hubC5',0,130,130,8,20,4,385.877,1253.37,80.2878,0.320144,'305:0 312:0 315:0 '), +(314,'DeepElemMine',0,130,213,8,20,4,376.82,1082.54,106.396,3.03959,'312:0 '), +(315,'SPF_hubC6',0,130,130,8,20,4,194.263,1268.01,72.8331,6.23812,'313:0 316:0 317:0 318:0 '), +(316,'OlsensFarthing',0,130,229,8,20,4,171.155,1487.65,114.395,4.84404,'315:0 '), +(317,'SPF_DalaranCamp1',0,130,130,8,20,4,-50.4703,1331.43,60.9321,5.92005,'315:0 312:0 318:0 319:0 323:0 '), +(318,'SPF_hubS1',0,130,130,8,20,4,-132.172,1170.25,63.4384,0.508657,'315:0 317:0 312:0 319:0 323:0 326:0 '), +(319,'AmberhillEntrance',0,130,233,11,20,4,-131.822,896.74,65.831,1.54539,'317:0 318:0 320:0 321:0 323:0 325:0 '), +(320,'AmberhillHall',0,130,233,13,20,4,-141.972,812.61,63.737,1.09457,'319:0 '), +(321,'AmberhillFarms',0,130,233,15,20,4,88.1028,694.265,60.6053,2.4376,'322:0 319:0 '), +(322,'AmberhillMurlocCamp',0,130,130,15,20,4,434.265,696.251,33.7837,3.19747,'321:0 '), +(323,'SPF_hubS2',0,130,130,8,20,4,-330.828,1311.68,37.1012,1.70757,'317:0 318:0 319:0 324:0 326:0 327:0 '), +(324,'PyrewoodVillage',0,130,204,8,20,4,-383.772,1597.45,16.8392,4.90611,'323:0 '), +(325,'SPF_exitSE',0,130,130,15,20,4,-543.136,724.877,91.2236,0.975962,'319:0 326:0 357:0 '), +(326,'SPF_hubS3',0,130,130,8,20,4,-503.287,1113.31,76.93,0.514545,'318:0 323:0 325:0 '), +(327,'GreymaneWall',0,130,230,8,20,4,-744.895,1522.56,15.5245,5.84742,'323:0 '), +(328,'DandredsFold',0,36,1682,35,40,0,1235.47,-274.821,40.3856,4.48552,'329:0 '), +(329,'UplandsN',0,36,284,28,40,0,1085.27,-663.59,87.8456,1.24773,'328:0 330:0 '), +(330,'UplandsS',0,36,284,28,40,0,958.693,-748.597,114.797,4.04964,'329:0 331:0 335:0 '), +(331,'Strahnbard',0,36,280,28,40,0,683.909,-963.157,164.301,0.671641,'330:0 332:0 343:0 '), +(332,'AM_hubE1',0,36,36,28,40,0,615.654,-1043.7,168.11,0.331968,'331:0 333:0 381:0 '), +(333,'AM_hubE2',0,36,1684,36,42,0,640.065,-1460.93,76.3018,6.17925,'332:0 334:0 '), +(334,'ChillwindCampRoad',0,28,3197,48,56,0,933.473,-1479.87,62.8272,3.06319,'333:0 173:0 '), +(335,'SlaughterHollowNE',0,36,283,28,40,0,802.9,-628.854,149.733,3.01018,'330:0 336:0 337:0 '), +(336,'SlaughterHollowN',0,36,283,28,40,0,835.191,-445.445,134.228,4.48673,'335:0 '), +(337,'SlaughterHollowC',0,36,281,28,40,0,620.637,-522.586,179.928,5.73159,'335:0 338:0 343:0 '), +(338,'RuinsOfAlterac',0,36,281,28,40,0,631.058,-371.491,154.481,2.42898,'340:0 337:0 339:0 '), +(339,'RuinsOfAlteracSW',0,36,281,28,40,0,500.776,-173.116,151.449,5.19357,'340:0 338:0 341:0 '), +(340,'RuinsOfAlteracSE',0,36,281,28,40,0,397.344,-248.661,161.727,5.94755,'338:0 339:0 '), +(341,'RuinsOfAlteracE',0,36,281,28,40,0,573.088,-14.3215,142.366,3.66008,'339:0 342:0 '), +(342,'RuinsOfAlteracSSW',0,36,1683,28,40,0,252.323,-82.4394,141.544,0.263228,'341:0 344:0 '), +(343,'RuinsOfAlteracE',0,36,1357,28,40,0,493.629,-621.553,172.877,0.602885,'331:0 337:0 344:0 345:0 380:0 '), +(344,'RuinsOfAlteracS',0,36,1683,28,40,0,141.788,-316.452,150.648,1.12125,'342:0 343:0 346:0 '), +(345,'CorrahnsDagger',0,36,1679,28,40,0,-45.3608,-581.074,153.724,5.82187,'343:0 346:0 380:0 '), +(346,'CorrahnsDaggerFork',0,36,1679,28,40,0,57.1052,-405.389,132.108,2.70582,'344:0 345:0 347:0 385:0 '), +(347,'Headland',0,36,1680,28,40,0,-163.833,-293.019,151.411,0.123425,'346:0 348:0 '), +(348,'GavinsNaze',0,36,1677,28,40,0,-63.0062,-197.056,131.24,2.72704,'347:0 349:0 350:0 355:0 '), +(349,'GavinsNazeTop',0,36,1677,28,40,0,-125.184,-59.9939,147.694,5.63693,'348:0 350:0 '), +(350,'GavinsNazeW',0,36,279,28,40,0,68.1015,-53.44,99.6287,3.99152,'348:0 349:0 351:0 352:0 353:0 '), +(351,'DalaranCraterN',0,36,279,28,40,4,466.751,167.322,41.9053,3.38088,'350:0 352:0 '), +(352,'DalaranCraterS',0,36,279,28,40,4,90.2654,237.935,43.2902,5.2894,'351:0 350:0 353:0 354:0 '), +(353,'LordamereInternmentCamp',0,36,278,28,40,4,-90.5356,218.366,53.2755,6.11997,'352:0 350:0 354:0 '), +(354,'HillsbradFieldsC',0,267,286,18,30,4,-499.681,78.0454,56.6165,0.278575,'352:0 353:0 356:0 355:0 '), +(355,'HillsbradFieldsE',0,267,286,18,30,0,-437.039,-135.243,56.2029,2.13409,'354:0 356:0 348:0 385:0 '), +(356,'HillsbradFieldsSRoad',0,267,267,18,30,0,-639.845,-98.2014,47.262,0.172558,'358:0 360:0 354:0 357:0 361:0 355:0 385:0 20:0 '), +(357,'SouthPointTower',0,267,285,18,30,0,-637.717,273.387,63.926,4.49972,'356:0 325:0 358:0 359:0 '), +(358,'WesternStrandW',0,267,295,18,30,0,-979.029,273.179,7.90667,5.62479,'357:0 356:0 359:0 '), +(359,'WesternStrandSW',0,267,295,18,30,0,-1128.84,276.704,0.0000662804,5.89576,'357:0 358:0 360:0 '), +(360,'WesternStrandS',0,267,295,18,30,0,-1156.55,7.61478,0.00000548363,6.16081,'359:0 356:0 361:0 '), +(361,'WesternStrandS',0,267,295,18,30,0,-1102.38,-114.03,0.0000309944,0.148599,'362:0 360:0 356:0 '), +(362,'WesternStrandE',0,267,271,18,30,0,-962.262,-506.545,2.13023,1.90396,'361:0 363:0 20:0 '), +(363,'SouthshoreS',0,267,271,18,30,0,-974.276,-592.998,0.229758,0.372044,'362:0 364:0 20:0 '), +(364,'EasternStrandW',0,267,294,18,30,0,-1012.85,-790.213,9.01406,5.30042,'363:0 365:0 368:0 376:0 '), +(365,'EasternStrandC',0,267,294,18,30,0,-1183.14,-847.579,1.43171,6.18988,'364:0 366:0 '), +(366,'EasternStrandS',0,267,294,18,30,0,-1311.64,-1052.47,18.2271,0.929683,'365:0 367:0 368:0 '), +(367,'DunGarok',0,267,290,18,30,4,-1256.01,-1190.37,38.9786,2.03514,'366:0 368:0 376:0 '), +(368,'DunGarokRoad',0,267,267,18,30,0,-1049.49,-1239.84,53.3175,1.75045,'364:0 366:0 367:0 369:0 373:0 '), +(369,'HillsbradFoothillsExitSE',0,267,267,18,30,0,-784.955,-1512.45,56.6724,1.97822,'370:0 368:0 373:0 410:0 '), +(370,'HillsbradFoothillsExitE',0,267,275,18,30,0,-594.279,-1721.03,62.3187,2.25507,'369:0 371:0 411:0 '), +(371,'DurnholdeNE',0,267,275,18,30,0,-383.173,-1720.01,90.0634,3.41784,'372:0 370:0 '), +(372,'DurnholdeNNE',0,267,275,18,30,0,-316.76,-1609.34,86.0559,5.49914,'371:0 377:0 386:0 '), +(373,'DurnholdeS',0,267,267,18,30,0,-669.891,-1326.66,66.6887,5.65621,'376:0 374:0 368:0 369:0 383:0 '), +(374,'DurnholdeInside',0,267,275,18,30,0,-518.661,-1435.16,64.889,2.5637,'373:0 375:0 '), +(375,'DurnholdeInsideDeep',0,267,275,18,30,0,-512.473,-1541.62,67.1947,1.69387,'374:0 '), +(376,'NethanderStead',0,267,289,18,30,0,-915.831,-926.49,31.1754,1.19711,'364:0 367:0 373:0 383:0 20:0 '), +(377,'DurnholdeN',0,267,275,18,30,0,-298.033,-1310.71,76.9034,4.69017,'372:0 378:0 383:0 '), +(378,'DurnholdeNN',0,267,267,18,30,0,-37.1467,-1296.59,83.7166,3.13705,'377:0 21:0 '), +(379,'TarrenMillN',0,267,267,18,30,4,188.047,-974.547,75.5356,0.0209713,'21:0 381:0 '), +(380,'GallowsCorner',0,36,1357,28,40,0,324.254,-615.115,145.246,3.03887,'343:0 345:0 21:0 382:0 '), +(381,'SoferasCorner',0,36,36,28,40,0,461.934,-926.633,129.374,2.72668,'379:0 332:0 382:0 '), +(382,'SoferasNaze',0,36,1678,28,40,0,248.314,-839.928,146.333,0.818164,'381:0 380:0 '), +(383,'HFH_bridge',0,267,267,18,30,0,-487.607,-967.55,34.4918,4.54488,'377:0 376:0 373:0 384:0 '), +(384,'DarrowHillE',0,267,267,18,30,0,-323.998,-672.57,54.6417,3.9048,'21:0 383:0 385:0 20:0 '), +(385,'DarrowHillW',0,267,1056,18,30,0,-332.483,-447.42,58.4647,0.297842,'346:0 355:0 356:0 384:0 20:0 '), +(386,'HinterlandsEntrance',0,47,47,38,50,0,-77.4076,-1845.63,143.116,2.46164,'372:0 55:0 '), +(387,'ZunWatha',0,47,352,38,50,0,-11.9998,-2493.37,119.659,1.99629,'55:0 82:0 388:0 391:0 '), +(388,'QuelDanilS',0,47,47,38,50,0,65.3995,-2664.2,111.823,5.20269,'55:0 82:0 387:0 389:0 390:0 391:0 392:0 '), +(389,'QuelDanil',0,47,350,38,50,4,210.196,-2789.46,122.156,1.50935,'388:0 390:0 392:0 '), +(390,'HL_hubW',0,47,47,38,50,0,132.208,-2876.17,116.583,1.69588,'388:0 389:0 392:0 393:0 397:0 398:0 '), +(391,'BogensLedge',0,47,1887,38,50,0,-198.398,-2585.67,120.378,6.18051,'387:0 388:0 393:0 '), +(392,'HiriWatha',0,47,1885,38,50,0,-35.4616,-2815.33,122.143,0.435323,'389:0 390:0 388:0 393:0 '), +(393,'ShadraAlorEntrance',0,47,47,38,50,0,-160.179,-2955.33,115.773,1.96371,'391:0 392:0 390:0 395:0 394:0 396:0 398:0 400:0 '), +(394,'ShadraAlorE',0,47,353,38,50,0,-366.126,-2955.41,89.39,6.27554,'393:0 395:0 396:0 '), +(395,'ShadraAlorS',0,47,353,38,50,0,-455.461,-2839.4,105.834,6.08704,'393:0 394:0 396:0 '), +(396,'ShadraAlorW',0,47,353,38,50,0,-296.621,-2833,96.7074,5.15045,'393:0 395:0 394:0 '), +(397,'AgolWatha',0,47,1884,38,50,0,397.177,-3352.2,123.451,2.24644,'390:0 398:0 399:0 '), +(398,'CreepingRun',0,47,1886,38,50,0,116.576,-3466.05,107.658,1.12922,'393:0 397:0 390:0 399:0 400:0 401:0 '), +(399,'SkulkRock',0,47,351,38,50,0,363.014,-3796.73,171.76,1.54745,'397:0 398:0 401:0 404:0 '), +(400,'AltarOfZul',0,47,355,38,50,0,-147.861,-3319.03,121.957,3.93113,'393:0 398:0 '), +(401,'HL_hubC',0,47,47,38,50,0,109.926,-3922.75,136.697,0.948588,'398:0 399:0 402:0 403:0 404:0 '), +(402,'JinthaAlor',0,47,354,38,50,0,-217.196,-4159.98,118.665,0.453769,'401:0 403:0 '), +(403,'OverlookCliffsS',0,47,307,38,50,0,-150.233,-4250.21,120.905,4.17653,'401:0 402:0 405:0 '), +(404,'SeradaneBridge',0,47,351,38,50,0,472.513,-3907.18,113.731,5.88677,'399:0 401:0 409:0 '), +(405,'OverlookCliffsRampTop',0,47,307,38,50,0,-243.665,-4377.48,105.997,0.98978,'403:0 406:0 '), +(406,'OverlookCliffsRampBottom',0,47,307,38,50,0,-3.15739,-4629.26,13.899,2.27391,'405:0 407:0 408:0 '), +(407,'RevantuskOutside',0,47,307,38,50,0,-355.789,-4475.87,11.3783,0.924997,'406:0 179:0 '), +(408,'OverlookCliffsEnd',0,47,307,38,50,0,133.371,-4745.73,2.05405,2.27392,'406:0 '), +(409,'Seradane',0,47,356,60,80,0,755.958,-4011.61,92.8808,5.95432,'404:0 '), +(410,'ArathiExitNW',0,45,334,28,40,0,-889.426,-1677.32,57.6111,1.06053,'369:0 411:0 412:0 414:0 '), +(411,'ArathiExitN',0,45,45,28,40,0,-684.458,-1831.3,53.3946,0.936824,'370:0 410:0 412:0 413:0 '), +(412,'ArathiCW',0,45,45,28,40,0,-863.176,-1785.9,39.6302,5.66886,'410:0 411:0 413:0 414:0 '), +(413,'NorthfoldManor',0,45,313,28,40,0,-822.895,-2039.83,34.4558,1.25884,'411:0 412:0 415:0 '), +(414,'Arathi_hubW',0,45,45,28,40,0,-1190.48,-1731.63,56.3674,6.26181,'410:0 412:0 415:0 420:0 '), +(415,'NorthfoldManor',0,45,313,28,40,0,-933.512,-2120.29,56.5869,2.05011,'78:0 413:0 414:0 416:0 '), +(416,'RefugePointeExitN',0,45,315,28,40,0,-1158.07,-2706.9,52.0215,2.18796,'78:0 22:0 415:0 417:0 418:0 419:0 435:0 '), +(417,'DabyrieFarmstead',0,45,45,28,40,0,-1091.19,-2856.55,42.4006,1.78073,'416:0 418:0 435:0 436:0 '), +(418,'ArathiCO',0,45,336,28,40,0,-1352.09,-2738.95,59.0948,6.08275,'416:0 417:0 419:0 431:0 432:0 435:0 '), +(419,'RefugePointeExitS',0,45,45,28,40,0,-1466.43,-2424.6,57.8277,5.98262,'22:0 78:0 416:0 418:0 420:0 427:0 429:0 431:0 '), +(420,'StromgardeRoad',0,45,45,28,40,0,-1323.01,-1833.39,63.6564,0.712589,'421:0 414:0 419:0 427:0 '), +(421,'StromgardeInside',0,45,324,28,40,0,-1576.29,-1800.48,67.6512,3.46934,'422:0 423:0 424:0 420:0 '), +(422,'StromgardeKeep',0,45,324,28,40,0,-1660.06,-1803.63,83.0724,6.27321,'421:0 426:0 '), +(423,'StromgardeE',0,45,324,28,40,0,-1602.44,-1922.26,67.2707,1.50115,'421:0 '), +(424,'StromgardeW1',0,45,324,28,40,0,-1596.2,-1745.56,67.3627,5.09826,'421:0 425:0 '), +(425,'StromgardeW2',0,45,324,28,40,0,-1720.34,-1736.69,52.4064,6.26851,'424:0 81:0 '), +(426,'StromgardeKeepSide',0,45,324,28,40,0,-1681.32,-1933.25,80.6272,1.35821,'422:0 '), +(427,'ArathiCI',0,45,335,28,40,0,-1517.62,-2100.44,22.3405,4.92548,'419:0 420:0 428:0 '), +(428,'ThandolRoadN',0,45,45,28,40,0,-1995.23,-2466.74,78.7992,0.837489,'427:0 429:0 441:0 '), +(429,'ArathiRoadSmid',0,45,45,28,40,0,-1763.42,-2422.8,59.5627,0.9015,'419:0 428:0 431:0 430:0 '), +(430,'BoulderfistHall',0,45,316,28,40,0,-1941.13,-2794.22,85.7724,0.38314,'429:0 431:0 432:0 '), +(431,'Arathi_hubE1',0,45,45,28,40,0,-1570.23,-2675.51,35.6533,6.03014,'419:0 418:0 429:0 432:0 435:0 430:0 '), +(432,'Arathi_hubE2',0,45,317,28,40,0,-1705.19,-3021.39,31.689,0.310091,'433:0 418:0 431:0 434:0 430:0 '), +(433,'WitherbarkVillage',0,45,317,28,40,0,-1867.12,-3365.42,56.1179,1.07782,'432:0 '), +(434,'GoshekFarm',0,45,314,28,40,0,-1526.14,-3075.57,14.1487,2.82925,'432:0 435:0 '), +(435,'Arathi_hubNE1',0,45,45,28,40,0,-1297.32,-3141.76,34.9289,1.25489,'416:0 417:0 418:0 431:0 434:0 436:0 438:0 '), +(436,'Arathi_hubNE2',0,45,45,28,40,0,-1005.91,-3313.55,55.2766,2.32737,'435:0 417:0 437:0 438:0 '), +(437,'ArathiCE',0,45,333,28,40,0,-839.717,-3280.93,78.5616,3.98495,'436:0 438:0 '), +(438,'HammerfallEntrance',0,45,321,28,40,0,-1164.53,-3558.81,50.1497,1.47167,'435:0 436:0 437:0 23:0 439:0 '), +(439,'DrywhiskerGorgeOutside',0,45,318,28,40,0,-1086,-3696.31,81.3913,2.10981,'438:0 440:0 '), +(440,'DrywhiskerGorgeEntrance',0,45,318,28,40,0,-1083.95,-3820.83,128.504,1.71515,'439:0 44:0 '), +(441,'ArathiRoadSbottom',0,45,880,28,40,0,-2248.64,-2487.38,80.1236,4.70713,'428:0 442:0 443:0 '), +(442,'ThandolSpanCamp',0,45,45,28,40,0,-2249.4,-2633.38,78.816,1.44851,'441:0 '), +(443,'ThandolSpanS',0,11,881,18,30,0,-2478.16,-2506.16,78.5672,0.110205,'441:0 444:0 '), +(444,'DunModrCamp',0,11,881,18,30,0,-2609.79,-2494.69,80.9667,1.57693,'443:0 6:0 445:0 '), +(445,'Wetlands_hubN1',0,11,11,18,30,0,-2937.95,-2470.57,26.6988,6.13028,'444:0 446:0 447:0 461:0 462:0 '), +(446,'DaggerforgeHill',0,11,1016,18,30,0,-2859.63,-2907.96,33.1564,1.54551,'445:0 448:0 449:0 '), +(447,'WL_hubC1',0,11,11,18,30,0,-3206.06,-2452.65,10.0327,5.76822,'445:0 448:0 455:0 461:0 462:0 '), +(448,'Greenwarden',0,11,1025,18,30,0,-3254.89,-2726.4,9.41813,1.21683,'446:0 447:0 449:0 455:0 458:0 '), +(449,'WL_hubE1',0,11,11,18,30,0,-3421.46,-3088.4,22.5006,4.5862,'446:0 448:0 450:0 451:0 455:0 456:0 '), +(450,'RaptorRidge',0,11,1017,23,30,0,-3132.51,-3240.98,63.5747,2.76015,'449:0 451:0 '), +(451,'DragonmawGates',0,11,1038,61,70,0,-3452,-3659.86,58.5533,1.08333,'449:0 450:0 452:0 '), +(452,'DragonmawGatesI1',0,11,1038,61,70,0,-3594.66,-4056.85,113.625,1.23963,'451:0 453:0 '), +(453,'DragonmawGatesI2',0,11,1038,61,70,0,-3956.61,-4019.52,170.857,6.00896,'452:0 454:0 '), +(454,'DragonmawGatesI3',0,11,1037,61,70,0,-4145.01,-3662.88,204.651,5.17056,'92:0 453:0 '), +(455,'WL_hubS1',0,11,1020,18,30,0,-3777.35,-2817.99,12.647,0.508038,'447:0 448:0 449:0 456:0 457:0 '), +(456,'MosshideFen',0,11,1020,18,30,0,-3913.48,-3043.47,11.7092,0.398469,'449:0 455:0 457:0 '), +(457,'ThelgenRock',0,11,1021,18,30,0,-3921.58,-2647.27,36.3203,4.94005,'455:0 456:0 479:0 '), +(458,'AngerfangEnampmentE',0,11,1036,18,30,0,-3503.17,-2440.43,48.2306,5.48001,'448:0 459:0 '), +(459,'AngerfangEnampmentW',0,11,1036,18,30,0,-3354.34,-2190.82,45.3521,1.88565,'458:0 460:0 461:0 '), +(460,'AngerfangEnampmentTop',0,11,1036,18,30,0,-3458.4,-2008.76,119.804,5.31784,'459:0 '), +(461,'WL_hubW1',0,11,11,18,30,0,-3186.98,-2117.49,15.8054,4.81126,'445:0 447:0 459:0 462:0 463:0 467:0 '), +(462,'IronbeardsTomb',0,11,309,18,30,0,-2861.94,-2217.72,29.2628,4.06121,'445:0 447:0 461:0 463:0 464:0 '), +(463,'MosshideWest',0,11,11,18,30,0,-2916.55,-1848.65,10.201,4.36986,'461:0 462:0 464:0 465:0 466:0 467:0 '), +(464,'BaradinBayN',0,11,1023,18,30,0,-2598.33,-1745.42,10.159,4.05766,'462:0 463:0 465:0 466:0 '), +(465,'BaradinBayNW',0,11,298,18,30,0,-2723.52,-1348.51,9.75282,4.1578,'464:0 463:0 466:0 474:0 '), +(466,'BaradinBayW',0,11,1022,18,30,0,-2951.24,-1112.1,9.14625,2.36904,'464:0 463:0 465:0 474:0 475:0 476:0 '), +(467,'WhelgarsOutside',0,11,1024,18,30,0,-3208.01,-1693.31,8.6748,4.04314,'461:0 463:0 468:0 473:0 474:0 '), +(468,'WhelgarsEntrance',0,11,118,18,30,0,-3328.04,-1856.79,25.9266,3.14189,'467:0 469:0 470:0 '), +(469,'WhelgarsBottom',0,11,118,18,30,0,-3540.59,-1803.75,24.3572,5.93869,'468:0 '), +(470,'WhelgarsRamp',0,11,118,18,30,0,-3370.49,-1931.82,63.5201,0.805329,'468:0 471:0 '), +(471,'WhelgarsRampTop',0,11,118,18,30,0,-3471.16,-1925.52,113.829,3.80162,'470:0 472:0 '), +(472,'WhelgarsCave',0,11,118,18,30,0,-3569.75,-1976.96,117.678,0.449137,'471:0 '), +(473,'BlueChannelMarsh',0,11,1018,18,30,0,-3582.96,-1324.33,9.39017,5.56012,'467:0 474:0 476:0 '), +(474,'BluegillMarsh',0,11,1022,18,30,0,-3135.95,-1301.23,7.27194,1.32132,'465:0 466:0 467:0 473:0 475:0 476:0 '), +(475,'BaradinBayS',0,11,298,18,30,0,-3202.27,-925.946,8.88303,5.67636,'466:0 474:0 476:0 '), +(476,'MenethilHarborRoad',0,11,1022,18,30,2,-3333.27,-1053.79,8.28482,2.51317,'11:0 466:0 473:0 474:0 475:0 '), +(477,'MenethilBayS',0,11,298,18,30,2,-3909.27,-638.054,4.91793,5.35238,'11:0 '), +(478,'MenethilBayN',0,11,298,18,30,2,-3732.25,-581.622,4.65228,4.77512,'11:0 '), +(479,'DunAlgazBottom',0,11,836,18,30,0,-4086.88,-2624.69,43.3702,1.60133,'457:0 480:0 '), +(480,'DunAlgazMid1',0,11,836,18,30,0,-4092.37,-2403.53,100.01,4.74881,'479:0 481:0 '), +(481,'DunAlgazMid2',0,11,836,18,30,0,-3994.82,-2377.44,120.415,3.60017,'480:0 482:0 '), +(482,'DunAlgazMid3',0,11,836,18,30,0,-4070,-2464.84,155.161,3.20551,'481:0 483:0 '), +(483,'DunAlgazMid4',0,11,836,18,30,0,-4418.61,-2470.44,212.203,0.00303268,'482:0 5:0 484:0 '), +(484,'DunAlgazMid5',0,11,836,18,30,0,-4453.35,-2691.02,268.159,1.48548,'483:0 485:0 '), +(485,'DunAlgazTop',0,38,837,8,20,0,-4741.84,-2699.03,325.269,6.26389,'484:0 91:0 487:0 488:0 '), +(486,'NorthGatePassBottom',0,38,838,8,20,2,-4797.55,-2537.41,354.11,2.07534,'91:0 524:0 '), +(487,'SilverStreamMine',0,38,149,8,20,2,-4801.44,-2968.22,321.735,1.63004,'91:0 485:0 488:0 '), +(488,'StonewroughDamW',0,38,146,8,20,2,-4676.63,-3184.73,310.249,2.0836,'487:0 485:0 489:0 '), +(489,'StonewroughDamC',0,38,146,8,20,2,-4761.78,-3306.52,310.258,4.81285,'488:0 490:0 '), +(490,'StonewroughDamE',0,38,146,8,20,2,-4680.9,-3464.1,310.208,3.57506,'489:0 491:0 '), +(491,'TheLochNE',0,38,38,8,20,2,-4859.79,-3631.09,306.481,2.35809,'490:0 492:0 495:0 496:0 497:0 '), +(492,'MogroshStronghold',0,38,143,8,20,2,-4884.91,-3956.61,298.791,3.26837,'491:0 493:0 494:0 495:0 '), +(493,'MogroshStrongholdTop',0,38,143,8,20,2,-4853.52,-4046.55,315.354,1.54756,'492:0 '), +(494,'LochModanE',0,38,38,8,20,2,-5200.93,-4080.99,324.555,1.53421,'492:0 495:0 512:0 '), +(495,'LochModanC2',0,38,38,8,20,2,-5358.15,-3755.11,304.073,5.92655,'491:0 492:0 494:0 496:0 506:0 512:0 '), +(496,'LochIsleE',0,38,38,8,20,2,-4983.21,-3478.31,305.5,4.76612,'491:0 495:0 497:0 498:0 '), +(497,'LochIsleW',0,38,38,8,20,2,-4869.1,-3302.75,307.516,3.90807,'491:0 496:0 498:0 '), +(498,'LochIsleS',0,38,38,8,20,2,-5031.68,-3316.94,298.701,5.51616,'497:0 496:0 499:0 '), +(499,'LochC1',0,38,556,8,20,2,-5138.3,-3125.08,302.046,2.10361,'498:0 502:0 '), +(500,'LochW1',0,38,38,8,20,2,-5067.56,-2665.82,323.188,6.27801,'91:0 501:0 502:0 '), +(501,'LochW2',0,38,38,8,20,2,-5383.73,-2741.02,362.182,0.255963,'500:0 502:0 503:0 '), +(502,'LochW3',0,38,38,8,20,0,-5215.01,-2861.61,336.947,0.715418,'91:0 499:0 500:0 501:0 503:0 '), +(503,'ThelsamarEntrance',0,38,38,8,20,0,-5405.86,-2884.97,342.8,0.491581,'501:0 502:0 19:0 504:0 515:0 '), +(504,'LochC3',0,38,144,8,20,0,-5441.5,-3109.22,349.359,0.721317,'503:0 19:0 505:0 '), +(505,'LochS',0,38,38,8,20,0,-5860.82,-3292.84,292.88,5.93047,'504:0 506:0 621:0 '), +(506,'IronbandsExcavationOutside',0,38,142,8,20,2,-5617.55,-3680.57,313.913,2.11697,'495:0 505:0 507:0 509:0 '), +(507,'IronbandsExcavationSW',0,38,142,8,20,2,-5787.94,-3777.91,328.169,4.57725,'506:0 508:0 510:0 '), +(508,'IronbandsExcavationInside1',0,38,142,8,20,2,-5722.82,-3946.06,324.917,2.02667,'507:0 509:0 '), +(509,'IronbandsExcavationInside2',0,38,142,8,20,2,-5578.49,-3926.28,327.498,1.64379,'506:0 508:0 '), +(510,'LochCornerSE',0,38,147,8,20,2,-5843.55,-4145.23,387.605,1.3532,'507:0 511:0 512:0 '), +(511,'FastriderLodgeEntrance',0,38,147,8,20,2,-5555.41,-4251.75,380.644,2.87687,'510:0 512:0 513:0 '), +(512,'LochModanSE',0,38,147,8,20,2,-5553.71,-4104.44,372.683,1.01823,'494:0 495:0 510:0 511:0 '), +(513,'FarstriderLodge',0,38,147,8,20,2,-5621.61,-4334.97,403.763,0.951478,'511:0 514:0 '), +(514,'FarstriderLodgeInside',0,38,147,8,20,3,-5674.26,-4247.48,407.006,5.29276,'513:0 '), +(515,'LochModanSW1',0,38,38,8,20,2,-5673.4,-2775.82,363.022,5.89359,'503:0 516:0 519:0 '), +(516,'StonesplinterValley1',0,38,923,8,20,2,-5878.01,-2909.13,366.788,0.558766,'515:0 517:0 518:0 '), +(517,'StonesplinterValley2',0,38,923,8,20,2,-6057.08,-3024.86,403.365,0.541086,'516:0 '), +(518,'StonesplinterValley3',0,38,923,8,20,2,-6077.12,-2770.04,413.566,5.71294,'516:0 '), +(519,'LochModanSW2',0,38,924,8,20,2,-5804.6,-2605.8,316.201,6.15474,'515:0 520:0 521:0 '), +(520,'LochModanSW3',0,38,924,8,20,2,-6029.33,-2496.11,310.016,2.1924,'519:0 561:0 '), +(521,'SouthGatePassBottom',0,38,839,8,20,2,-5690.63,-2589.07,346.087,0.415367,'519:0 522:0 '), +(522,'SouthGateOutpost',0,1,806,1,10,0,-5520.09,-2399.78,400.417,2.27487,'521:0 523:0 '), +(523,'SouthGatePassTop',0,1,805,1,10,2,-5636.7,-2242.95,424.761,5.34381,'522:0 527:0 69:0 '), +(524,'NorthGatePassTop1',0,1,808,1,10,2,-4911.88,-2330.69,408.794,5.22601,'486:0 525:0 '), +(525,'NorthGatePassBottom2',0,1,808,1,10,2,-5204.26,-2288.4,400.984,2.97781,'524:0 526:0 '), +(526,'NorthGatePassTop2',0,1,807,1,10,2,-5294.53,-2180,423.003,2.8011,'525:0 527:0 '), +(527,'DunMoroghPassFork',0,1,1,1,10,2,-5515.28,-2008.17,399.376,4.60752,'526:0 523:0 528:0 '), +(528,'DunMoroghE1',0,1,1,1,10,2,-5509.11,-1798.18,397.338,5.99176,'527:0 69:0 529:0 '), +(529,'GolBolarFork',0,1,1,1,10,2,-5611.84,-1487.57,399.057,5.05517,'528:0 530:0 533:0 '), +(530,'GolBolarCamp',0,1,134,1,10,2,-5711.36,-1562.28,383.568,2.84075,'529:0 531:0 '), +(531,'GolBolarQuarry1',0,1,134,1,10,2,-5849.25,-1514.03,358.812,5.96152,'530:0 532:0 '), +(532,'GolBolarQuarry2',0,1,134,1,10,2,-5737.17,-1683.52,362.196,6.04595,'531:0 '), +(533,'TundridHills',0,1,804,1,10,2,-5651.13,-1033.73,410.816,4.75985,'529:0 534:0 536:0 556:0 '), +(534,'Vagash1',0,1,1,1,10,2,-5422.73,-1195.14,450.063,2.06791,'533:0 535:0 '), +(535,'Vagash2',0,1,803,1,10,2,-5417.7,-1264.17,446.78,2.22146,'534:0 '), +(536,'DunMoroghS1',0,1,1,1,10,2,-5892.71,-628.276,400.121,5.26175,'533:0 537:0 '), +(537,'DwarfGnomeStartExit1',0,1,1,1,10,2,-5975.18,-476.82,406.774,5.36188,'536:0 9:0 538:0 '), +(538,'DwarfGnomeStartExit2',0,1,1,1,10,2,-6013.25,-224.693,412.196,4.79638,'537:0 539:0 '), +(539,'DwarfGnomeStartExit3',0,1,800,1,10,2,-5890.34,69.4737,372.157,4.07706,'538:0 540:0 545:0 546:0 '), +(540,'ColdridgePass1',0,1,800,1,10,2,-6066.08,42.2314,408.342,0.120498,'539:0 541:0 '), +(541,'ColdridgePass2',0,1,800,4,10,2,-6231.65,125.6,430.753,5.63871,'540:0 542:0 '), +(542,'DwarfGnomeStart1',0,1,132,1,10,3,-6261.08,369.552,383.537,4.94484,'541:0 1:0 543:0 544:0 '), +(543,'DwarfGnomeStart2',0,1,132,1,10,2,-6482.65,496.244,386.328,5.80485,'542:0 544:0 '), +(544,'DwarfGnomeStart3',0,1,132,1,10,2,-6269.69,741.137,386.893,4.77403,'542:0 543:0 '), +(545,'GrizzledDen',0,1,136,1,10,2,-5690.95,-281.229,364.314,5.54297,'539:0 9:0 '), +(546,'DunMoroghSW1',0,1,1,1,10,2,-5756.39,127.948,368.817,4.07511,'539:0 547:0 548:0 '), +(547,'OldIcebeard',0,1,801,1,10,2,-5602.44,-28.5472,416.22,2.31387,'546:0 '), +(548,'DunMoroghW1',0,1,135,1,10,2,-5661.62,365.277,393.274,4.27146,'546:0 549:0 550:0 551:0 '), +(549,'FrostmaneHold',0,1,135,1,10,2,-5549.4,568.067,394.761,1.30462,'548:0 551:0 '), +(550,'GnomereganS',0,1,133,1,10,2,-5183.81,585.068,404.291,4.35,'548:0 551:0 '), +(551,'IceflowLake',0,1,211,1,10,2,-5250.86,115.774,394.041,2.85773,'548:0 549:0 550:0 552:0 555:0 '), +(552,'ShimmerRidge1',0,1,802,1,10,2,-5266.4,-149.752,437.903,6.01894,'551:0 553:0 '), +(553,'ShimmerRidge2',0,1,802,1,10,2,-5088.11,-166.104,442.035,0.727311,'552:0 554:0 555:0 '), +(554,'ShimmerRidge3',0,1,802,1,10,2,-5043,-267.85,441.4,2.19994,'553:0 '), +(555,'ShimmerRidge4',0,1,802,1,10,2,-5021.64,-132.237,411.899,3.77858,'553:0 551:0 '), +(556,'DunmoroghC1',0,1,1,1,10,2,-5391.34,-928.482,393.467,4.06525,'533:0 557:0 '), +(557,'IronforgeRamp',0,1,1,1,10,2,-5259.37,-493.579,386.436,3.14828,'556:0 9:0 558:0 '), +(558,'IronforgeRampMid',0,1,809,1,10,2,-5197.66,-730.697,445.722,5.89324,'557:0 559:0 '), +(559,'IronforgeRampTop',0,1,809,1,10,2,-5060.46,-745.984,480.27,3.47972,'558:0 8:0 '), +(560,'IronforgeBank',0,1537,1537,1,60,3,-4909.88,-969.761,501.463,5.46481,'8:0 '), +(561,'SGgate',0,51,1959,43,54,0,-6413.58,-2007.56,244.634,2.74852,'520:0 562:0 563:0 '), +(562,'DustfireValleyMid',0,51,1959,43,54,0,-6590.48,-1884.27,245.713,1.74321,'561:0 580:0 585:0 '), +(563,'SGexitE',0,51,51,43,54,0,-6906.38,-1818.59,241.661,5.89992,'561:0 564:0 566:0 593:0 '), +(564,'GrimesiltDigSite',0,51,247,43,54,0,-7019.65,-1698.57,241.667,5.46992,'563:0 565:0 566:0 567:0 568:0 '), +(565,'TannerCamp',0,51,1958,43,54,0,-7225.75,-1752.49,244.286,0.924438,'564:0 566:0 567:0 568:0 '), +(566,'SGtower4',0,51,1444,43,54,0,-6980,-1505.92,242.742,3.93841,'563:0 564:0 565:0 567:0 568:0 '), +(567,'SGtower3',0,51,1444,43,54,0,-7036.7,-1320.2,244.272,4.7631,'565:0 564:0 566:0 568:0 '), +(568,'SGsouth1',0,51,1444,43,54,0,-7155.86,-1317.76,242.155,5.03211,'565:0 569:0 567:0 564:0 566:0 '), +(569,'BRM_SG1',0,51,1445,43,54,0,-7215.95,-1057.94,242.73,1.72755,'571:0 568:0 570:0 572:0 573:0 '), +(570,'BRM_SG2',0,25,25,46,60,0,-7399.25,-1106.95,278.077,0.229401,'569:0 622:0 '), +(571,'SGwest1',0,51,51,43,54,0,-7085,-941.753,268.272,4.123,'569:0 572:0 573:0 '), +(572,'SGtower2',0,51,51,43,54,0,-7001.07,-1109.84,243.97,2.78587,'571:0 569:0 573:0 '), +(573,'SGwest2',0,51,51,43,54,0,-6974.99,-1007.32,241.667,3.29324,'571:0 569:0 572:0 574:0 588:0 '), +(574,'CauldronRampTop',0,51,246,43,54,0,-6974.25,-1172.02,228.781,3.87051,'573:0 575:0 '), +(575,'CauldronRampMid1',0,51,246,43,54,0,-7024.61,-1220.88,207.974,5.71816,'574:0 576:0 '), +(576,'CauldronRampMid2',0,51,246,43,54,0,-6955.19,-1270.62,180.686,0.528642,'575:0 577:0 581:0 '), +(577,'CauldronRampBottom',0,51,246,43,54,0,-6866.28,-1410.17,172.855,2.20156,'576:0 578:0 '), +(578,'CauldronRamp2Bottom',0,51,246,43,54,0,-6712.76,-1610.77,196.339,2.17329,'577:0 579:0 '), +(579,'CauldronRamp2Mid',0,51,246,43,54,0,-6668.28,-1664.76,229.053,1.92432,'578:0 580:0 '), +(580,'CauldronRamp2Top',0,51,246,43,54,0,-6680,-1730.07,255.913,4.54363,'562:0 579:0 '), +(581,'SlagPit1',0,51,1443,43,54,0,-6850.01,-1218.8,177.395,3.61294,'576:0 582:0 '), +(582,'SlagPit2',0,51,1443,43,54,0,-6764.59,-1174.15,187.213,1.12951,'581:0 583:0 '), +(583,'SlagPit3',0,51,1443,43,54,0,-6439.47,-1311.02,180.938,2.63197,'582:0 584:0 '), +(584,'SlagPit4',0,51,1443,43,54,0,-6631.29,-1289.33,208.714,0.00481129,'583:0 '), +(585,'SG_hubN1',0,51,51,43,54,0,-6686.77,-1347.22,247.961,4.79378,'562:0 586:0 588:0 589:0 '), +(586,'CauldronTopN',0,51,246,43,54,0,-6862.1,-1188.37,240.366,5.51045,'585:0 '), +(587,'FirewatchRidgeBottom',0,51,1442,43,54,0,-6612.66,-860.886,244.297,0.237204,'588:0 589:0 591:0 '), +(588,'FirewatchRidgeS',0,51,1442,43,54,0,-6817.17,-866.771,248.462,4.87941,'573:0 585:0 587:0 '), +(589,'ThoriumPointRampBottom',0,51,1442,43,54,0,-6608.41,-1025.59,244.328,1.21161,'587:0 585:0 590:0 '), +(590,'ThoriumPointRampTop',0,51,1446,43,54,1,-6473.64,-1104.63,303.285,2.64303,'589:0 180:0 '), +(591,'FirewatchRidgeTop1',0,51,1442,43,54,0,-6473.38,-887.709,324.088,3.80543,'587:0 592:0 '), +(592,'FirewatchRidgeTop2',0,51,1442,43,54,0,-6502.46,-1018.74,344.372,1.2407,'591:0 '), +(593,'SGexitEmid',0,51,51,43,54,0,-6952.2,-2064.74,282.478,5.11429,'563:0 594:0 '), +(594,'BLexitW',0,3,3,33,45,0,-6885.86,-2233.35,242.64,0.361067,'593:0 595:0 24:0 '), +(595,'ApocryphansRest',0,3,337,33,45,0,-6892.26,-2477.47,247.238,1.71982,'594:0 87:0 596:0 597:0 600:0 601:0 602:0 '), +(596,'CampCaggS',0,3,344,33,45,0,-7270.91,-2407.34,268.195,5.1163,'595:0 597:0 '), +(597,'CampCaggE',0,3,3,33,45,0,-7140.34,-2650.51,243.568,0.467924,'595:0 596:0 602:0 603:0 '), +(598,'KargathE',0,3,3,33,45,0,-6600.53,-2373.56,254.037,0.026153,'600:0 599:0 24:0 '), +(599,'KargathNE',0,3,3,33,45,0,-6431.02,-2454.24,321.326,3.51922,'598:0 600:0 '), +(600,'DustbowlW',0,3,1878,33,45,0,-6718.55,-2572.24,241.801,0.471881,'595:0 598:0 599:0 601:0 602:0 '), +(601,'DustbowlNE',0,3,1878,33,45,0,-6653.03,-2889.09,241.667,2.73974,'595:0 600:0 602:0 620:0 '), +(602,'BL_hubC',0,3,1879,33,45,0,-6902.56,-2958.24,244.772,1.67356,'600:0 597:0 601:0 595:0 608:0 603:0 '), +(603,'AgmondsendS',0,3,345,33,45,0,-7141.97,-3251.77,246.326,0.774282,'597:0 602:0 604:0 605:0 607:0 '), +(604,'BL_elemsS',0,3,3,33,45,0,-7319.09,-3144.54,317.901,5.93436,'603:0 '), +(605,'AgmondsEnd',0,3,345,33,45,0,-7033.01,-3313.39,238.277,2.5061,'603:0 606:0 607:0 609:0 '), +(606,'BL_elemsSE',0,3,3,33,45,0,-7339.37,-3450.9,320.961,0.385524,'605:0 '), +(607,'CampBoff',0,3,342,33,45,0,-7039.11,-3655.92,244.152,1.66966,'603:0 605:0 608:0 609:0 '), +(608,'BL_hubN1',0,3,1877,33,45,0,-6667.51,-3285.91,241.069,2.14484,'25:0 602:0 607:0 609:0 620:0 '), +(609,'BL_hubNE1',0,3,1898,33,45,0,-6775.23,-3555.53,245.004,5.79028,'605:0 607:0 608:0 610:0 617:0 620:0 '), +(610,'LethlorRavineEntranceS',0,3,339,38,45,0,-6742.54,-3895.6,264.574,1.61394,'609:0 611:0 '), +(611,'LethlorRavineS',0,3,339,38,45,0,-7002.17,-3938.58,263.889,5.47219,'610:0 612:0 '), +(612,'LethlorRavineC',0,3,339,38,45,0,-6820.98,-4135.08,263.933,2.32276,'611:0 613:0 '), +(613,'LethlorRavineN',0,3,339,38,45,0,-6424.74,-4106.41,263.889,3.0143,'612:0 614:0 '), +(614,'LethlorRavineNW',0,3,339,38,45,0,-6513.4,-4004.6,264.394,5.30492,'613:0 615:0 '), +(615,'LethlorRavineEntranceN',0,3,339,38,45,0,-6396.09,-3976.18,268.618,3.37284,'614:0 616:0 '), +(616,'LethlorRavineExitN',0,3,1898,38,45,0,-6467.07,-3837.32,315.608,5.41801,'615:0 617:0 '), +(617,'CampKoshS',0,3,1898,33,45,0,-6360.66,-3685.6,245.154,4.17511,'616:0 609:0 618:0 619:0 '), +(618,'CampKosh',0,3,341,33,45,0,-6249.45,-3750.01,243.041,2.72409,'617:0 619:0 '), +(619,'HammertoesDigsite',0,3,346,33,45,0,-6349.91,-3447.28,241.681,0.369852,'617:0 618:0 620:0 621:0 '), +(620,'BL_hubN2',0,3,338,33,45,0,-6494.59,-3248.21,242.617,1.06886,'25:0 601:0 608:0 609:0 619:0 '), +(621,'MakersTerrace',0,3,1897,33,45,0,-6056.99,-3302.11,258.645,3.45646,'86:0 505:0 619:0 '), +(622,'BRM_SG3',0,25,25,46,60,0,-7496.19,-1063.83,264.543,4.72301,'570:0 623:0 628:0 '), +(623,'BRM_chain1',0,25,25,46,60,0,-7501.48,-1151.43,269.644,1.34305,'622:0 624:0 627:0 '), +(624,'BRM_chain2',0,25,25,46,60,0,-7599.78,-1110.62,249.93,1.94977,'623:0 625:0 '), +(625,'BRM_chain3',0,25,25,46,60,0,-7523.43,-1048.09,180.912,0.471264,'624:0 626:0 '), +(626,'BRM_chain4',0,25,25,46,60,0,-7383.99,-1012.09,173.658,3.00809,'625:0 '), +(627,'BRM_BRS1',0,25,25,46,60,0,-7609.9,-1226.43,233.401,0.518375,'623:0 628:0 '), +(628,'BRM_BS1',0,25,25,46,60,0,-7697.61,-1089.8,217.609,3.00611,'627:0 622:0 629:0 '), +(629,'BRM_BS2',0,25,25,46,60,0,-7769.06,-1129.81,215.084,0.38328,'628:0 630:0 '), +(630,'BRM_BS3',0,25,25,46,60,0,-7993.75,-1138.96,163.061,0.128021,'629:0 631:0 637:0 639:0 641:0 '), +(631,'DracodarNW',0,46,2421,48,56,0,-7968.74,-817.038,131.202,4.58672,'630:0 632:0 636:0 '), +(632,'AltarOfStormsRoad',0,46,46,48,56,0,-7803.25,-717,176.761,6.28318,'631:0 633:0 '), +(633,'AltarOfStormsFork',0,46,255,48,56,0,-7679.67,-712.976,183.628,3.3461,'632:0 634:0 635:0 '), +(634,'AltarOfStormsVendor',0,46,255,48,56,0,-7644.75,-636.535,200.452,4.00749,'633:0 '), +(635,'AltarOfStorms',0,46,255,48,56,0,-7586.74,-773.071,190.859,2.70569,'633:0 '), +(636,'DracodarW',0,46,2421,48,56,0,-8170.67,-727.141,135.21,5.81979,'631:0 637:0 '), +(637,'DracodarS',0,46,2421,48,56,0,-8255.07,-1040.7,147.3,0.143321,'630:0 636:0 638:0 639:0 '), +(638,'DracodarS_hill1',0,46,2421,48,56,0,-8419.15,-933.413,214.705,5.70788,'637:0 '), +(639,'DracodarSE',0,46,46,48,56,0,-8254.03,-1168.68,144.64,0.0510463,'630:0 637:0 640:0 641:0 '), +(640,'DracodarS_hill2',0,46,46,48,56,0,-8431.62,-1227.92,207.816,0.498725,'639:0 '), +(641,'DracodarHub',0,46,46,48,56,0,-8073.86,-1401.78,132.05,4.03301,'630:0 639:0 642:0 644:0 645:0 '), +(642,'BlackrockStrongholdOutside',0,46,46,48,56,0,-7757.29,-1591.7,133.263,1.43923,'641:0 643:0 644:0 646:0 '), +(643,'BlackrockStrongholdInside',0,46,252,48,56,0,-7698.11,-1443.02,139.787,4.00159,'642:0 '), +(644,'PillarOfAshS',0,46,46,48,56,0,-8232.79,-1737.58,147.888,6.17911,'641:0 642:0 645:0 658:0 '), +(645,'PillarOfAshE',0,46,253,48,56,0,-8102.12,-1922.65,134.695,1.31201,'641:0 644:0 646:0 647:0 658:0 '), +(646,'ThaurissanNW',0,46,250,48,56,0,-7704.8,-2045.32,133.437,1.61634,'642:0 645:0 139:0 647:0 648:0 '), +(647,'DreadmaulRockW',0,46,249,48,56,0,-7971.45,-2449.46,130.882,1.47692,'646:0 645:0 648:0 652:0 654:0 658:0 659:0 660:0 '), +(648,'DreadmaulRockNW',0,46,249,48,56,0,-7665.96,-2453.95,147.044,2.94798,'647:0 646:0 138:0 649:0 654:0 '), +(649,'DreadmaulRockNE',0,46,249,48,56,0,-7765.85,-2707.5,172.836,1.14313,'648:0 650:0 655:0 '), +(650,'DreadmaulRockSE',0,46,2420,48,56,0,-8161.75,-2869.02,134.8,0.000368118,'649:0 138:0 651:0 652:0 659:0 '), +(651,'MorgansVigil',0,46,2418,48,56,0,-8379.57,-2741.96,186.492,6.13436,'650:0 652:0 659:0 '), +(652,'DreadmaulRockTop1',0,46,249,48,56,0,-7971.64,-2664.1,198.213,2.32517,'647:0 650:0 651:0 138:0 653:0 '), +(653,'DreadmaulRockTop2',0,46,249,48,56,0,-7873.63,-2613.13,221.072,2.92403,'652:0 '), +(654,'DreadmaulRockNWW',0,46,249,48,56,0,-7776.13,-2493.23,160.084,5.03518,'648:0 647:0 '), +(655,'DreadmaulRockInside1',0,46,249,48,56,0,-7848.98,-2661.75,172.955,1.73534,'649:0 656:0 657:0 '), +(656,'DreadmaulRockInside2',0,46,249,48,56,0,-7961.48,-2603.36,173.834,6.08133,'655:0 '), +(657,'DreadmaulRockInside3',0,46,249,48,56,0,-7975.04,-2695.45,157.958,5.80842,'655:0 '), +(658,'DreadmaulPassW',0,46,46,48,56,0,-8216.14,-2308.1,151.442,1.03911,'644:0 645:0 647:0 659:0 '), +(659,'DreadmaulPassN',0,46,46,48,56,0,-8150.16,-2629.44,133.659,1.18246,'647:0 650:0 651:0 658:0 660:0 '), +(660,'DreadmaulPass',0,46,2417,48,56,0,-8431.68,-2546.4,133.207,6.07628,'647:0 659:0 661:0 '), +(661,'RedridgeExitN',0,44,44,13,25,0,-8900,-2574.56,131.851,0.152413,'660:0 75:0 663:0 '), +(662,'RendersRockInside',0,44,998,13,25,0,-8738.68,-2205.37,149.754,2.59501,'75:0 '), +(663,'AlthersMillEntrance',0,44,97,13,25,0,-9153.73,-2628.6,109.561,0.156359,'661:0 664:0 690:0 '), +(664,'RedridgeHub1',0,44,44,13,25,0,-9099.79,-2451.23,120.464,0.197585,'663:0 665:0 667:0 669:0 '), +(665,'RedridgeBridgeN',0,44,69,13,25,2,-9287.68,-2284.47,67.5443,5.81789,'664:0 10:0 672:0 673:0 '), +(666,'RedridgeW',0,44,44,13,25,2,-9312.11,-1873.26,82.1473,5.08747,'10:0 672:0 '), +(667,'RedridgeCanyons1',0,44,95,13,25,2,-8916.14,-2305.07,134.682,3.96044,'664:0 668:0 670:0 '), +(668,'RedridgeCanyons2',0,44,95,13,25,2,-8864.57,-2150.9,133.327,3.71892,'667:0 669:0 670:0 '), +(669,'RedridgeCanyons3',0,44,69,13,25,2,-9145.97,-2198.54,119.516,4.7478,'668:0 664:0 670:0 '), +(670,'RedridgeCanyons4',0,44,95,13,25,2,-9135.91,-2029.37,127.775,5.01877,'667:0 668:0 669:0 77:0 '), +(671,'RethbanCavernsInside',0,44,98,13,25,0,-8826.83,-1947.01,133.091,2.11555,'77:0 '), +(672,'RedridgeBridgeS',0,44,44,13,25,2,-9503.49,-2289.12,74.7234,1.13183,'665:0 666:0 674:0 675:0 '), +(673,'EverstillN',0,44,68,13,25,2,-9263.45,-2458.04,56.1636,1.20646,'665:0 '), +(674,'ThreeCorners',0,44,1002,13,25,0,-9607.09,-2055.97,65.077,4.99696,'672:0 675:0 692:0 693:0 694:0 '), +(675,'RedridgeS1',0,44,1001,13,25,0,-9713.82,-2284.16,63.9369,5.46531,'672:0 674:0 676:0 677:0 '), +(676,'RedridgeS2',0,44,68,13,25,0,-9620.32,-2514.61,59.4453,0.974015,'675:0 677:0 '), +(677,'RedridgeS3',0,44,1001,13,25,0,-9624.96,-2717.09,56.3955,1.53951,'676:0 675:0 678:0 679:0 691:0 '), +(678,'StonewatchS',0,44,70,18,25,0,-9468.55,-3007.78,135.551,1.52185,'677:0 689:0 '), +(679,'RedridgeS4',0,44,997,13,25,0,-9751.37,-3185.18,58.6091,5.63735,'677:0 680:0 681:0 '), +(680,'RedridgeE1',0,44,997,13,25,0,-9611,-3315.1,49.8404,2.09716,'679:0 681:0 682:0 683:0 684:0 '), +(681,'RedridgeE2',0,44,71,13,25,0,-9499.43,-3249.9,50.6,3.02197,'679:0 680:0 '), +(682,'RedridgeE3',0,44,44,18,25,0,-9598.71,-3503.57,121.964,2.30923,'680:0 683:0 '), +(683,'RedridgeE4',0,44,71,18,25,0,-9465.03,-3460.8,116.076,2.91242,'680:0 682:0 74:0 684:0 '), +(684,'RedridgeE5',0,44,71,18,25,0,-9469.61,-3328.04,5.51622,4.94268,'680:0 683:0 '), +(685,'RedridgeNE',0,44,1000,18,25,0,-9116.4,-3271.35,104.33,2.00332,'74:0 686:0 687:0 '), +(686,'RedridgeE6',0,44,1000,18,25,0,-9317.33,-3210.41,107.191,5.54352,'685:0 74:0 687:0 '), +(687,'StonewatchFork',0,44,70,18,25,0,-9195.1,-3020.88,94.6252,4.57746,'686:0 685:0 688:0 689:0 690:0 '), +(688,'StonewatchTower',0,44,999,18,25,0,-9297.56,-2958.62,128.754,5.64364,'687:0 689:0 '), +(689,'StonewatchTop',0,44,2099,18,25,0,-9364.92,-3072.52,164.756,3.2698,'687:0 688:0 678:0 '), +(690,'AlthersMillCenter',0,44,97,13,25,0,-9215.61,-2770.74,89.3399,4.66193,'687:0 663:0 '), +(691,'EverstillE',0,44,44,13,25,0,-9457.68,-2870.36,85.486,2.24488,'677:0 '), +(692,'ThreeCornersCamp1',0,44,1002,13,25,0,-9472.89,-1956.22,83.561,4.19346,'674:0 693:0 694:0 '), +(693,'RedridgeExitW1',0,44,1002,13,25,2,-9620.34,-1809.71,51.8565,1.49408,'692:0 674:0 694:0 696:0 '), +(694,'RedridgeExitW2',0,44,1002,13,25,0,-9831.1,-1766.69,23.8242,2.51983,'692:0 674:0 693:0 695:0 792:0 '), +(695,'ElwynnHubS1',0,12,798,1,10,2,-9928.36,-1115.12,24.1788,6.21189,'705:0 704:0 694:0 696:0 706:0 '), +(696,'ElwynnHubE1',0,12,12,1,10,2,-9659.48,-1341.54,48.8561,4.72993,'693:0 697:0 698:0 704:0 695:0 '), +(697,'EastvaleLC1',0,12,88,1,10,2,-9405,-1343.43,50.0284,2.89799,'696:0 698:0 699:0 '), +(698,'StoneCairnLakeSE',0,12,86,1,10,2,-9299.52,-1180.36,69.4951,3.31622,'696:0 697:0 699:0 701:0 797:0 '), +(699,'StoneCairnLakeNE',0,12,86,1,10,2,-8956.22,-1264.96,77.7779,2.94511,'700:0 697:0 698:0 797:0 '), +(700,'StoneCairnLakeNW',0,12,86,1,10,2,-8860.62,-826.796,71.9141,4.57088,'699:0 701:0 797:0 '), +(701,'StoneCairnLakeSW',0,12,12,1,10,2,-9355.59,-802.598,64.4981,0.692979,'89:0 700:0 708:0 698:0 705:0 706:0 707:0 797:0 '), +(702,'JasperlodeMineInside1',0,12,54,1,10,2,-9125.33,-585.357,58.3507,3.90606,'89:0 703:0 '), +(703,'JasperlodeMineInside2',0,12,54,1,10,2,-9049.44,-618.138,53.1456,4.05725,'702:0 '), +(704,'ElwynnHubE2',0,12,12,1,10,2,-9613.79,-1100.85,40.5009,4.62273,'696:0 705:0 695:0 '), +(705,'ElwynnHubE3',0,12,12,1,10,2,-9618.95,-1024.81,40.4731,4.99973,'704:0 695:0 701:0 706:0 707:0 '), +(706,'BrackwellPumpkinPatch',0,12,62,1,10,2,-9777.88,-877.924,39.5328,5.4101,'705:0 701:0 695:0 707:0 '), +(707,'ElwynnHubC1',0,12,12,1,10,2,-9609.57,-527.364,55.1285,4.48725,'89:0 708:0 701:0 705:0 706:0 716:0 '), +(708,'CrystalLakeE',0,12,18,1,10,2,-9464.16,-422.645,58.9952,3.63313,'89:0 701:0 707:0 709:0 '), +(709,'CrystalLakeW',0,12,18,1,10,2,-9470.07,-173.045,59.9322,4.58858,'708:0 710:0 7:0 '), +(710,'NorthshireExit',0,12,12,1,10,2,-9133.8,-66.2742,82.4151,1.35237,'709:0 16:0 13:0 7:0 '), +(711,'EchoRidgeMineInside',0,12,34,1,10,2,-8560.22,-214.447,85.0045,2.95145,'70:0 '), +(712,'TradeDistrict',0,1519,1519,1,60,3,-8794.44,645.505,94.4595,3.54245,'16:0 '), +(713,'ForestsEdgeS',0,12,60,1,10,2,-10070.2,658.114,37.3319,5.88491,'72:0 88:0 717:0 '), +(714,'FargodeepMineOutside',0,12,57,1,10,2,-9868.23,221.066,14.0194,6.19474,'7:0 71:0 72:0 715:0 '), +(715,'FargodeepMineInside',0,12,57,1,10,2,-9779.25,104.966,4.57989,3.01191,'714:0 '), +(716,'ElwynnHubS2',0,12,12,1,10,2,-9808.32,-269.092,40.0067,5.15408,'71:0 707:0 '), +(717,'ElwynnExitW',0,12,60,1,10,2,-9747.07,741.601,25.5892,4.55914,'7:0 88:0 72:0 713:0 718:0 '), +(718,'WestfallExitNE',0,40,916,8,20,2,-9853.3,918.261,30.2216,5.34454,'717:0 719:0 745:0 746:0 '), +(719,'Longshore1',0,40,2,8,20,2,-9616.19,1059.16,5.79699,0.013632,'720:0 718:0 745:0 '), +(720,'Longshore2',0,40,2,8,20,2,-9634.2,1389.29,9.09694,4.71817,'719:0 721:0 '), +(721,'Longshore3',0,40,2,8,20,2,-9648.12,1575.23,3.54258,4.75547,'720:0 722:0 '), +(722,'Longshore4',0,40,2,8,20,2,-9776.1,1654.24,11.5179,2.15188,'721:0 723:0 742:0 743:0 '), +(723,'Longshore5',0,40,2,8,20,2,-9985.75,1913.6,4.86301,5.35756,'722:0 724:0 742:0 '), +(724,'Longshore6',0,40,2,8,20,2,-10334,2061.94,3.69692,5.05125,'723:0 725:0 739:0 '), +(725,'Longshore7',0,40,2,8,20,2,-10695.4,2102.07,8.28685,6.14098,'724:0 726:0 738:0 '), +(726,'Longshore8',0,40,2,8,20,2,-10975.2,2108.77,-0.213533,6.27058,'725:0 727:0 737:0 '), +(727,'Longshore9',0,40,2,8,20,2,-11307.8,1926.18,9.26031,4.25015,'726:0 728:0 '), +(728,'Longshore10',0,40,2,8,20,2,-11466.2,1725.82,8.61195,0.641238,'727:0 729:0 '), +(729,'DaggerHillsW',0,40,920,8,20,2,-11264.1,1714.56,39.9062,4.76851,'728:0 730:0 737:0 '), +(730,'DaggerHillsC1',0,40,920,8,20,2,-11258.8,1470.77,88.9353,4.93148,'729:0 731:0 '), +(731,'DaggerHillsC2',0,40,920,8,20,2,-11211.1,1297.89,91.1699,1.90966,'730:0 732:0 733:0 '), +(732,'DaggerHillsNook',0,40,920,8,20,2,-11235.2,1182.36,91.6522,1.1871,'731:0 733:0 '), +(733,'DaggerHillsE',0,40,920,8,20,2,-11062.7,1163.02,43.0261,2.75789,'736:0 732:0 731:0 734:0 735:0 750:0 '), +(734,'DustPlains',0,40,922,8,20,2,-11152,739.176,32.5228,1.40505,'733:0 735:0 750:0 '), +(735,'DeadAcre',0,40,917,8,20,2,-10770.8,864.294,33.1505,2.913,'736:0 733:0 734:0 746:0 750:0 '), +(736,'WestfallHubC1',0,40,40,8,20,2,-10842.5,1196.94,34.875,1.14192,'12:0 15:0 735:0 733:0 738:0 741:0 750:0 '), +(737,'DemontsPlace',0,40,921,8,20,2,-11087.9,1894.97,35.4372,5.06105,'726:0 729:0 12:0 738:0 '), +(738,'AlexstonFarmstead',0,40,219,8,20,2,-10615,1671.77,41.41,4.1441,'737:0 725:0 12:0 741:0 742:0 736:0 '), +(739,'GoldCoastQuarry',0,40,113,8,20,2,-10403.7,1909.99,9.96272,4.64675,'724:0 740:0 741:0 '), +(740,'GoldCoastQuarryInside',0,40,113,8,20,2,-10575.3,1990.25,-8.03668,3.01509,'739:0 '), +(741,'WestfallHubC2',0,40,40,8,20,2,-10495.4,1349.24,42.6842,1.76042,'738:0 739:0 15:0 742:0 745:0 736:0 '), +(742,'WestfallHubC3',0,40,918,8,20,2,-10274.9,1408.84,38.9079,3.90454,'741:0 738:0 723:0 722:0 15:0 743:0 745:0 '), +(743,'JangolodeMine',0,40,111,8,20,2,-10017,1466.37,41.0745,6.08639,'742:0 722:0 744:0 745:0 '), +(744,'JangolodeMineInside',0,40,111,8,20,2,-9887.11,1427.15,40.0346,0.896887,'743:0 '), +(745,'SaldeansFarm',0,40,107,8,20,2,-10154.1,1116.55,36.8816,1.65283,'15:0 718:0 719:0 741:0 742:0 743:0 746:0 '), +(746,'WestfallHubE1',0,40,40,8,20,2,-10333.7,859.579,39.774,0.563072,'15:0 718:0 735:0 745:0 752:0 '), +(747,'DefiasHideout',0,1581,1581,15,22,2,-11112,1483.69,32.39,3.56723,'12:0 748:0 '), +(748,'Deadmines1',0,1581,1581,15,22,2,-11252.8,1533.82,28.6803,0.814416,'747:0 749:0 '), +(749,'Deadmines2',0,1581,1581,15,22,2,-11214.4,1638.37,27.2613,1.55073,'748:0 '), +(750,'WestfallExitSE',0,40,40,8,20,2,-10870.3,667.353,30.8385,4.48578,'733:0 734:0 735:0 736:0 15:0 751:0 '), +(751,'DuskwoodExitW',0,10,10,18,30,0,-10857.5,557.367,30.4883,6.27453,'750:0 752:0 14:0 753:0 755:0 '), +(752,'HushedBankN',0,10,1097,18,30,0,-10326.3,623.959,26.42,3.17024,'746:0 751:0 757:0 758:0 763:0 '), +(753,'AddlesSteadW',0,10,536,18,30,0,-11054.9,270.776,25.2035,6.01338,'751:0 14:0 754:0 '), +(754,'AddlesSteadE',0,10,536,18,30,0,-10997.4,179.443,30.4059,1.04377,'753:0 14:0 766:0 '), +(755,'RHCemetaryS',0,10,492,18,30,0,-10587.6,294.965,31.0441,0.0129175,'751:0 14:0 756:0 757:0 765:0 766:0 '), +(756,'RHCemetaryNE',0,10,492,24,30,0,-10398,201.223,34.2462,5.21064,'755:0 757:0 758:0 762:0 764:0 765:0 '), +(757,'RHCemetaryW',0,10,492,22,30,0,-10427.4,409.679,46.6267,4.40525,'752:0 755:0 756:0 758:0 759:0 '), +(758,'RHCemetaryN',0,10,243,24,30,0,-10308.2,348.036,59.693,3.07006,'752:0 757:0 756:0 '), +(759,'DawningWoodCatacombs1',0,10,2098,18,30,0,-10261.5,383.964,10.414,2.92673,'757:0 760:0 '), +(760,'DawningWoodCatacombs2',0,10,2098,18,30,0,-10234.6,284.99,2.79944,4.82935,'759:0 761:0 '), +(761,'DawningWoodCatacombs3',0,10,2098,18,30,0,-10220.1,166.144,0.047382,3.23144,'760:0 762:0 '), +(762,'DawningWoodCatacombs4',0,10,2098,18,30,0,-10338.4,136.513,4.82896,0.380849,'756:0 761:0 '), +(763,'DuskwoodNW1',0,10,799,18,30,0,-10173.2,350.099,32.3759,4.62356,'752:0 764:0 '), +(764,'DuskwoodNW2',0,10,799,18,30,0,-10183.8,-55.3832,27.6626,5.67208,'763:0 756:0 765:0 793:0 '), +(765,'DuskwoodC1',0,10,10,18,30,0,-10482,-16.5716,51.5694,0.81242,'764:0 755:0 756:0 766:0 '), +(766,'DuskwoodC2',0,10,10,18,30,0,-10750.8,67.4617,28.3337,1.80202,'765:0 755:0 14:0 754:0 767:0 '), +(767,'DuskwoodC3',0,10,10,18,30,0,-10910.1,-371.894,39.8351,3.06651,'766:0 768:0 769:0 773:0 774:0 775:0 779:0 795:0 '), +(768,'DuskwoodExitS',0,10,10,18,30,0,-11268.2,-368.936,61.3823,6.14723,'767:0 858:0 '), +(769,'VulGolOgreMound1',0,10,93,24,30,0,-11005.9,-176.407,14.5532,5.12818,'767:0 770:0 771:0 '), +(770,'VulGolOgreMound2',0,10,93,24,30,0,-10959.3,-38.5543,13.6432,4.44096,'769:0 771:0 '), +(771,'VulGolOgreMound3',0,10,93,24,30,0,-11083.6,-80.7448,16.7954,5.39522,'769:0 770:0 772:0 '), +(772,'VulGolOgreMoundInside',0,10,93,24,30,0,-11234.1,-172.07,4.22438,4.5174,'771:0 '), +(773,'YorgenFarmsteadW',0,10,245,24,30,0,-11096.9,-452.028,32.1764,4.54422,'767:0 774:0 '), +(774,'YorgenFarmsteadE',0,10,245,24,30,0,-11052.8,-598.054,29.3447,1.47723,'767:0 773:0 776:0 '), +(775,'DuskwoodC4',0,10,10,18,30,0,-10908,-739.892,54.8364,0.583838,'767:0 776:0 778:0 779:0 '), +(776,'RottingOrchardW',0,10,241,18,30,0,-10986.3,-770.892,55.1122,1.55183,'774:0 775:0 777:0 778:0 '), +(777,'RottingOrchardS',0,10,241,18,30,0,-11103.5,-891.729,62.1149,0.69773,'776:0 778:0 '), +(778,'DuskwoodC5',0,10,10,18,30,0,-10823,-837.935,55.75,1.63037,'775:0 776:0 777:0 779:0 780:0 '), +(779,'DuskwoodC6',0,10,10,18,30,0,-10768,-644.008,42.2621,2.90859,'767:0 775:0 778:0 786:0 '), +(780,'DuskwoodE1',0,10,42,18,30,0,-10757.1,-1156.72,24.9918,1.69202,'778:0 124:0 782:0 783:0 '), +(781,'RolandsDoomInside',0,10,2161,24,30,0,-11157.5,-1167.04,42.5151,0.315576,'124:0 '), +(782,'TranquilGardensCemetery',0,10,121,18,30,0,-11023.4,-1315.7,53.1833,6.14911,'780:0 783:0 '), +(783,'DuskwoodE2',0,10,10,18,30,0,-10813.8,-1366.37,42.2527,0.00140238,'780:0 782:0 3:0 784:0 794:0 '), +(784,'DarkshireExitE',0,10,10,18,30,0,-10539.7,-1338.09,48.0906,1.65268,'785:0 783:0 3:0 '), +(785,'DuskwoodExitE',0,10,10,18,30,0,-10442.9,-1486.98,73.8977,5.13748,'784:0 798:0 '), +(786,'DuskwoodC7',0,10,242,24,30,0,-10451.9,-825.607,50.4132,2.61635,'779:0 787:0 788:0 793:0 '), +(787,'DuskwoodC8',0,10,242,24,30,0,-10647.9,-908.344,50.934,5.93661,'786:0 3:0 788:0 '), +(788,'DuskwoodNE1',0,10,242,18,30,0,-10219.2,-1021.98,31.9202,2.92658,'786:0 787:0 3:0 789:0 790:0 793:0 '), +(789,'ManorMismantle',0,10,1098,24,30,0,-10332.7,-1264.03,35.3024,1.53643,'788:0 3:0 790:0 '), +(790,'DuskwoodNE2',0,10,10,18,30,0,-10181.4,-1145.35,24.304,3.28393,'788:0 789:0 791:0 793:0 '), +(791,'DuskwoodNE3',0,10,10,18,30,0,-10067.1,-1379.68,29.9246,1.99195,'790:0 792:0 '), +(792,'DuskwoodExitNE',0,10,10,18,30,0,-9963.01,-1637.21,27.2574,2.08816,'694:0 791:0 '), +(793,'DuskwoodN',0,10,799,18,30,0,-10020.5,-660.012,39.1578,1.66406,'790:0 788:0 764:0 786:0 '), +(794,'Naraxis',0,10,10,18,30,0,-10619.4,-1502.44,90.534,3.1465,'783:0 '), +(795,'TwilightGroveEntrance',0,10,10,50,60,0,-10718.7,-425.681,126.691,0.442773,'767:0 796:0 '), +(796,'TwilightGrove',0,10,856,50,60,0,-10419.3,-421.597,45.6561,3.03852,'795:0 '), +(797,'HeroesVigil',0,12,56,1,10,0,-9101.04,-1034.1,72.9837,5.20227,'698:0 699:0 700:0 701:0 '), +(798,'DWPExitW',0,41,2697,50,60,0,-10464.2,-1734.78,86.7801,1.49361,'785:0 171:0 802:0 '), +(799,'DeadwindRavineSW',0,41,2558,50,60,0,-10919.4,-1957.74,114.777,4.67839,'171:0 800:0 '), +(800,'TheViceW',0,41,2561,50,60,0,-10843.3,-2118.87,121.161,1.51324,'799:0 172:0 79:0 '), +(801,'DWPExitE',0,41,2938,50,60,0,-10593.5,-2125.78,90.9212,3.83802,'172:0 170:0 802:0 811:0 '), +(802,'DeadsmansCrossingE',0,41,41,50,60,0,-10437,-2037.61,94.6245,1.59492,'798:0 801:0 810:0 '), +(803,'GroshgokCompoundInside',0,41,2937,50,60,0,-11169.4,-2483.11,105.139,0.944598,'79:0 '), +(804,'KarazhanOutskirts',0,41,2562,50,60,0,-11148.9,-2130,55.9803,1.2403,'79:0 805:0 806:0 '), +(805,'Karazhan',0,41,2562,50,60,0,-11115.2,-2008.72,48.4017,4.31317,'804:0 806:0 '), +(806,'KarazhanCellarEntrance',0,41,2837,50,60,0,-11173.4,-2033.69,47.0759,0.798522,'804:0 805:0 807:0 '), +(807,'KarazhanCellar1',0,41,2837,50,60,0,-11100.4,-1963.77,1.93936,4.49657,'806:0 808:0 '), +(808,'KarazhanCellar2',0,41,2837,50,60,0,-11159.5,-1898.41,-17.7918,6.21463,'807:0 809:0 '), +(809,'KarazhanCellar3',0,41,2837,50,60,0,-11033.9,-1910.04,-32.9889,4.98353,'808:0 '), +(810,'DeadmansCrossingBottom',0,41,41,50,60,0,-10282.6,-2019.49,51.0186,2.21028,'802:0 '), +(811,'SSExitW',0,8,8,33,45,0,-10546.2,-2376.39,84.2413,1.87648,'801:0 812:0 '), +(812,'SSW1',0,8,8,33,45,0,-10381.8,-2424.26,52.7345,2.75809,'811:0 813:0 '), +(813,'SSW2',0,8,8,33,45,0,-10427.8,-2549.06,24.1947,5.68762,'812:0 814:0 830:0 '), +(814,'IthariusCaveOutside',0,8,1777,33,45,0,-10561.3,-2508.68,22.0709,5.83489,'813:0 815:0 816:0 '), +(815,'IthariusCave',0,8,1777,33,45,0,-10677.4,-2531.21,28.912,6.2649,'814:0 '), +(816,'SSCRW1',0,8,1780,33,45,0,-10387.5,-2710.03,21.6778,1.56036,'814:0 817:0 818:0 819:0 830:0 '), +(817,'MistyValley',0,8,116,33,45,0,-10110,-2413.91,29.9136,4.35049,'816:0 818:0 '), +(818,'SSW3',0,8,8,33,45,0,-10320.4,-2794.27,21.9908,1.07734,'817:0 816:0 819:0 820:0 829:0 830:0 '), +(819,'Harborage',0,8,657,33,45,3,-10113,-2807.71,22.1444,3.06047,'818:0 816:0 820:0 829:0 '), +(820,'SSN1',0,8,1798,33,45,0,-10099.4,-3266.23,20.4381,1.58785,'818:0 819:0 821:0 827:0 828:0 829:0 '), +(821,'SSN2',0,8,76,33,45,0,-9974.74,-3687.45,21.6788,2.09247,'820:0 822:0 823:0 827:0 828:0 829:0 '), +(822,'SSNE1',0,8,2403,33,45,0,-9634.53,-3969.85,0.0000146627,2.75613,'821:0 823:0 '), +(823,'SSNE2',0,8,300,33,45,0,-10034.8,-4306.49,1.78832,0.101491,'822:0 821:0 824:0 826:0 827:0 '), +(824,'SSE',0,8,300,33,45,0,-10512,-4371.71,6.04597,6.27469,'823:0 825:0 80:0 826:0 827:0 '), +(825,'SSSE',0,8,300,33,45,0,-11038.3,-4116.28,1.98503,5.82703,'186:0 824:0 80:0 '), +(826,'PoolOfTearsE',0,8,1778,33,45,0,-10444.5,-4087.57,23.9872,1.47592,'80:0 186:0 823:0 824:0 827:0 '), +(827,'PoolOfTearsN',0,8,8,33,45,0,-10147.3,-3802.05,22.1649,2.27113,'820:0 821:0 823:0 824:0 826:0 828:0 829:0 '), +(828,'PoolOfTearsW',0,8,8,33,45,0,-10377.4,-3577.61,22.0023,3.94206,'820:0 821:0 827:0 80:0 51:0 829:0 '), +(829,'SSC',0,8,1798,33,45,0,-10292.9,-3192.2,22.1489,5.69742,'818:0 819:0 820:0 821:0 827:0 828:0 51:0 830:0 '), +(830,'SSExitS',0,8,8,33,45,0,-10553.9,-3043.33,24.794,0.0857489,'813:0 816:0 818:0 829:0 51:0 831:0 '), +(831,'SSExitSS',0,4,4,43,54,0,-10690.8,-2978.8,37.8508,5.87412,'830:0 834:0 '), +(832,'StagalbogInside1',0,8,1817,33,45,0,-10966.8,-3697.03,11.0105,4.21105,'80:0 833:0 '), +(833,'StagalbogInside2',0,8,1817,33,45,0,-10894.6,-3613.42,16.0499,6.1176,'832:0 '), +(834,'BLExitN',0,4,4,43,54,0,-10811.4,-2992.21,41.134,0.16765,'831:0 835:0 839:0 846:0 '), +(835,'BLNW1',0,4,1437,43,54,0,-11009.6,-2785.5,4.70615,5.63206,'834:0 836:0 839:0 840:0 '), +(836,'DreadmaulHoldEntrance',0,4,1437,43,54,0,-10916.9,-2714.76,7.63624,3.85903,'835:0 837:0 838:0 '), +(837,'DreadmaulHoldRight',0,4,1437,43,54,0,-10814.2,-2705.35,8.14439,3.20125,'836:0 838:0 '), +(838,'DreadmaulHoldLeft',0,4,1437,43,54,0,-10859.8,-2615.61,8.07541,4.52661,'836:0 837:0 '), +(839,'BLNW2',0,4,4,43,54,0,-11080.5,-2903.3,9.18506,0.371865,'835:0 834:0 196:0 '), +(840,'BLW1',0,4,4,43,54,0,-11249.1,-2724.95,13.3527,2.49675,'835:0 841:0 842:0 '), +(841,'DreadmaulPost',0,4,1439,43,54,0,-11528.8,-2858.71,8.50437,6.19557,'840:0 196:0 842:0 856:0 '), +(842,'BLAltarOfStormsBottom',0,4,4,43,54,0,-11501.6,-2718.85,5.65372,0.900431,'840:0 841:0 843:0 856:0 '), +(843,'BLAltarOfStormsMid',0,4,4,47,54,0,-11381.5,-2566.03,75.8033,3.74358,'842:0 844:0 '), +(844,'BLAltarOfStormsTop',0,4,1441,47,54,0,-11255.9,-2556.41,97.0494,2.9896,'843:0 '), +(845,'BLE1',0,4,4,43,54,0,-11149.6,-3232.6,8.07553,2.30354,'196:0 846:0 850:0 '), +(846,'NethergardeOutside',0,4,1438,43,54,0,-10961.7,-3200.5,45.5708,0.954623,'834:0 845:0 847:0 '), +(847,'NethergardeInside1',0,4,1438,43,54,2,-11008.9,-3340.48,64.7225,4.78147,'846:0 848:0 '), +(848,'NethergardeInside2',0,4,1438,43,54,3,-10993.2,-3453.24,64.8707,3.21459,'847:0 849:0 '), +(849,'NethergardeInside3',0,4,1438,43,54,2,-11112.2,-3438.11,79.0946,6.1402,'848:0 '), +(850,'BLE2',0,4,1440,43,54,0,-11310.9,-3410.18,7.46828,4.16649,'845:0 851:0 852:0 '), +(851,'BLE2Inside',0,4,1440,43,54,0,-11225.8,-3486.48,8.69295,2.17551,'850:0 '), +(852,'BLE3',0,4,4,43,54,0,-11438.8,-3304.34,7.30604,5.76674,'196:0 850:0 853:0 854:0 '), +(853,'BLSE',0,4,72,43,54,0,-11632.5,-3381.23,14.577,0.215935,'852:0 854:0 855:0 '), +(854,'BLS1',0,4,4,43,54,0,-11607.8,-3099.85,7.81021,5.30924,'196:0 852:0 853:0 855:0 '), +(855,'BLS2',0,4,72,43,54,0,-11765.8,-2959.84,7.91609,5.25623,'853:0 854:0 856:0 '), +(856,'BLS3',0,4,4,43,54,0,-11716.6,-2785.99,8.27783,3.20635,'855:0 841:0 842:0 857:0 '), +(857,'TaintedScar1',0,4,73,55,60,0,-11907.8,-2658.64,-2.15637,5.48008,'856:0 '), +(858,'SVExitN',0,33,33,33,45,0,-11362.1,-380.82,64.9035,0.170793,'768:0 859:0 '), +(859,'SVExitNFork',0,33,33,33,45,0,-11397.1,-284.376,58.1739,3.29667,'858:0 860:0 861:0 '), +(860,'RebelCamp',0,33,99,33,45,3,-11314.2,-182.813,75.1397,3.66777,'859:0 '), +(861,'SVEntranceN',0,33,33,33,45,0,-11511.9,-302.586,38.7986,0.353382,'859:0 863:0 864:0 862:0 '), +(862,'NesingwarysExpeditionCamp',0,33,100,33,45,0,-11616,-50.0157,10.9823,4.49439,'861:0 879:0 880:0 881:0 901:0 902:0 907:0 '), +(863,'SVNBridge1N',0,33,33,33,45,0,-11604.1,-282.333,37.221,5.64107,'861:0 879:0 '), +(864,'SVNHubN1',0,33,33,33,45,0,-11643.7,-473.703,17.1608,0.3141,'861:0 865:0 873:0 '), +(865,'KurzensCompound',0,33,101,33,45,0,-11604,-644.056,29.157,3.77181,'864:0 866:0 872:0 '), +(866,'TheStockpile1',0,33,106,33,45,0,-11461.5,-750.28,30.6105,4.24893,'865:0 867:0 '), +(867,'TheStockpile2',0,33,106,33,45,0,-11514.8,-845.839,22.0076,3.08261,'866:0 868:0 869:0 '), +(868,'TheStockpile3',0,33,106,33,45,0,-11418.4,-794.929,14.9653,3.89354,'867:0 '), +(869,'TheStockpile4',0,33,106,33,45,0,-11506.4,-946.763,29.2275,0.414209,'867:0 870:0 '), +(870,'TheStockpile5',0,33,106,33,45,0,-11406.8,-896.737,18.0813,0.89682,'869:0 871:0 '), +(871,'TheStockpile6',0,33,106,33,45,0,-11337.1,-985.396,27.3203,1.9202,'870:0 '), +(872,'SVNHubN2',0,33,33,33,45,0,-11729.7,-799.127,29.6214,0.89486,'865:0 873:0 875:0 913:0 '), +(873,'VentureCoBaseCamp',0,33,1760,33,45,0,-11959.6,-531.751,11.3978,5.11834,'872:0 864:0 874:0 875:0 913:0 '), +(874,'SVNHub1',0,33,33,33,45,0,-12225,-546.002,28.8839,0.0407319,'873:0 875:0 911:0 913:0 915:0 '), +(875,'SVE1',0,33,33,33,45,0,-12149.1,-965.968,32.4681,1.43677,'872:0 873:0 874:0 876:0 878:0 913:0 '), +(876,'MoshoggOgreMound',0,33,105,33,45,0,-12352.7,-972.262,13.1171,5.38338,'875:0 877:0 878:0 913:0 '), +(877,'MoshoggOgreMoundInside',0,33,105,33,45,0,-12364,-1147.48,0.104103,2.92311,'876:0 '), +(878,'MoshoggOgreMoundUpper',0,33,105,33,45,0,-12466.5,-881.644,39.1084,5.72306,'875:0 876:0 914:0 918:0 '), +(879,'SVNBridge1C',0,33,33,33,45,0,-11710.1,-210.005,39.5643,5.90174,'863:0 880:0 862:0 '), +(880,'SVNBridge1S',0,33,33,33,45,0,-11818.3,-43.4252,39.7487,5.27146,'879:0 862:0 903:0 905:0 907:0 '), +(881,'SVNHubW1',0,33,33,33,45,0,-11518.9,255.102,25.1192,4.23669,'862:0 882:0 883:0 901:0 '), +(882,'SVNHubW2',0,33,33,33,45,0,-11502.5,373.117,53.2392,3.3217,'881:0 883:0 '), +(883,'ZulKundaNE',0,33,33,33,45,0,-11650.7,401.397,42.8581,5.68183,'881:0 882:0 884:0 889:0 890:0 '), +(884,'ZulKundaE',0,33,33,33,45,0,-11785.5,436.17,47.4037,6.2807,'883:0 885:0 889:0 899:0 '), +(885,'ZulKundaSE',0,33,33,33,45,0,-11863.3,560.263,47.0214,5.06137,'884:0 886:0 887:0 889:0 '), +(886,'ZulKundaS',0,33,33,33,45,0,-11831.5,711.922,45.1329,4.4252,'885:0 888:0 '), +(887,'SavageCoastNW1',0,33,301,33,45,0,-11924.1,793.032,3.06387,4.97496,'885:0 894:0 185:0 897:0 898:0 '), +(888,'ZulKunda1',0,33,102,33,45,0,-11690.4,742.837,49.7495,3.41987,'886:0 889:0 891:0 '), +(889,'ZulKunda2',0,33,102,33,45,0,-11694.8,565.959,49.7011,1.34053,'883:0 884:0 885:0 888:0 890:0 891:0 '), +(890,'ZulKunda3',0,33,102,33,45,0,-11550.4,601.183,50.5784,3.11945,'883:0 889:0 891:0 '), +(891,'ZulKunda4',0,33,102,33,45,0,-11622.9,760.15,39.7329,0.007312,'888:0 889:0 890:0 892:0 893:0 '), +(892,'ZulKunda5',0,33,102,33,45,0,-11528.7,724.898,59.4101,3.50037,'891:0 '), +(893,'ZulKundaW',0,33,122,33,45,0,-11572.6,840.524,8.14386,2.72086,'891:0 894:0 '), +(894,'SavageCoastNW2',0,33,122,33,45,0,-11692.5,954.651,3.70656,5.00441,'893:0 887:0 185:0 897:0 '), +(895,'YojambaIsleW',0,33,3357,33,45,0,-11810.6,1364.62,0.0261903,4.46053,'185:0 '), +(896,'YojambaIsleN',0,33,3357,33,45,0,-11748.3,1318.44,5.22585,2.37531,'185:0 '), +(897,'VileReefIsle',0,33,301,33,45,0,-12154.2,871.091,18.6659,5.996,'894:0 887:0 '), +(898,'SavageCoastW1',0,33,301,33,45,0,-12016.1,440.588,3.35169,0.661185,'887:0 899:0 900:0 '), +(899,'SVNWHub1',0,33,33,33,45,0,-11888.7,290.463,12.6077,0.998902,'898:0 884:0 900:0 901:0 902:0 903:0 '), +(900,'SavageCoastW2',0,33,33,33,45,0,-12196.8,238.239,2.19021,4.81004,'898:0 899:0 904:0 37:0 '), +(901,'SVNHunW3',0,33,100,33,45,0,-11656.6,59.5536,17.3151,1.50941,'899:0 881:0 862:0 902:0 '), +(902,'TkashiRuins',0,33,126,33,45,0,-11843.2,59.0602,14.1655,3.34528,'899:0 901:0 862:0 903:0 905:0 '), +(903,'KalaiRuins',0,33,125,33,45,0,-12069.3,66.3927,-5.18214,3.5436,'902:0 899:0 880:0 904:0 906:0 '), +(904,'SVNHub2',0,33,33,33,45,0,-12283.3,28.601,18.2879,0.246879,'903:0 900:0 37:0 912:0 '), +(905,'SVNBridge2N',0,33,33,33,45,0,-11907.9,-50.0313,39.7259,0.870484,'902:0 880:0 906:0 907:0 '), +(906,'SVNBridge2S',0,33,33,33,45,0,-12090.1,-139.679,35.2928,0.399245,'903:0 905:0 910:0 '), +(907,'SVCHub1',0,33,33,33,45,0,-11853.5,-167.599,15.3333,0.51312,'862:0 880:0 905:0 '), +(908,'SavageCoastW3',0,33,301,33,45,0,-12543.1,74.9728,0.873061,0.513523,'37:0 912:0 '), +(909,'SavageCoastW4',0,33,1578,33,45,0,-12691.2,142.464,3.092,5.80512,'921:0 924:0 925:0 936:0 '), +(910,'SVNBridge3W',0,33,33,33,45,0,-12171.4,-240.719,29.8621,0.766786,'906:0 911:0 912:0 '), +(911,'SVNBridge3E',0,33,33,33,45,0,-12157.4,-431.52,30.3485,1.45204,'874:0 910:0 913:0 '), +(912,'MizjahRuins',0,33,129,33,45,0,-12468.7,-147.093,13.8431,5.95827,'904:0 908:0 910:0 916:0 '), +(913,'SVNHub3',0,33,1740,33,45,0,-12127.6,-649.027,14.8876,2.30225,'874:0 876:0 911:0 873:0 875:0 872:0 '), +(914,'BaliamahRuins',0,33,127,33,45,0,-12540.7,-734.478,39.4424,6.20568,'878:0 915:0 917:0 918:0 '), +(915,'SVNHub4',0,33,33,33,45,0,-12417,-580.502,11.0755,6.10556,'914:0 874:0 916:0 '), +(916,'SVNHub5',0,33,33,33,45,0,-12522,-370.721,12.7107,0.882666,'912:0 915:0 917:0 921:0 '), +(917,'ZiatajaiRuins',0,33,128,33,45,0,-12701.1,-464.07,30.0552,6.19787,'914:0 916:0 918:0 920:0 '), +(918,'ZulMamweN',0,33,33,33,45,0,-12776.9,-784.489,63.0348,0.421265,'878:0 914:0 917:0 919:0 '), +(919,'ZulMamweC',0,33,103,33,45,0,-12985.7,-833.459,69.9343,0.193503,'918:0 920:0 '), +(920,'ZulMamweW',0,33,103,33,45,0,-12943.6,-608.837,53.0521,6.05178,'917:0 919:0 '), +(921,'SVSHub1',0,33,33,33,45,0,-12826,-301.024,9.96047,5.46666,'916:0 922:0 909:0 925:0 '), +(922,'SVSVentureCoMine1',0,33,33,33,45,0,-12978,-452.477,53.6008,5.60214,'921:0 923:0 '), +(923,'SVSVentureCoMine2',0,33,33,33,45,0,-13088.9,-466.483,47.2022,3.61705,'922:0 '), +(924,'GurubashiArenaOuterN',0,33,1577,33,45,0,-12949,251.702,18.4183,2.2214,'909:0 937:0 '), +(925,'STCHub1',0,33,1577,33,45,0,-13136.8,-184.248,-3.10173,5.92455,'909:0 921:0 926:0 929:0 '), +(926,'CrystalveinMine1',0,33,310,33,45,0,-13322.7,-420.202,15.4509,2.04861,'925:0 927:0 928:0 929:0 '), +(927,'CrystalveinMine2',0,33,310,33,45,0,-13158.5,-564.518,4.64271,0.568135,'926:0 928:0 '), +(928,'CrystalveinMine3',0,33,310,33,45,0,-13170,-467.696,3.57551,3.1305,'926:0 927:0 '), +(929,'STCHub2',0,33,1577,33,45,0,-13246.3,-110.492,19.5979,5.04296,'926:0 925:0 930:0 '), +(930,'GurubashiOuterSE',0,33,1741,33,45,0,-13280.6,57.3154,17.1498,4.95263,'929:0 931:0 932:0 933:0 934:0 '), +(931,'GurubashiOuterSSE',0,33,1741,33,45,0,-13402.5,96.0163,23.7905,6.03724,'930:0 932:0 933:0 934:0 935:0 943:0 944:0 '), +(932,'RuinsOfJubuwal',0,33,477,33,45,0,-13382.8,-24.5642,22.0332,0.894086,'930:0 931:0 '), +(933,'GurubashiArenaInside1',0,33,2177,33,45,0,-13216,312.587,21.8574,3.50161,'930:0 931:0 934:0 '), +(934,'GurubashiArenaInside2',0,33,2177,33,45,0,-13163.8,257.166,21.8574,3.62531,'930:0 931:0 933:0 '), +(935,'STCHub3',0,33,1577,33,45,0,-13475.6,312.292,31.942,5.22555,'931:0 938:0 942:0 '), +(936,'SSavageCoast1',0,33,1578,33,45,0,-12862.3,459.666,6.403,4.74253,'909:0 937:0 '), +(937,'GurubashiArenaOuterW',0,33,1741,33,45,0,-13076.6,428.328,24.6507,6.12678,'924:0 936:0 938:0 '), +(938,'GurubashiArenaOuterSW',0,33,1577,33,45,0,-13273.2,479.65,3.7542,5.77924,'935:0 937:0 939:0 940:0 '), +(939,'BloodsailCompoundW',0,33,1739,33,45,0,-13331.9,777.87,2.16424,3.85895,'938:0 940:0 941:0 '), +(940,'BloodsailCompoundC',0,33,1739,33,45,0,-13470.3,687.069,8.46535,3.18977,'938:0 939:0 941:0 '), +(941,'SSavageCoast2',0,33,1578,33,45,0,-13705.9,620.54,10.152,3.17013,'939:0 940:0 966:0 968:0 '), +(942,'STCHub4',0,33,1577,33,45,0,-13618.7,330.576,43.7429,5.59271,'935:0 943:0 968:0 '), +(943,'STCHub5',0,33,1577,33,45,0,-13719.1,129.331,23.7103,3.72188,'931:0 942:0 944:0 964:0 965:0 '), +(944,'STCHub6',0,33,1577,33,45,0,-13572.2,-93.8439,42.7481,1.44857,'931:0 943:0 945:0 '), +(945,'RuinsOfAboraz',0,33,311,33,45,0,-13627.7,-351.772,12.434,1.10692,'944:0 946:0 '), +(946,'CrystalShore1',0,33,302,33,45,0,-13867.2,-89.5993,18.4363,2.92315,'945:0 947:0 964:0 '), +(947,'CrystalShore2',0,33,302,33,45,0,-14086.6,-142.781,3.55938,1.30916,'946:0 948:0 '), +(948,'WildShore1',0,33,43,33,45,0,-14260.2,-15.7846,3.81308,5.84287,'947:0 949:0 '), +(949,'WildShore2',0,33,43,33,45,0,-14293.2,109.305,7.5176,0.82025,'948:0 950:0 962:0 '), +(950,'WildShore3',0,33,43,33,45,0,-14585.8,177.319,2.91916,6.15507,'949:0 951:0 952:0 955:0 958:0 '), +(951,'WildShore4',0,33,43,33,45,0,-14708.6,511.328,2.52661,5.16547,'950:0 952:0 '), +(952,'WildShoreShips1',0,33,43,33,45,0,-14894,302.458,3.67936,4.08476,'950:0 951:0 953:0 954:0 955:0 '), +(953,'WildShoreShips1Left',0,33,43,33,45,0,-14940,357.129,0.221882,0.0694,'952:0 '), +(954,'WildShoreShips1Right',0,33,43,33,45,0,-15009.9,266.91,0.198072,0.0485881,'952:0 '), +(955,'WildShoreShips2',0,33,43,33,45,0,-14843.8,75.9812,2.07268,0.570875,'950:0 952:0 956:0 957:0 '), +(956,'WildShoreShips2Center',0,33,43,33,45,0,-14926.8,110.764,0.2001,4.9102,'955:0 '), +(957,'JagueroIsle1',0,33,297,33,45,0,-14817.1,-427.547,1.62653,0.804531,'955:0 958:0 '), +(958,'JagueroIsle2',0,33,297,33,45,0,-14556.9,-277.709,10.043,3.68499,'950:0 957:0 '), +(959,'BootyBayW',0,33,35,33,45,0,-14300.4,523.178,8.69884,4.45859,'36:0 960:0 '), +(960,'BootyBayS',0,33,35,33,45,1,-14462.8,467.081,15.1246,5.4423,'36:0 959:0 '), +(961,'BootyBayEntrance',0,33,1577,33,45,0,-14249.5,333.168,24.6767,2.49077,'36:0 962:0 967:0 '), +(962,'STCHub7',0,33,1577,33,45,0,-14217.2,239.309,20.2817,3.77098,'949:0 961:0 963:0 '), +(963,'STCHub8',0,33,1577,33,45,0,-14073.6,266.586,17.42,5.16308,'962:0 964:0 965:0 '), +(964,'MistvaleValley',0,33,1737,33,45,0,-13952.8,86.1666,15.8298,5.2161,'76:0 943:0 946:0 963:0 '), +(965,'STCHub9',0,33,1577,33,45,0,-13879.6,258.837,17.7662,5.83459,'943:0 963:0 '), +(966,'SSavageCoast3',0,33,1578,33,45,0,-13908.6,676.67,10.0579,5.32409,'967:0 941:0 969:0 '), +(967,'SSavageCoast4',0,33,1578,33,45,0,-14050.8,500.109,2.8225,0.635261,'961:0 966:0 '), +(968,'SSavageCoast5',0,33,1578,33,45,0,-13683.6,505.174,34.349,3.2526,'941:0 942:0 969:0 970:0 '), +(969,'SSavageCoast6',0,33,1577,33,45,0,-13859.9,575.635,44.9464,6.06235,'966:0 968:0 '), +(970,'SSavageCoast7',0,33,1577,33,45,0,-13849.3,494.156,89.7643,5.84244,'968:0 971:0 '), +(971,'SSavageCoast8',0,33,1738,33,45,0,-13806.7,377.83,94.1372,2.10198,'970:0 '); diff --git a/sql/Bots/updates/world/2023_04_04_00_creature_template_npcbot_wander_nodes.sql b/sql/Bots/updates/world/2023_04_04_00_creature_template_npcbot_wander_nodes.sql new file mode 100644 index 000000000..8d55e92e7 --- /dev/null +++ b/sql/Bots/updates/world/2023_04_04_00_creature_template_npcbot_wander_nodes.sql @@ -0,0 +1,2343 @@ +-- +DROP TABLE IF EXISTS `creature_template_npcbot_wander_nodes`; +CREATE TABLE `creature_template_npcbot_wander_nodes` ( + `id` int(10) unsigned NOT NULL, + `name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'RENAME_ME', + `mapid` smallint(5) unsigned NOT NULL DEFAULT '0', + `zoneid` int(10) unsigned NOT NULL DEFAULT '0', + `areaid` int(10) unsigned NOT NULL DEFAULT '0', + `minlevel` tinyint(3) unsigned NOT NULL DEFAULT '0', + `maxlevel` tinyint(3) unsigned NOT NULL DEFAULT '0', + `flags` int(10) unsigned NOT NULL DEFAULT '0', + `x` float NOT NULL DEFAULT '0', + `y` float NOT NULL DEFAULT '0', + `z` float NOT NULL DEFAULT '0', + `o` float NOT NULL DEFAULT '0', + `links` mediumtext COLLATE utf8mb4_unicode_ci, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Bot Wander Map'; + +INSERT INTO `creature_template_npcbot_wander_nodes` (`id`,`name`,`mapid`,`zoneid`,`areaid`,`minlevel`,`maxlevel`,`flags`,`x`,`y`,`z`,`o`,`links`) VALUES +(1,'Anvilmar',0,1,132,1,5,2,-6077.84,384.826,393.598,4.63263,'542:0 '), +(2,'Brill',0,85,159,1,10,5,2249.85,278.414,34.1142,5.11546,'18:0 277:0 278:0 279:0 281:0 '), +(3,'Darkshire',0,10,42,18,30,3,-10560.6,-1182.34,27.9637,3.1803,'783:0 784:0 787:0 788:0 789:0 '), +(4,'Deathknell',0,85,154,1,10,5,1879.83,1588.2,90.1725,5.25172,'295:0 296:0 '), +(5,'Dun Algaz',0,11,836,18,30,0,-4245.13,-2356.42,204.034,3.9477,'483:0 '), +(6,'Dun Modr',0,11,205,18,30,0,-2610.26,-2350.56,81.5918,1.1263,'444:0 '), +(7,'Goldshire',0,12,87,1,10,3,-9480.09,63.5218,56.1755,3.4173,'88:0 71:0 16:0 42:0 72:0 710:0 709:0 714:0 717:0 '), +(8,'Ironforge',0,1,809,1,10,2,-5023.64,-831.425,495.319,5.37056,'559:0 560:0 '), +(9,'Kharanos',0,1,131,1,10,3,-5501.22,-472.604,408.453,2.4002,'537:0 545:0 557:0 '), +(10,'Lakeshire',0,44,69,13,25,3,-9235.12,-2145.54,71.2121,5.71383,'665:0 666:0 '), +(11,'Menethil Harbor',0,11,150,18,30,3,-3672.7,-828.455,9.89925,3.18444,'476:0 477:0 478:0 '), +(12,'Moonbrook',0,40,20,8,20,2,-11017.1,1510.17,43.1667,2.58627,'737:0 738:0 736:0 747:0 '), +(13,'Northshire Abbey',0,12,9,1,10,3,-8900.51,-116.199,81.8499,0.369434,'710:0 70:0 73:0 '), +(14,'Raven Hill',0,10,94,18,30,0,-10805.5,291.025,30.9282,3.92422,'751:0 753:0 754:0 755:0 766:0 '), +(15,'Sentinel Hill',0,40,108,8,20,3,-10509.5,1047.74,60.519,5.20046,'741:0 742:0 745:0 746:0 736:0 750:0 '), +(16,'Stormwind City',0,12,12,1,10,2,-9153.77,364.057,90.151,5.16709,'42:0 710:0 712:0 7:0 '), +(17,'The Sepulcher',0,130,228,8,20,5,470.768,1589.82,126.632,5.16292,'305:0 '), +(18,'Undercity',0,85,153,1,10,5,1885.94,236.924,58.0313,3.1016,'2:0 285:0 '), +(19,'Thelsamar',0,38,144,8,20,3,-5334.7,-3015.26,324.2,1.73643,'503:0 504:0 '), +(20,'Southshore',0,267,271,18,30,3,-803.031,-531.727,15.9656,1.39359,'362:0 363:0 376:0 384:0 385:0 356:0 '), +(21,'Tarren Mill',0,267,272,18,30,5,-27.0354,-900.562,55.9602,4.57301,'378:0 380:0 379:0 384:0 '), +(22,'Refuge Pointe',0,45,320,28,40,3,-1280.81,-2466.89,34.7175,5.16384,'78:0 416:0 419:0 '), +(23,'Hammerfall',0,45,321,28,40,5,-955.494,-3540.82,56.7101,3.23882,'438:0 '), +(24,'Kargath',0,3,340,33,45,5,-6625,-2152.82,249.141,4.5492,'594:0 598:0 '), +(25,'Angor Fortress',0,3,338,33,45,0,-6392.65,-3158,299.765,4.68826,'608:0 620:0 '), +(26,'Bloodhoof Village',1,215,222,1,10,5,-2383.39,-343.878,-0.729101,4.63375,'1817:0 1818:0 1819:0 '), +(27,'Camp Narache',1,215,221,1,10,5,-2886.88,-207.003,54.8208,5.16976,'1825:0 1826:0 '), +(28,'Razor Hill',1,14,362,1,10,0,311.158,-4740.49,9.58476,2.69842,'1401:0 1402:0 1409:0 '), +(29,'The Den',1,14,363,1,10,0,-604.098,-4202.92,38.7281,1.10189,'1431:0 '), +(30,'Orgrimmar',1,14,14,1,10,5,1429.15,-4364.86,25.4626,0.273189,'1361:0 '), +(31,'Senjin Village',1,14,367,1,10,0,-827.631,-4901.78,19.7839,0.842982,'1413:0 1419:0 1420:0 1421:0 1422:0 '), +(32,'The Crossroads',1,17,380,8,25,5,-456.721,-2653.33,95.6449,4.9548,'1487:0 1490:0 1526:0 1535:0 '), +(33,'Aldrassil',1,141,256,1,4,2,10318.8,828.943,1326.38,0.947904,'972:0 62:0 974:0 '), +(34,'Dolanaar',1,141,186,1,10,3,9809.39,956.34,1308.79,0.244923,'981:0 983:0 985:0 '), +(35,'Ratchet',1,17,392,8,25,0,-949.408,-3670.31,9.21524,5.18454,'1475:0 1562:0 1484:0 1618:0 '), +(36,'Booty Bay',0,33,35,33,45,0,-14308.8,440.139,25.5878,0.828079,'959:0 960:0 961:0 '), +(37,'Gromgol Base Camp',0,33,117,33,45,5,-12416.5,185.125,1.83125,6.17782,'904:0 900:0 908:0 '), +(38,'Astranaar',1,331,415,18,30,3,2687.8,-420.338,107.402,0.195851,'1056:0 1055:0 '), +(39,'Stonetalon Peak',1,406,467,18,25,3,2693.45,1481.51,235.304,5.88192,'1692:0 1694:0 1690:0 '), +(40,'Thalanaar',1,357,489,23,35,3,-4496.36,-778.473,-40.558,2.54579,'2014:0 2019:0 2020:0 2021:0 '), +(41,'Freewind Post',1,400,484,23,35,4,-5429.82,-2388.59,89.4787,2.85815,'2104:0 2105:0 '), +(42,'Mirror Lake',0,12,92,1,10,2,-9389.26,458.427,38.2852,2.4529,'72:0 16:0 88:0 7:0 '), +(44,'Drywhisker Gorge',0,45,318,28,40,0,-1014.22,-3827.39,145.41,2.59534,'440:0 '), +(45,'Sun Rock Retreat',1,406,460,13,25,4,946.771,895.865,106.952,1.46258,'1662:0 1663:0 '), +(46,'Temple of Zin-Malor',1,16,1223,43,54,0,3549.15,-5359.12,188.348,1.22969,'1325:0 '), +(48,'Uthers Tomb',0,28,196,48,56,0,1021.5,-1809.43,77.1448,3.5376,'271:0 '), +(50,'Whitereach Post',1,400,2237,23,35,0,-4917.35,-1375.5,-52.612,3.05417,'2034:0 2036:0 2038:0 2040:0 '), +(51,'Stonard',0,8,75,33,45,5,-10445,-3261.11,20.179,5.57373,'828:0 829:0 830:0 '), +(52,'Gadgetzan',1,440,976,38,50,0,-7145.2,-3724,8.49316,4.91643,'2144:0 2146:0 2147:0 '), +(53,'Camp Mojache',1,357,1099,38,50,4,-4402.29,263.941,25.2783,4.51526,'1995:0 1996:0 1997:0 '), +(54,'Theramore Isle',1,15,513,33,45,2,-3702.55,-4393.17,15.8191,0.332538,'1882:0 1883:0 '), +(55,'Aerie Peak',0,47,348,38,50,0,151.003,-2052.21,117.992,6.03287,'82:0 386:0 387:0 388:0 '), +(56,'Everlook',1,618,2255,53,60,0,6726.4,-4671.8,720.854,3.62348,'1218:0 1240:0 '), +(57,'Shadowprey Village',1,405,2408,31,40,5,-1591.76,3150,62.5296,3.57741,'1792:0 '), +(58,'Feathermoon Stronghold',1,357,1116,38,50,3,-4377.97,3297.09,28.7853,5.12409,'1940:0 1941:0 1939:0 '), +(59,'Auberdine',1,148,442,8,20,0,6300.77,315.841,22.9342,5.97913,'993:0 994:0 1008:0 1010:0 1011:0 1012:0 '), +(60,'Nijels Point',1,405,608,28,40,3,164.433,1335.57,197.473,2.41439,'1716:0 '), +(62,'Shadowthread Cave',1,141,257,1,4,2,10749.1,923.327,1336.99,6.25796,'33:0 972:0 973:0 '), +(63,'Fel Rock',1,141,258,1,10,2,10038.9,1030.06,1329.12,0.362337,'983:0 984:0 '), +(64,'Banethil Barrow Den',1,141,736,4,10,2,9972.27,1541.52,1308.75,3.05312,'985:0 986:0 987:0 '), +(66,'DurotarCanyon1_1',1,14,410,1,10,0,622.608,-4563.33,7.7631,0.614446,'1372:0 1373:0 1374:0 '), +(67,'Torkren Farm',1,14,2979,1,10,0,723.247,-4242.04,17.2031,4.54537,'1372:0 1373:0 1385:0 1392:0 1400:0 '), +(69,'Ironbands Compound',0,1,716,1,10,2,-5858.7,-2004.1,401.648,0.478922,'523:0 528:0 '), +(70,'Echo Ridge Mine',0,12,34,1,10,2,-8691.38,-113.152,89.09,5.86134,'73:0 13:0 711:0 '), +(71,'Maclure Vineyards',0,12,64,1,10,2,-9948.39,69.1568,33.3005,5.49869,'72:0 7:0 714:0 716:0 '), +(72,'Stonefield Farm',0,12,63,1,10,2,-9901.14,378.19,35.2801,5.75613,'71:0 7:0 42:0 88:0 713:0 714:0 717:0 '), +(73,'Northshire Vineyards',0,12,59,1,10,2,-9067.35,-333.965,73.4519,1.09469,'70:0 13:0 '), +(74,'Tower of Ilgalar',0,44,96,18,25,0,-9282.21,-3330.43,115.604,1.41034,'683:0 685:0 686:0 '), +(75,'Renders Rock',0,44,998,13,25,0,-8677.89,-2302.91,155.917,1.18325,'661:0 662:0 '), +(76,'Spirit Den',0,33,1742,33,45,0,-13751.4,-18.3165,44.0002,0.851192,'964:0 '), +(77,'Rethban Caverns',0,44,98,13,25,2,-8976.38,-2016.83,136.063,1.26142,'670:0 671:0 '), +(78,'Boulderfist Outpost',0,45,1858,28,40,0,-1183.46,-2180.67,55.5524,1.6849,'22:0 415:0 416:0 419:0 '), +(79,'Groshgok Compound',0,41,2937,50,60,0,-11094.7,-2311.06,117.13,4.77931,'800:0 803:0 804:0 '), +(80,'Stagalbog Cave',0,8,1817,33,45,0,-10775.6,-3747.85,22.347,3.74768,'186:0 825:0 824:0 826:0 828:0 832:0 '), +(81,'The Tower of Arathor',0,45,324,28,40,0,-1760.68,-1537.05,64.8696,2.02383,'425:0 '), +(82,'Wildhammer Keep',0,47,349,38,50,3,250.073,-2203.83,125.172,0.474207,'55:0 387:0 388:0 '), +(83,'Night Webs Hollow',0,85,155,1,10,4,2053.22,1802.16,99.8006,1.83635,'295:0 '), +(84,'Terrorweb Tunnel',0,139,2626,53,60,0,3035.33,-2773.9,100.473,5.05065,'215:0 243:0 '), +(85,'Browman Mill',0,139,2269,53,60,0,2475.41,-5143.98,77.5041,5.20031,'234:0 211:0 '), +(86,'Uldaman',0,3,1337,33,45,0,-6092.01,-3179.35,255.852,5.9237,'621:0 '), +(87,'Dustbelch Grotto',0,3,347,33,45,0,-7299.86,-2270.26,244.599,3.07308,'595:0 '), +(88,'Westbrook Garrison',0,12,120,1,10,2,-9671.96,690.134,36.5414,5.31336,'72:0 42:0 713:0 7:0 717:0 '), +(89,'Jasperlode Mine',0,12,54,1,10,0,-9194.18,-610.205,60.7892,0.426731,'701:0 702:0 707:0 708:0 '), +(91,'Algaz Station',0,38,925,8,20,0,-4880.26,-2723,328.908,0.873682,'485:0 486:0 487:0 500:0 502:0 '), +(92,'Grim Batol',0,11,1037,61,70,0,-4130,-3468.16,259.76,0.014411,'454:0 '), +(93,'Venture Co. Mine',1,215,215,1,10,4,-1466.09,-994.107,148.38,4.09562,'1868:0 '), +(94,'Blackwood Den',1,148,455,8,20,0,4626.07,40.557,68.8626,1.32329,'95:0 999:0 1003:0 1006:0 '), +(95,'WindbendRiverS',1,148,454,8,20,0,5014.05,224.988,33.2141,0.194282,'996:0 999:0 1005:0 94:0 1006:0 '), +(96,'Cliffspring Falls',1,148,445,8,20,0,6878.37,-656.215,84.4074,3.85022,'1019:0 1020:0 1021:0 '), +(98,'Wailing Caverns',1,17,718,8,25,0,-868.365,-2044.69,81.6627,1.16842,'1539:0 1540:0 1557:0 1614:0 '), +(99,'Shady Rest Inn',1,15,403,33,45,0,-3695.7,-2558.8,61.0548,1.48476,'1872:0 1873:0 1904:0 '), +(100,'Darkmist Cavern',1,15,499,33,45,0,-2829.79,-2722.81,36.7883,2.20851,'187:0 102:0 1902:0 '), +(101,'North Point Tower',1,15,504,33,45,2,-2869.79,-3419.99,39.3517,3.30334,'102:0 1875:0 1877:0 1878:0 1902:0 '), +(102,'Bluefen',1,15,507,33,45,0,-2695.87,-3011.4,41.88,2.01726,'100:0 187:0 1874:0 101:0 1875:0 1902:0 '), +(103,'Lost Point',1,15,506,33,45,0,-3917.65,-2833.95,42.7963,4.55319,'1873:0 1904:0 1907:0 1908:0 1909:0 '), +(104,'Tidefury Cove',1,15,517,33,45,0,-4414.52,-4100.84,6.37542,0.28057,'106:0 1912:0 '), +(105,'Stonemaul Ruins',1,15,508,33,45,0,-4346.02,-3321.15,34.2542,6.16796,'106:0 233:0 1906:0 1907:0 1909:0 '), +(106,'Onyxias Lair',1,15,511,33,45,0,-4638.56,-3708.98,38.6239,2.33441,'233:0 105:0 1910:0 1911:0 104:0 '), +(107,'Emberstrifes Den',1,15,2158,33,45,0,-4956.66,-3850.19,43.6067,2.10466,'1910:0 1911:0 '), +(108,'Bloodfen Burrow',1,15,498,33,45,0,-4335.04,-2639.53,38.0638,1.30845,'1908:0 1909:0 '), +(109,'Sentry Point',1,15,503,33,45,0,-3410.26,-4186.35,10.7187,3.27073,'1881:0 1882:0 '), +(110,'Swamplight Manor',1,15,497,33,45,0,-2949.47,-3893.5,35.0303,1.23141,'1877:0 1878:0 1879:0 1880:0 '), +(111,'Marshals Refuge',1,490,541,46,56,1,-6112.07,-1130.95,-187.426,1.5835,'2245:0 2246:0 '), +(112,'Fungal Rock',1,490,542,46,56,0,-6383.04,-1806.79,-266.123,6.01903,'2249:0 2250:0 2251:0 '), +(113,'Wavestrider Beach',1,440,988,38,50,0,-7619.63,-4828.43,0.668867,3.36332,'2157:0 2158:0 2159:0 2160:0 '), +(114,'Uldum',1,440,989,44,55,0,-9473.22,-2749.02,15.4728,5.89777,'2193:0 2194:0 2195:0 '), +(115,'Darkcloud Pinnacle',1,400,2097,23,35,0,-5086.21,-1919.44,88.1806,6.15461,'2071:0 2077:0 2078:0 '), +(116,'Roguefeather Den',1,400,487,23,35,0,-5462.36,-1633.26,30.0036,4.16001,'2056:0 2057:0 '), +(117,'Mirage Raceway',1,400,2240,23,35,0,-6239.42,-3973.12,-58.7501,5.06259,'122:0 123:0 2136:0 2137:0 118:0 2138:0 '), +(118,'The Rustmaul Digsite',1,400,479,23,35,0,-6490.61,-3449.15,-58.7821,3.58106,'117:0 123:0 2138:0 2139:0 '), +(119,'Splithoof Hold',1,400,1557,23,35,0,-5071.33,-2349.27,-53.6634,5.48527,'2084:0 2094:0 2112:0 '), +(121,'Ironstone Camp',1,400,3037,23,35,0,-5813.18,-3421.49,-50.9325,2.44381,'2131:0 2133:0 122:0 2134:0 2138:0 '), +(122,'Weazels Crater',1,400,3038,23,35,0,-5880.19,-3796.15,-59.9445,0.25844,'121:0 2133:0 2134:0 2136:0 117:0 2138:0 '), +(123,'Tahonda Ruins',1,400,3039,23,35,0,-6569.91,-3894.97,-58.7495,0.100017,'117:0 2137:0 118:0 2138:0 2140:0 '), +(124,'Rolands Doom',0,10,2161,24,30,0,-11045.3,-1130.98,38.585,3.65547,'780:0 781:0 '), +(127,'Maraudon',1,405,2100,33,40,0,-1421.88,2905.15,136.781,1.31624,'1786:0 1787:0 '), +(128,'Ghost Walker Post',1,405,597,28,40,4,-1258.05,1699.26,89.9098,0.914953,'1765:0 1766:0 1769:0 1776:0 '), +(129,'Scrabblescrews Camp',1,405,2617,31,40,0,-1407.87,1493.13,60.8875,4.75641,'1749:0 1750:0 1764:0 1769:0 1770:0 1777:0 1778:0 '), +(130,'Valley of Bones',1,405,2657,33,40,0,-2323.83,1355.6,63.5887,1.00444,'1812:0 1813:0 '), +(131,'Rage Scar Hold',1,357,1115,38,50,0,-3839.01,1752.22,143.122,4.59397,'1927:0 1928:0 '), +(132,'Ruins of Solarsal',1,357,1117,38,50,2,-4932.61,3653.2,12.2193,0.301741,'1940:0 1941:0 140:0 '), +(133,'Ravaged Twilight Camp',1,1377,3100,53,60,0,-6206.25,1766.95,17.464,4.2566,'2318:0 2319:0 '), +(134,'Twilight Post',1,1377,3098,55,60,0,-6753.94,1661.67,6.34336,0.541008,'2319:0 2320:0 2321:0 2322:0 '), +(135,'Twilight Outpost',1,1377,3099,55,60,0,-7929.11,1833.28,4.86506,0.733634,'2336:0 2337:0 2338:0 '), +(136,'Woodpaw Hills',1,357,2519,38,50,0,-4867.47,196.488,57.1639,3.31322,'1991:0 1992:0 '), +(137,'Lariss Pavillion',1,357,2518,38,50,0,-4097.42,96.644,76.2811,5.8877,'2004:0 2006:0 2007:0 '), +(138,'Slither Rock',0,46,2419,48,56,0,-7653.86,-2991.1,135.917,1.62615,'648:0 650:0 652:0 '), +(139,'Flame Crest',0,46,251,48,56,5,-7486.64,-2184.45,166.505,5.87909,'646:0 '), +(140,'Shalzarus Lair',1,357,3117,38,50,0,-5418.89,3677.45,4.7662,3.81836,'132:0 1942:0 '), +(142,'Timbermaw Hold',1,618,618,53,60,0,6889.31,-2300.49,584.754,3.03017,'1196:0 1202:0 '), +(143,'The Ruins of KelTheril',1,618,2252,53,60,0,6430.1,-4328.62,666.714,0.935105,'1216:0 '), +(144,'Dun Mandarr',1,618,2248,53,60,0,5667.17,-4495.06,769.144,0.689558,'1255:0 1258:0 1259:0 1260:0 '), +(145,'Talrendis Point',1,16,3137,43,54,3,2689.78,-3854.8,103.228,5.51976,'1270:0 '), +(147,'Moon Horror Den',1,618,3139,53,60,0,7122.72,-4596.74,637.499,3.77862,'1225:0 '), +(148,'Timbermaw Hold',1,361,1769,46,56,0,6811.82,-2090.36,625.019,5.71392,'1148:0 1196:0 '), +(149,'Irontree Cavern',1,361,1768,46,56,0,6464.49,-1502.77,438.462,4.97283,'1142:0 1143:0 1183:0 1184:0 '), +(150,'Bloodvenom Post',1,361,1997,46,56,5,5070.53,-333.931,367.077,5.98328,'1164:0 '), +(151,'Sishir Canyon',1,406,2541,13,25,0,599.482,622.34,74.9622,3.1924,'1645:0 '), +(152,'Cragpool Lake',1,406,463,13,25,0,1491.34,85.8624,11.9254,3.12763,'1649:0 1650:0 1651:0 1652:0 '), +(153,'Windshear Mine',1,406,461,13,25,0,981.949,-358.9,14.3123,3.71479,'1648:0 1654:0 1655:0 '), +(154,'The Talondeep Path',1,406,1277,13,25,0,1531.94,-576.57,67.9212,5.18318,'1653:0 1655:0 168:0 '), +(155,'The Talon Den',1,406,468,18,25,4,2416.89,1792.39,393.641,3.36062,'1692:0 1693:0 '), +(156,'The Ruins of OrdilAran',1,331,412,18,30,0,3476.85,-104.536,2.98631,5.31466,'157:0 1029:0 1031:0 1033:0 '), +(157,'Bathrans Haunt',1,331,411,18,30,0,3827.37,-161.305,-0.60768,3.24941,'1028:0 156:0 '), +(158,'Zoramgar Outpost',1,331,2897,18,30,5,3362.22,1010.16,3.59814,2.49558,'1035:0 1036:0 1037:0 '), +(159,'Falfarren River',1,331,433,18,30,0,2247.11,-2187.63,105.176,0.680699,'1078:0 1079:0 '), +(160,'Xavian',1,331,429,18,30,0,2931.82,-2808,212.839,0.056247,'1080:0 1081:0 1085:0 '), +(161,'Forest Song',1,331,2358,18,30,3,2936.03,-3270.73,159.15,3.69459,'1085:0 1091:0 1093:0 '), +(162,'Demon Fall Canyon',1,331,435,18,30,0,1708.23,-3157.86,94.4801,5.70126,'1099:0 1100:0 1104:0 '), +(163,'Silverwing Outpost',1,331,2360,18,30,0,1776.04,-2061.14,106.556,1.50326,'1109:0 '), +(164,'The DorDanil Barrow Den',1,331,432,18,30,0,1776.92,-2583.5,85.9882,3.82761,'169:0 1113:0 1115:0 1117:0 '), +(165,'Silverwind Refuge',1,331,420,18,30,0,2141.6,-1189.65,96.7733,3.54353,'1063:0 1064:0 1066:0 '), +(166,'Greenpaw Village',1,331,2359,18,30,0,2274.68,-1460.01,90.2583,4.48209,'1066:0 1068:0 1069:0 '), +(167,'Bloodtooth Camp',1,331,2357,18,30,0,1668.68,-1469.31,140.228,5.79562,'1067:0 '), +(168,'The Talondeep Path',1,406,1277,13,25,0,1941.31,-740.96,113.545,2.12791,'154:0 1063:0 '), +(169,'Warsong Labor Camp',1,331,3177,18,30,0,1575.91,-2463.94,98.549,2.24558,'164:0 1110:0 '), +(170,'Aridens Camp',0,41,2560,50,60,0,-10443.3,-2141.1,90.7796,5.92189,'801:0 '), +(171,'Deadwind Ravine',0,41,2558,50,60,0,-10607.9,-1904.89,117.201,2.66188,'798:0 799:0 '), +(172,'Sleeping Gorge',0,41,2938,50,60,0,-10740.7,-1951.45,121.127,3.69664,'800:0 801:0 '), +(173,'Chillwind Camp',0,28,3197,48,56,3,940.955,-1419.2,66.7723,0.796556,'271:0 272:0 334:0 '), +(174,'Camp Taurajo',1,17,378,8,25,5,-2353.97,-1913.23,95.7826,0.204344,'1550:0 1551:0 1553:0 1554:0 1584:0 1585:0 1814:0 '), +(175,'Splintertree Post',1,331,431,18,30,5,2251.83,-2538.3,90.4185,6.01297,'1113:0 1114:0 '), +(177,'Bones of Grakkarond',1,1377,3257,53,60,0,-7228.09,854.674,-1.38994,1.38965,'2323:0 2324:0 2335:0 2343:0 2348:0 2349:0 2350:0 '), +(178,'Woodpaw Den',1,357,2520,38,50,0,-4775.84,905.452,142.986,4.17326,'1980:0 '), +(179,'Revantusk Village',0,47,3317,38,50,5,-573.459,-4590.51,10.4122,3.48476,'407:0 '), +(180,'Thorium Point',0,51,1446,43,54,0,-6521.12,-1190.02,309.255,4.39589,'590:0 '), +(183,'The Weeping Cave',0,28,198,48,56,0,2249.61,-2389.63,59.8017,5.27414,'244:0 255:0 256:0 '), +(184,'Valors Rest',1,1377,3077,50,60,0,-6382.38,-308.522,-1.89701,4.0769,'2292:0 2293:0 '), +(185,'Yojamba Isle',0,33,3357,33,45,0,-11838,1268.18,1.74176,4.79629,'894:0 887:0 895:0 896:0 '), +(186,'Misty Reed Post',0,8,1978,33,45,4,-10854.8,-4093.43,21.7429,5.03063,'825:0 80:0 826:0 '), +(187,'Brackenwall Village',1,15,496,33,45,5,-3113.73,-2860.5,34.4097,2.94207,'1873:0 100:0 102:0 '), +(188,'Cenarion Hold',1,1377,3425,53,60,3,-6886.15,718.398,42.798,6.10305,'2305:0 2306:0 '), +(189,'Twilight Base Camp',1,1377,2739,53,60,0,-6969.25,1167.66,12.8483,1.06764,'2322:0 2323:0 2324:0 2325:0 2335:0 '), +(191,'Twilights Run',1,1377,3446,55,60,0,-6332.81,164.268,6.01109,2.33216,'2295:0 2296:0 2297:0 2299:0 '), +(192,'Ortells Hideout',1,1377,2744,53,60,0,-7601.34,285.736,2.83228,0.367888,'2349:0 2357:0 2358:0 '), +(193,'Bronzebeard Encampment',1,1377,3427,53,60,0,-7993.44,1118.16,-1.76652,1.00873,'2339:0 2340:0 2341:0 2344:0 2347:0 2346:0 '), +(196,'BLC1',0,4,4,43,54,0,-11202.2,-3034.46,6.232,1.13173,'839:0 841:0 845:0 852:0 854:0 '), +(199,'Under Attack - Eastern Plaguelands',0,139,2258,53,60,0,2492.32,-3803.41,177.692,4.09879,'248:0 '), +(200,'Ivars Patch',0,130,239,8,20,4,1233.05,1214.28,52.5845,3.07876,'201:0 298:0 299:0 303:0 '), +(201,'Valgans Field',0,130,227,8,20,4,908.754,1255.63,45.9684,0.575633,'299:0 302:0 303:0 200:0 '), +(211,'Eastwall Tower - Horde',0,139,2271,53,60,0,2550.77,-4783.57,109.501,5.47479,'85:0 235:0 237:0 250:0 '), +(215,'Plaguewood Tower - Horde, Progressing',0,139,4067,53,60,0,2991.34,-3045.27,119.143,2.65173,'84:0 241:0 242:0 '), +(220,'Northpass Tower - Horde, Contested',0,139,2275,53,60,0,3168.41,-4356.58,138.976,4.89792,'236:0 237:0 238:0 '), +(230,'Crown Guard Tower - Alliance',0,139,2263,53,60,0,1861.6,-3701.08,160.834,4.34226,'248:0 247:0 249:0 2369:0 '), +(233,'Mudsprocket',1,15,4010,33,45,1,-4601.09,-3173.17,38.4677,3.21013,'1909:0 106:0 105:0 1911:0 '), +(234,'LightsHopeChapel',0,139,2268,53,60,1,2207.38,-5321.35,92.2225,0.203619,'85:0 252:0 '), +(235,'BlackwoodLake',0,139,2624,53,60,0,2464.31,-4192.32,86.7625,5.25334,'211:0 236:0 250:0 '), +(236,'EPL_hubN1',0,139,139,53,60,0,2895.84,-4274.37,91.0774,2.94428,'220:0 235:0 237:0 240:0 '), +(237,'Northdale',0,139,2272,53,60,0,2939.45,-4922.8,110.201,2.76758,'236:0 211:0 220:0 238:0 '), +(238,'ZulMasharEntrance',0,139,2273,53,60,0,3243.57,-4728.42,157.177,1.67982,'237:0 220:0 239:0 '), +(239,'MazraAlor',0,139,2274,53,60,0,3446.77,-4987,196.046,5.53613,'238:0 '), +(240,'PlaguewoodEast',0,139,2277,53,60,0,3031.09,-3786.79,119.967,4.37961,'236:0 241:0 242:0 '), +(241,'PlaguewoodSouth',0,139,2277,53,60,0,2764.18,-3442.19,97.1331,4.24216,'215:0 240:0 242:0 '), +(242,'PlaguewoodCenter',0,139,2277,53,60,0,3137.26,-3403.95,139.517,0.937594,'215:0 240:0 241:0 '), +(243,'TerrorweTunnelWest',0,139,2619,53,60,0,2724.41,-2453.2,66.836,5.42496,'244:0 84:0 '), +(244,'ThondorilRiverSouth',0,139,2619,53,60,0,2417.75,-2469.65,72.4504,0.00296164,'183:0 243:0 245:0 '), +(245,'EPL_hubSW',0,139,139,53,60,0,2076.2,-2853.69,86.3391,0.879086,'244:0 246:0 248:0 255:0 '), +(246,'EPL_hubSWRoad',0,139,139,53,60,0,1811.3,-3046.98,75.4985,1.09939,'245:0 247:0 255:0 '), +(247,'Undercroft',0,139,2261,53,60,0,1600.37,-3283.08,91.4858,0.567285,'246:0 230:0 '), +(248,'EPL_hubSW2',0,139,139,53,60,0,2175.83,-3487.76,123.024,5.90406,'199:0 230:0 245:0 '), +(249,'EPL_SRoad',0,139,139,53,60,0,1841.76,-4088.5,101.961,5.05584,'230:0 250:0 '), +(250,'CorinsCrossing',0,139,2264,53,60,0,2074.45,-4556.23,73.5772,0.745981,'211:0 235:0 249:0 251:0 252:0 '), +(251,'ScarletBaseCamp',0,139,2265,53,60,0,1656.05,-4826.35,87.7738,0.384695,'250:0 252:0 253:0 '), +(252,'PestilentScar',0,139,2622,53,60,0,2013.6,-4964.85,73.6116,5.58402,'234:0 250:0 251:0 253:0 '), +(253,'TyrshandEntrance',0,139,2266,53,60,0,1690.77,-5201.46,74.6226,4.83788,'251:0 252:0 254:0 '), +(254,'Tyrshand',0,139,2266,53,60,0,1609.91,-5528.27,111.168,4.2076,'253:0 '), +(255,'ThondorilRiverBridge',0,139,2619,53,60,0,1924.72,-2608.2,62.8109,4.6761,'183:0 245:0 246:0 256:0 '), +(256,'GahrronWithering',0,28,201,48,56,0,1768.07,-2280.14,59.7087,0.010807,'183:0 255:0 257:0 258:0 '), +(257,'WritingHaunt',0,28,202,48,56,0,1506.64,-1862.76,59.0986,5.271,'256:0 258:0 259:0 266:0 269:0 '), +(258,'WPL_hubC2',0,28,28,48,56,0,1760.12,-1779.45,64.8245,4.87437,'256:0 257:0 259:0 260:0 266:0 269:0 '), +(259,'DalsonTears',0,28,200,48,56,0,1864.12,-1558.81,59.2668,3.44495,'257:0 258:0 260:0 266:0 267:0 269:0 '), +(260,'WPL_hubN1',0,28,28,48,56,0,2122.47,-1665.81,64.0458,5.36013,'258:0 259:0 261:0 262:0 '), +(261,'NorthridgeLumberCamp',0,28,192,48,56,0,2421.88,-1647.26,103.541,5.77835,'260:0 '), +(262,'WPL_hubN2',0,28,28,48,56,0,2425.99,-1947.45,109.098,4.71217,'260:0 263:0 '), +(263,'HearthglenTower',0,28,28,48,56,0,2701.43,-1944.23,107.238,0.832291,'262:0 265:0 '), +(264,'Hearthglen',0,28,203,48,56,0,2924.12,-1426.31,150.782,1.09736,'265:0 '), +(265,'HearthglenEntrance',0,28,190,48,56,0,2782.62,-1612.54,129.551,0.826396,'264:0 263:0 '), +(266,'WPL_hubC1',0,28,28,48,56,0,1680.03,-1358.64,69.8578,5.02985,'268:0 257:0 258:0 259:0 267:0 269:0 270:0 274:0 '), +(267,'FelstoneField',0,28,199,48,56,0,1795.08,-1188.53,59.8914,5.54821,'268:0 259:0 266:0 269:0 273:0 274:0 '), +(268,'AndorhalW',0,28,193,48,56,0,1336.99,-1272.01,57.8614,2.26525,'266:0 267:0 269:0 270:0 272:0 '), +(269,'AndorhalNE',0,28,193,48,56,0,1540.11,-1606.19,65.1216,5.937,'257:0 258:0 259:0 266:0 267:0 268:0 270:0 '), +(270,'AndorhalEntranceSE',0,28,197,48,56,0,1294.98,-1678.78,62.5727,3.83801,'268:0 266:0 269:0 271:0 '), +(271,'SorrowHillCR',0,28,197,48,56,0,1162.78,-1758.3,60.6308,3.04279,'48:0 173:0 270:0 334:0 '), +(272,'AndorhalEntranceSW',0,28,28,48,56,0,1214.59,-1145.63,60.8962,5.48342,'268:0 173:0 334:0 '), +(273,'Bulwark',0,28,813,48,56,5,1718.17,-802.509,57.5466,1.68044,'267:0 274:0 275:0 276:0 '), +(274,'WPL_hubE',0,28,813,48,56,0,1663.85,-956.731,69.3084,0.651574,'266:0 267:0 273:0 '), +(275,'TG_hubSE',0,85,85,1,10,4,1806.63,-369.504,32.3876,6.02566,'273:0 276:0 277:0 '), +(276,'BalnirFarmstead',0,85,165,6,10,4,2029.54,-432.459,35.4011,5.30269,'273:0 275:0 277:0 '), +(277,'TG_hubC2',0,85,85,1,10,4,2029.86,-87.8105,35.5077,4.49765,'2:0 275:0 276:0 '), +(278,'BrillEntrance',0,85,159,1,10,4,2296.05,429.197,35.7333,5.74799,'2:0 279:0 285:0 297:0 '), +(279,'GarrensHauntGraves',0,85,85,4,10,4,2599.87,521.024,17.2769,5.97968,'2:0 278:0 280:0 281:0 '), +(280,'GarrensHaunt',0,85,164,6,10,4,2883.52,376.589,25.8553,5.87914,'279:0 281:0 284:0 '), +(281,'BrightwaterLake',0,85,85,1,10,4,2634.44,79.5244,31.089,6.13244,'2:0 279:0 280:0 282:0 284:0 '), +(282,'NorthCoastE',0,85,168,7,10,4,3032.54,-341.266,5.44748,2.52746,'281:0 283:0 '), +(283,'NorthCoastC',0,85,168,1,10,4,2935.15,41.7046,6.92148,4.82869,'282:0 284:0 '), +(284,'NorthCoastW',0,85,168,1,10,4,2984.21,388.32,7.96326,4.49688,'280:0 281:0 283:0 '), +(285,'ColdHearthManor',0,85,166,1,10,4,2107.98,617.879,35.0405,0.497239,'278:0 18:0 286:0 287:0 297:0 '), +(286,'TG_hubC1',0,85,85,1,10,0,2204.7,1063.28,28.6853,4.1572,'285:0 287:0 288:0 292:0 294:0 '), +(287,'CrusadersOutpost',0,85,85,1,10,0,1797.65,703.831,48.147,1.54771,'286:0 285:0 297:0 '), +(288,'TG_hubNE',0,85,85,1,10,4,2446.9,1082.07,58.5203,0.911539,'286:0 289:0 292:0 293:0 294:0 '), +(289,'AgamandMillsEntrance',0,85,157,4,10,4,2701.33,937.571,110.912,0.173269,'288:0 290:0 291:0 '), +(290,'AgamandMillsW',0,85,157,6,10,4,2889.98,1065.92,105.434,5.38438,'289:0 291:0 '), +(291,'AgamandMillsE',0,85,157,6,10,4,2973.71,619.932,93.8373,1.31013,'289:0 290:0 '), +(292,'SollidenFarmstead',0,85,156,1,10,4,2329.03,1407.79,33.3337,0.622907,'286:0 288:0 293:0 294:0 '), +(293,'CrusadersOutpost',0,85,85,1,10,4,2430.28,1585.02,37.0619,0.587566,'288:0 292:0 '), +(294,'UndeadStartExit',0,85,85,5,10,4,2197.51,1192.39,31.5497,5.11342,'288:0 286:0 292:0 295:0 '), +(295,'UndeadStartEnd',0,85,154,1,10,4,2061.91,1418.99,63.8379,5.30978,'294:0 83:0 4:0 296:0 '), +(296,'UndeadStartE',0,85,154,1,10,4,1805.3,1351.42,87.1354,0.232168,'295:0 4:0 '), +(297,'TH_exitS',0,85,85,7,12,4,1642.09,555.658,33.4922,2.88878,'278:0 285:0 287:0 298:0 '), +(298,'ShiningStrandN',0,130,927,8,14,4,1331.69,679.903,40.5373,1.02387,'297:0 200:0 '), +(299,'DeadField',0,130,240,8,20,4,1076.06,1545.35,28.6412,4.79771,'201:0 300:0 301:0 302:0 303:0 200:0 '), +(300,'SkitteringDark',0,130,226,8,20,4,1271.34,1974.49,17.8502,4.31862,'299:0 301:0 '), +(301,'NorthTidesHollow',0,130,305,8,20,4,833.25,1880.5,21.9868,5.06279,'299:0 300:0 '), +(302,'SPF_hubC1',0,130,130,8,20,4,867.729,1518.72,35.2045,0.160734,'201:0 299:0 303:0 304:0 '), +(303,'SPF_hubC2',0,130,130,8,20,4,934.532,1356.51,43.5579,3.0887,'201:0 299:0 302:0 200:0 305:0 '), +(304,'SPF_hubC3',0,130,130,8,20,4,736.764,1449.26,64.4284,0.457609,'302:0 305:0 306:0 '), +(305,'SPF_hubC4',0,130,130,8,20,4,587.903,1352.93,90.6192,0.359403,'303:0 304:0 306:0 17:0 313:0 '), +(306,'DecrepitFerry',0,130,237,13,20,4,664.305,1021.48,45.3265,5.10911,'304:0 305:0 307:0 '), +(307,'FenrisIsle',0,130,172,15,20,4,704.961,674.359,43.3624,1.57874,'306:0 308:0 311:0 '), +(308,'FenrisIsleSE',0,130,172,15,20,4,653.059,335.097,35.0481,1.33722,'307:0 309:0 '), +(309,'FenrisIsleE',0,130,232,16,20,4,866.03,78.8803,34.2361,2.23258,'308:0 310:0 '), +(310,'DawningIsles',0,130,232,17,22,4,1199.57,370.42,34.3257,3.13185,'309:0 '), +(311,'FenrisKeep',0,130,172,8,20,4,1013.6,734.689,59.2651,3.23986,'307:0 '), +(312,'DeepElemMineFork',0,130,213,8,20,4,271.231,1110.33,80.2136,5.99661,'313:0 314:0 317:0 318:0 '), +(313,'SPF_hubC5',0,130,130,8,20,4,385.877,1253.37,80.2878,0.320144,'305:0 312:0 315:0 '), +(314,'DeepElemMine',0,130,213,8,20,4,376.82,1082.54,106.396,3.03959,'312:0 '), +(315,'SPF_hubC6',0,130,130,8,20,4,194.263,1268.01,72.8331,6.23812,'313:0 316:0 317:0 318:0 '), +(316,'OlsensFarthing',0,130,229,8,20,4,171.155,1487.65,114.395,4.84404,'315:0 '), +(317,'SPF_DalaranCamp1',0,130,130,8,20,4,-50.4703,1331.43,60.9321,5.92005,'315:0 312:0 318:0 319:0 323:0 '), +(318,'SPF_hubS1',0,130,130,8,20,4,-132.172,1170.25,63.4384,0.508657,'315:0 317:0 312:0 319:0 323:0 326:0 '), +(319,'AmberhillEntrance',0,130,233,11,20,4,-131.822,896.74,65.831,1.54539,'317:0 318:0 320:0 321:0 323:0 325:0 '), +(320,'AmberhillHall',0,130,233,13,20,4,-141.972,812.61,63.737,1.09457,'319:0 '), +(321,'AmberhillFarms',0,130,233,15,20,4,88.1028,694.265,60.6053,2.4376,'322:0 319:0 '), +(322,'AmberhillMurlocCamp',0,130,130,15,20,4,434.265,696.251,33.7837,3.19747,'321:0 '), +(323,'SPF_hubS2',0,130,130,8,20,4,-330.828,1311.68,37.1012,1.70757,'317:0 318:0 319:0 324:0 326:0 327:0 '), +(324,'PyrewoodVillage',0,130,204,8,20,4,-383.772,1597.45,16.8392,4.90611,'323:0 '), +(325,'SPF_exitSE',0,130,130,15,20,4,-543.136,724.877,91.2236,0.975962,'319:0 326:0 357:0 '), +(326,'SPF_hubS3',0,130,130,8,20,4,-503.287,1113.31,76.93,0.514545,'318:0 323:0 325:0 '), +(327,'GreymaneWall',0,130,230,8,20,4,-744.895,1522.56,15.5245,5.84742,'323:0 '), +(328,'DandredsFold',0,36,1682,35,40,0,1235.47,-274.821,40.3856,4.48552,'329:0 '), +(329,'UplandsN',0,36,284,28,40,0,1085.27,-663.59,87.8456,1.24773,'328:0 330:0 '), +(330,'UplandsS',0,36,284,28,40,0,958.693,-748.597,114.797,4.04964,'329:0 331:0 335:0 '), +(331,'Strahnbard',0,36,280,28,40,0,683.909,-963.157,164.301,0.671641,'330:0 332:0 343:0 '), +(332,'AM_hubE1',0,36,36,28,40,0,615.654,-1043.7,168.11,0.331968,'331:0 333:0 381:0 '), +(333,'AM_hubE2',0,36,1684,36,42,0,641.277,-1429.39,84.6042,5.55291,'332:0 334:0 '), +(334,'ChillwindCampRoad',0,28,3197,48,56,0,966.365,-1511.29,75.4645,2.81971,'173:0 271:0 272:0 333:0 '), +(335,'SlaughterHollowNE',0,36,283,28,40,0,802.9,-628.854,149.733,3.01018,'330:0 336:0 337:0 '), +(336,'SlaughterHollowN',0,36,283,28,40,0,835.191,-445.445,134.228,4.48673,'335:0 '), +(337,'SlaughterHollowC',0,36,281,28,40,0,620.637,-522.586,179.928,5.73159,'335:0 338:0 343:0 '), +(338,'RuinsOfAlterac',0,36,281,28,40,0,631.058,-371.491,154.481,2.42898,'340:0 337:0 339:0 '), +(339,'RuinsOfAlteracSW',0,36,281,28,40,0,500.776,-173.116,151.449,5.19357,'340:0 338:0 341:0 '), +(340,'RuinsOfAlteracSE',0,36,281,28,40,0,397.344,-248.661,161.727,5.94755,'338:0 339:0 '), +(341,'RuinsOfAlteracE',0,36,281,28,40,0,573.088,-14.3215,142.366,3.66008,'339:0 342:0 '), +(342,'RuinsOfAlteracSSW',0,36,1683,28,40,0,252.323,-82.4394,141.544,0.263228,'341:0 344:0 '), +(343,'RuinsOfAlteracE',0,36,1357,28,40,0,493.629,-621.553,172.877,0.602885,'331:0 337:0 344:0 345:0 380:0 '), +(344,'RuinsOfAlteracS',0,36,1683,28,40,0,141.788,-316.452,150.648,1.12125,'342:0 343:0 346:0 '), +(345,'CorrahnsDagger',0,36,1679,28,40,0,-45.3608,-581.074,153.724,5.82187,'343:0 346:0 380:0 '), +(346,'CorrahnsDaggerFork',0,36,1679,28,40,0,57.1052,-405.389,132.108,2.70582,'344:0 345:0 347:0 385:0 '), +(347,'Headland',0,36,1680,28,40,0,-163.833,-293.019,151.411,0.123425,'346:0 348:0 '), +(348,'GavinsNaze',0,36,1677,28,40,0,-63.0062,-197.056,131.24,2.72704,'347:0 349:0 350:0 355:0 '), +(349,'GavinsNazeTop',0,36,1677,28,40,0,-125.184,-59.9939,147.694,5.63693,'348:0 350:0 '), +(350,'GavinsNazeW',0,36,279,28,40,0,68.1015,-53.44,99.6287,3.99152,'348:0 349:0 351:0 352:0 353:0 '), +(351,'DalaranCraterN',0,36,279,28,40,4,466.751,167.322,41.9053,3.38088,'350:0 352:0 '), +(352,'DalaranCraterS',0,36,279,28,40,4,90.2654,237.935,43.2902,5.2894,'351:0 350:0 353:0 354:0 '), +(353,'LordamereInternmentCamp',0,36,278,28,40,4,-90.5356,218.366,53.2755,6.11997,'352:0 350:0 354:0 '), +(354,'HillsbradFieldsC',0,267,286,18,30,4,-499.681,78.0454,56.6165,0.278575,'352:0 353:0 356:0 355:0 '), +(355,'HillsbradFieldsE',0,267,286,18,30,0,-437.039,-135.243,56.2029,2.13409,'354:0 356:0 348:0 385:0 '), +(356,'HillsbradFieldsSRoad',0,267,267,18,30,0,-639.845,-98.2014,47.262,0.172558,'20:0 358:0 360:0 354:0 355:0 361:0 385:0 357:0 '), +(357,'SouthPointTower',0,267,285,18,30,5,-613.976,388.915,83.0604,2.91123,'325:0 356:0 358:0 '), +(358,'WesternStrandW',0,267,295,18,30,0,-979.029,273.179,7.90667,5.62479,'356:0 359:0 357:0 '), +(359,'WesternStrandSW',0,267,295,18,30,0,-1128.84,276.704,0.0000662804,5.89576,'358:0 360:0 '), +(360,'WesternStrandS',0,267,295,18,30,0,-1156.55,7.61478,0.00000548363,6.16081,'359:0 356:0 361:0 '), +(361,'WesternStrandS',0,267,295,18,30,0,-1102.38,-114.03,0.0000309944,0.148599,'362:0 360:0 356:0 '), +(362,'WesternStrandE',0,267,271,18,30,0,-962.262,-506.545,2.13023,1.90396,'361:0 363:0 20:0 '), +(363,'SouthshoreS',0,267,271,18,30,0,-974.276,-592.998,0.229758,0.372044,'362:0 364:0 20:0 '), +(364,'EasternStrandW',0,267,294,18,30,0,-1012.85,-790.213,9.01406,5.30042,'363:0 365:0 368:0 376:0 '), +(365,'EasternStrandC',0,267,294,18,30,0,-1183.14,-847.579,1.43171,6.18988,'364:0 366:0 '), +(366,'EasternStrandS',0,267,294,18,30,0,-1311.64,-1052.47,18.2271,0.929683,'365:0 367:0 368:0 '), +(367,'DunGarok',0,267,290,18,30,4,-1256.01,-1190.37,38.9786,2.03514,'366:0 368:0 376:0 '), +(368,'DunGarokRoad',0,267,267,18,30,0,-1049.49,-1239.84,53.3175,1.75045,'364:0 366:0 367:0 369:0 373:0 '), +(369,'HillsbradFoothillsExitSE',0,267,267,18,30,0,-784.955,-1512.45,56.6724,1.97822,'370:0 368:0 373:0 410:0 '), +(370,'HillsbradFoothillsExitE',0,267,275,18,30,0,-594.279,-1721.03,62.3187,2.25507,'369:0 371:0 411:0 '), +(371,'DurnholdeNE',0,267,275,18,30,0,-383.173,-1720.01,90.0634,3.41784,'372:0 370:0 '), +(372,'DurnholdeNNE',0,267,275,18,30,0,-316.76,-1609.34,86.0559,5.49914,'371:0 377:0 386:0 '), +(373,'DurnholdeS',0,267,267,18,30,0,-669.891,-1326.66,66.6887,5.65621,'376:0 374:0 368:0 369:0 383:0 '), +(374,'DurnholdeInside',0,267,275,18,30,0,-518.661,-1435.16,64.889,2.5637,'373:0 375:0 '), +(375,'DurnholdeInsideDeep',0,267,275,18,30,0,-512.473,-1541.62,67.1947,1.69387,'374:0 '), +(376,'NethanderStead',0,267,289,18,30,0,-915.831,-926.49,31.1754,1.19711,'364:0 367:0 373:0 383:0 20:0 '), +(377,'DurnholdeN',0,267,275,18,30,0,-298.033,-1310.71,76.9034,4.69017,'372:0 378:0 383:0 '), +(378,'DurnholdeNN',0,267,267,18,30,0,-37.1467,-1296.59,83.7166,3.13705,'377:0 21:0 '), +(379,'TarrenMillN',0,267,267,18,30,4,188.047,-974.547,75.5356,0.0209713,'21:0 381:0 '), +(380,'GallowsCorner',0,36,1357,28,40,0,324.254,-615.115,145.246,3.03887,'343:0 345:0 21:0 382:0 '), +(381,'SoferasCorner',0,36,36,28,40,0,461.934,-926.633,129.374,2.72668,'379:0 332:0 382:0 '), +(382,'SoferasNaze',0,36,1678,28,40,0,248.314,-839.928,146.333,0.818164,'381:0 380:0 '), +(383,'HFH_bridge',0,267,267,18,30,0,-487.607,-967.55,34.4918,4.54488,'377:0 376:0 373:0 384:0 '), +(384,'DarrowHillE',0,267,267,18,30,0,-323.998,-672.57,54.6417,3.9048,'21:0 383:0 385:0 20:0 '), +(385,'DarrowHillW',0,267,1056,18,30,0,-332.483,-447.42,58.4647,0.297842,'346:0 355:0 356:0 384:0 20:0 '), +(386,'HinterlandsEntrance',0,47,47,38,50,0,-77.4076,-1845.63,143.116,2.46164,'372:0 55:0 '), +(387,'ZunWatha',0,47,352,38,50,0,-11.9998,-2493.37,119.659,1.99629,'55:0 82:0 388:0 391:0 '), +(388,'QuelDanilS',0,47,47,38,50,0,65.3995,-2664.2,111.823,5.20269,'55:0 82:0 387:0 389:0 390:0 391:0 392:0 '), +(389,'QuelDanil',0,47,350,38,50,4,210.196,-2789.46,122.156,1.50935,'388:0 390:0 392:0 '), +(390,'HL_hubW',0,47,47,38,50,0,132.208,-2876.17,116.583,1.69588,'388:0 389:0 392:0 393:0 397:0 398:0 '), +(391,'BogensLedge',0,47,1887,38,50,0,-198.398,-2585.67,120.378,6.18051,'387:0 388:0 393:0 '), +(392,'HiriWatha',0,47,1885,38,50,0,-35.4616,-2815.33,122.143,0.435323,'389:0 390:0 388:0 393:0 '), +(393,'ShadraAlorEntrance',0,47,47,38,50,0,-160.179,-2955.33,115.773,1.96371,'391:0 392:0 390:0 395:0 394:0 396:0 398:0 400:0 '), +(394,'ShadraAlorE',0,47,353,38,50,0,-366.126,-2955.41,89.39,6.27554,'393:0 395:0 396:0 '), +(395,'ShadraAlorS',0,47,353,38,50,0,-455.461,-2839.4,105.834,6.08704,'393:0 394:0 396:0 '), +(396,'ShadraAlorW',0,47,353,38,50,0,-296.621,-2833,96.7074,5.15045,'393:0 395:0 394:0 '), +(397,'AgolWatha',0,47,1884,38,50,0,397.177,-3352.2,123.451,2.24644,'390:0 398:0 399:0 '), +(398,'CreepingRun',0,47,1886,38,50,0,116.576,-3466.05,107.658,1.12922,'393:0 397:0 390:0 399:0 400:0 401:0 '), +(399,'SkulkRock',0,47,351,38,50,0,363.014,-3796.73,171.76,1.54745,'397:0 398:0 401:0 404:0 '), +(400,'AltarOfZul',0,47,355,38,50,0,-147.861,-3319.03,121.957,3.93113,'393:0 398:0 '), +(401,'HL_hubC',0,47,47,38,50,0,109.926,-3922.75,136.697,0.948588,'398:0 399:0 402:0 403:0 404:0 '), +(402,'JinthaAlor',0,47,354,38,50,0,-217.196,-4159.98,118.665,0.453769,'401:0 403:0 '), +(403,'OverlookCliffsS',0,47,307,38,50,0,-150.233,-4250.21,120.905,4.17653,'401:0 402:0 405:0 '), +(404,'SeradaneBridge',0,47,351,38,50,0,472.513,-3907.18,113.731,5.88677,'399:0 401:0 409:0 '), +(405,'OverlookCliffsRampTop',0,47,307,38,50,0,-243.665,-4377.48,105.997,0.98978,'403:0 406:0 '), +(406,'OverlookCliffsRampBottom',0,47,307,38,50,0,-3.15739,-4629.26,13.899,2.27391,'405:0 407:0 408:0 '), +(407,'RevantuskOutside',0,47,307,38,50,0,-355.789,-4475.87,11.3783,0.924997,'406:0 179:0 '), +(408,'OverlookCliffsEnd',0,47,307,38,50,0,133.371,-4745.73,2.05405,2.27392,'406:0 '), +(409,'Seradane',0,47,356,60,80,0,755.958,-4011.61,92.8808,5.95432,'404:0 '), +(410,'ArathiExitNW',0,45,334,28,40,0,-889.426,-1677.32,57.6111,1.06053,'369:0 411:0 412:0 414:0 '), +(411,'ArathiExitN',0,45,45,28,40,0,-684.458,-1831.3,53.3946,0.936824,'370:0 410:0 412:0 413:0 '), +(412,'ArathiCW',0,45,45,28,40,0,-863.176,-1785.9,39.6302,5.66886,'410:0 411:0 413:0 414:0 '), +(413,'NorthfoldManor',0,45,313,28,40,0,-822.895,-2039.83,34.4558,1.25884,'411:0 412:0 415:0 '), +(414,'Arathi_hubW',0,45,45,28,40,0,-1190.48,-1731.63,56.3674,6.26181,'410:0 412:0 415:0 420:0 '), +(415,'NorthfoldManor',0,45,313,28,40,0,-933.512,-2120.29,56.5869,2.05011,'78:0 413:0 414:0 416:0 '), +(416,'RefugePointeExitN',0,45,315,28,40,0,-1158.07,-2706.9,52.0215,2.18796,'78:0 22:0 415:0 417:0 418:0 419:0 435:0 '), +(417,'DabyrieFarmstead',0,45,45,28,40,0,-1091.19,-2856.55,42.4006,1.78073,'416:0 418:0 435:0 436:0 '), +(418,'ArathiCO',0,45,336,28,40,0,-1352.09,-2738.95,59.0948,6.08275,'416:0 417:0 419:0 431:0 432:0 435:0 '), +(419,'RefugePointeExitS',0,45,45,28,40,0,-1466.43,-2424.6,57.8277,5.98262,'22:0 78:0 416:0 418:0 420:0 427:0 429:0 431:0 '), +(420,'StromgardeRoad',0,45,45,28,40,0,-1323.01,-1833.39,63.6564,0.712589,'421:0 414:0 419:0 427:0 '), +(421,'StromgardeInside',0,45,324,28,40,0,-1576.29,-1800.48,67.6512,3.46934,'422:0 423:0 424:0 420:0 '), +(422,'StromgardeKeep',0,45,324,28,40,0,-1660.06,-1803.63,83.0724,6.27321,'421:0 426:0 '), +(423,'StromgardeE',0,45,324,28,40,0,-1602.44,-1922.26,67.2707,1.50115,'421:0 '), +(424,'StromgardeW1',0,45,324,28,40,0,-1596.2,-1745.56,67.3627,5.09826,'421:0 425:0 '), +(425,'StromgardeW2',0,45,324,28,40,0,-1720.34,-1736.69,52.4064,6.26851,'424:0 81:0 '), +(426,'StromgardeKeepSide',0,45,324,28,40,0,-1681.32,-1933.25,80.6272,1.35821,'422:0 '), +(427,'ArathiCI',0,45,335,28,40,0,-1517.62,-2100.44,22.3405,4.92548,'419:0 420:0 428:0 '), +(428,'ThandolRoadN',0,45,45,28,40,0,-1995.23,-2466.74,78.7992,0.837489,'427:0 429:0 441:0 '), +(429,'ArathiRoadSmid',0,45,45,28,40,0,-1763.42,-2422.8,59.5627,0.9015,'419:0 428:0 431:0 430:0 '), +(430,'BoulderfistHall',0,45,316,28,40,0,-1941.13,-2794.22,85.7724,0.38314,'429:0 431:0 432:0 '), +(431,'Arathi_hubE1',0,45,45,28,40,0,-1570.23,-2675.51,35.6533,6.03014,'419:0 418:0 429:0 432:0 435:0 430:0 '), +(432,'Arathi_hubE2',0,45,317,28,40,0,-1705.19,-3021.39,31.689,0.310091,'433:0 418:0 431:0 434:0 430:0 '), +(433,'WitherbarkVillage',0,45,317,28,40,0,-1867.12,-3365.42,56.1179,1.07782,'432:0 '), +(434,'GoshekFarm',0,45,314,28,40,0,-1526.14,-3075.57,14.1487,2.82925,'432:0 435:0 '), +(435,'Arathi_hubNE1',0,45,45,28,40,0,-1297.32,-3141.76,34.9289,1.25489,'416:0 417:0 418:0 431:0 434:0 436:0 438:0 '), +(436,'Arathi_hubNE2',0,45,45,28,40,0,-1005.91,-3313.55,55.2766,2.32737,'435:0 417:0 437:0 438:0 '), +(437,'ArathiCE',0,45,333,28,40,0,-839.717,-3280.93,78.5616,3.98495,'436:0 438:0 '), +(438,'HammerfallEntrance',0,45,321,28,40,0,-1164.53,-3558.81,50.1497,1.47167,'435:0 436:0 437:0 23:0 439:0 '), +(439,'DrywhiskerGorgeOutside',0,45,318,28,40,0,-1086,-3696.31,81.3913,2.10981,'438:0 440:0 '), +(440,'DrywhiskerGorgeEntrance',0,45,318,28,40,0,-1083.95,-3820.83,128.504,1.71515,'439:0 44:0 '), +(441,'ArathiRoadSbottom',0,45,880,28,40,0,-2248.64,-2487.38,80.1236,4.70713,'428:0 442:0 443:0 '), +(442,'ThandolSpanCamp',0,45,45,28,40,0,-2249.4,-2633.38,78.816,1.44851,'441:0 '), +(443,'ThandolSpanS',0,11,881,18,30,0,-2478.16,-2506.16,78.5672,0.110205,'441:0 444:0 '), +(444,'DunModrCamp',0,11,881,18,30,0,-2609.79,-2494.69,80.9667,1.57693,'443:0 6:0 445:0 '), +(445,'Wetlands_hubN1',0,11,11,18,30,0,-2937.95,-2470.57,26.6988,6.13028,'444:0 446:0 447:0 461:0 462:0 '), +(446,'DaggerforgeHill',0,11,1016,18,30,0,-2859.63,-2907.96,33.1564,1.54551,'445:0 448:0 449:0 '), +(447,'WL_hubC1',0,11,11,18,30,0,-3206.06,-2452.65,10.0327,5.76822,'445:0 448:0 455:0 461:0 462:0 '), +(448,'Greenwarden',0,11,1025,18,30,0,-3254.89,-2726.4,9.41813,1.21683,'446:0 447:0 449:0 455:0 458:0 '), +(449,'WL_hubE1',0,11,11,18,30,0,-3421.46,-3088.4,22.5006,4.5862,'446:0 448:0 450:0 451:0 455:0 456:0 '), +(450,'RaptorRidge',0,11,1017,23,30,0,-3132.51,-3240.98,63.5747,2.76015,'449:0 451:0 '), +(451,'DragonmawGates',0,11,1038,61,70,0,-3452,-3659.86,58.5533,1.08333,'449:0 450:0 452:0 '), +(452,'DragonmawGatesI1',0,11,1038,61,70,0,-3594.66,-4056.85,113.625,1.23963,'451:0 453:0 '), +(453,'DragonmawGatesI2',0,11,1038,61,70,0,-3956.61,-4019.52,170.857,6.00896,'452:0 454:0 '), +(454,'DragonmawGatesI3',0,11,1037,61,70,0,-4145.01,-3662.88,204.651,5.17056,'92:0 453:0 '), +(455,'WL_hubS1',0,11,1020,18,30,0,-3777.35,-2817.99,12.647,0.508038,'447:0 448:0 449:0 456:0 457:0 '), +(456,'MosshideFen',0,11,1020,18,30,0,-3913.48,-3043.47,11.7092,0.398469,'449:0 455:0 457:0 '), +(457,'ThelgenRock',0,11,1021,18,30,0,-3921.58,-2647.27,36.3203,4.94005,'455:0 456:0 479:0 '), +(458,'AngerfangEnampmentE',0,11,1036,18,30,0,-3503.17,-2440.43,48.2306,5.48001,'448:0 459:0 '), +(459,'AngerfangEnampmentW',0,11,1036,18,30,0,-3354.34,-2190.82,45.3521,1.88565,'458:0 460:0 461:0 '), +(460,'AngerfangEnampmentTop',0,11,1036,18,30,0,-3458.4,-2008.76,119.804,5.31784,'459:0 '), +(461,'WL_hubW1',0,11,11,18,30,0,-3186.98,-2117.49,15.8054,4.81126,'445:0 447:0 459:0 462:0 463:0 467:0 '), +(462,'IronbeardsTomb',0,11,309,18,30,0,-2861.94,-2217.72,29.2628,4.06121,'445:0 447:0 461:0 463:0 464:0 '), +(463,'MosshideWest',0,11,11,18,30,0,-2916.55,-1848.65,10.201,4.36986,'461:0 462:0 464:0 465:0 466:0 467:0 '), +(464,'BaradinBayN',0,11,1023,18,30,5,-2598.33,-1745.42,10.159,4.05766,'462:0 463:0 465:0 466:0 '), +(465,'BaradinBayNW',0,11,298,18,30,0,-2723.52,-1348.51,9.75282,4.1578,'464:0 463:0 466:0 474:0 '), +(466,'BaradinBayW',0,11,1022,18,30,0,-2951.24,-1112.1,9.14625,2.36904,'464:0 463:0 465:0 474:0 475:0 476:0 '), +(467,'WhelgarsOutside',0,11,1024,18,30,0,-3208.01,-1693.31,8.6748,4.04314,'461:0 463:0 468:0 473:0 474:0 '), +(468,'WhelgarsEntrance',0,11,118,18,30,0,-3328.04,-1856.79,25.9266,3.14189,'467:0 469:0 470:0 '), +(469,'WhelgarsBottom',0,11,118,18,30,0,-3540.59,-1803.75,24.3572,5.93869,'468:0 '), +(470,'WhelgarsRamp',0,11,118,18,30,0,-3370.49,-1931.82,63.5201,0.805329,'468:0 471:0 '), +(471,'WhelgarsRampTop',0,11,118,18,30,0,-3471.16,-1925.52,113.829,3.80162,'470:0 472:0 '), +(472,'WhelgarsCave',0,11,118,18,30,0,-3569.75,-1976.96,117.678,0.449137,'471:0 '), +(473,'BlueChannelMarsh',0,11,1018,18,30,0,-3582.96,-1324.33,9.39017,5.56012,'467:0 474:0 476:0 '), +(474,'BluegillMarsh',0,11,1022,18,30,0,-3135.95,-1301.23,7.27194,1.32132,'465:0 466:0 467:0 473:0 475:0 476:0 '), +(475,'BaradinBayS',0,11,298,18,30,0,-3202.27,-925.946,8.88303,5.67636,'466:0 474:0 476:0 '), +(476,'MenethilHarborRoad',0,11,1022,18,30,2,-3333.27,-1053.79,8.28482,2.51317,'11:0 466:0 473:0 474:0 475:0 '), +(477,'MenethilBayS',0,11,298,18,30,2,-3909.27,-638.054,4.91793,5.35238,'11:0 '), +(478,'MenethilBayN',0,11,298,18,30,2,-3732.25,-581.622,4.65228,4.77512,'11:0 '), +(479,'DunAlgazBottom',0,11,836,18,30,0,-4086.88,-2624.69,43.3702,1.60133,'457:0 480:0 '), +(480,'DunAlgazMid1',0,11,836,18,30,0,-4092.37,-2403.53,100.01,4.74881,'479:0 481:0 '), +(481,'DunAlgazMid2',0,11,836,18,30,0,-3994.82,-2377.44,120.415,3.60017,'480:0 482:0 '), +(482,'DunAlgazMid3',0,11,836,18,30,0,-4070,-2464.84,155.161,3.20551,'481:0 483:0 '), +(483,'DunAlgazMid4',0,11,836,18,30,0,-4418.61,-2470.44,212.203,0.00303268,'482:0 5:0 484:0 '), +(484,'DunAlgazMid5',0,11,836,18,30,0,-4453.35,-2691.02,268.159,1.48548,'483:0 485:0 '), +(485,'DunAlgazTop',0,38,837,8,20,0,-4741.84,-2699.03,325.269,6.26389,'484:0 91:0 487:0 488:0 '), +(486,'NorthGatePassBottom',0,38,838,8,20,2,-4797.55,-2537.41,354.11,2.07534,'91:0 524:0 '), +(487,'SilverStreamMine',0,38,149,8,20,2,-4801.44,-2968.22,321.735,1.63004,'91:0 485:0 488:0 '), +(488,'StonewroughDamW',0,38,146,8,20,2,-4676.63,-3184.73,310.249,2.0836,'487:0 485:0 489:0 '), +(489,'StonewroughDamC',0,38,146,8,20,2,-4761.78,-3306.52,310.258,4.81285,'488:0 490:0 '), +(490,'StonewroughDamE',0,38,146,8,20,2,-4680.9,-3464.1,310.208,3.57506,'489:0 491:0 '), +(491,'TheLochNE',0,38,38,8,20,2,-4859.79,-3631.09,306.481,2.35809,'490:0 492:0 495:0 496:0 497:0 '), +(492,'MogroshStronghold',0,38,143,8,20,2,-4884.91,-3956.61,298.791,3.26837,'491:0 493:0 494:0 495:0 '), +(493,'MogroshStrongholdTop',0,38,143,8,20,2,-4853.52,-4046.55,315.354,1.54756,'492:0 '), +(494,'LochModanE',0,38,38,8,20,2,-5200.93,-4080.99,324.555,1.53421,'492:0 495:0 512:0 '), +(495,'LochModanC2',0,38,38,8,20,2,-5358.15,-3755.11,304.073,5.92655,'491:0 492:0 494:0 496:0 506:0 512:0 '), +(496,'LochIsleE',0,38,38,8,20,2,-4983.21,-3478.31,305.5,4.76612,'491:0 495:0 497:0 498:0 '), +(497,'LochIsleW',0,38,38,8,20,2,-4869.1,-3302.75,307.516,3.90807,'491:0 496:0 498:0 '), +(498,'LochIsleS',0,38,38,8,20,2,-5031.68,-3316.94,298.701,5.51616,'497:0 496:0 499:0 '), +(499,'LochC1',0,38,556,8,20,2,-5138.3,-3125.08,302.046,2.10361,'498:0 502:0 '), +(500,'LochW1',0,38,38,8,20,2,-5067.56,-2665.82,323.188,6.27801,'91:0 501:0 502:0 '), +(501,'LochW2',0,38,38,8,20,2,-5383.73,-2741.02,362.182,0.255963,'500:0 502:0 503:0 '), +(502,'LochW3',0,38,38,8,20,0,-5215.01,-2861.61,336.947,0.715418,'91:0 499:0 500:0 501:0 503:0 '), +(503,'ThelsamarEntrance',0,38,38,8,20,0,-5405.86,-2884.97,342.8,0.491581,'501:0 502:0 19:0 504:0 515:0 '), +(504,'LochC3',0,38,144,8,20,0,-5441.5,-3109.22,349.359,0.721317,'503:0 19:0 505:0 '), +(505,'LochS',0,38,38,8,20,0,-5860.82,-3292.84,292.88,5.93047,'504:0 506:0 621:0 '), +(506,'IronbandsExcavationOutside',0,38,142,8,20,2,-5617.55,-3680.57,313.913,2.11697,'495:0 505:0 507:0 509:0 '), +(507,'IronbandsExcavationSW',0,38,142,8,20,2,-5787.94,-3777.91,328.169,4.57725,'506:0 508:0 510:0 '), +(508,'IronbandsExcavationInside1',0,38,142,8,20,2,-5722.82,-3946.06,324.917,2.02667,'507:0 509:0 '), +(509,'IronbandsExcavationInside2',0,38,142,8,20,2,-5578.49,-3926.28,327.498,1.64379,'506:0 508:0 '), +(510,'LochCornerSE',0,38,147,8,20,2,-5843.55,-4145.23,387.605,1.3532,'507:0 511:0 512:0 '), +(511,'FastriderLodgeEntrance',0,38,147,8,20,2,-5555.41,-4251.75,380.644,2.87687,'510:0 512:0 513:0 '), +(512,'LochModanSE',0,38,147,8,20,2,-5553.71,-4104.44,372.683,1.01823,'494:0 495:0 510:0 511:0 '), +(513,'FarstriderLodge',0,38,147,8,20,2,-5621.61,-4334.97,403.763,0.951478,'511:0 514:0 '), +(514,'FarstriderLodgeInside',0,38,147,8,20,3,-5674.26,-4247.48,407.006,5.29276,'513:0 '), +(515,'LochModanSW1',0,38,38,8,20,2,-5673.4,-2775.82,363.022,5.89359,'503:0 516:0 519:0 '), +(516,'StonesplinterValley1',0,38,923,8,20,2,-5878.01,-2909.13,366.788,0.558766,'515:0 517:0 518:0 '), +(517,'StonesplinterValley2',0,38,923,8,20,2,-6057.08,-3024.86,403.365,0.541086,'516:0 '), +(518,'StonesplinterValley3',0,38,923,8,20,2,-6077.12,-2770.04,413.566,5.71294,'516:0 '), +(519,'LochModanSW2',0,38,924,8,20,2,-5804.6,-2605.8,316.201,6.15474,'515:0 520:0 521:0 '), +(520,'LochModanSW3',0,38,924,8,20,2,-6029.33,-2496.11,310.016,2.1924,'519:0 561:0 '), +(521,'SouthGatePassBottom',0,38,839,8,20,2,-5690.63,-2589.07,346.087,0.415367,'519:0 522:0 '), +(522,'SouthGateOutpost',0,1,806,1,10,0,-5520.09,-2399.78,400.417,2.27487,'521:0 523:0 '), +(523,'SouthGatePassTop',0,1,805,1,10,2,-5636.7,-2242.95,424.761,5.34381,'522:0 527:0 69:0 '), +(524,'NorthGatePassTop1',0,1,808,1,10,2,-4911.88,-2330.69,408.794,5.22601,'486:0 525:0 '), +(525,'NorthGatePassBottom2',0,1,808,1,10,2,-5204.26,-2288.4,400.984,2.97781,'524:0 526:0 '), +(526,'NorthGatePassTop2',0,1,807,1,10,2,-5294.53,-2180,423.003,2.8011,'525:0 527:0 '), +(527,'DunMoroghPassFork',0,1,1,1,10,2,-5515.28,-2008.17,399.376,4.60752,'526:0 523:0 528:0 '), +(528,'DunMoroghE1',0,1,1,1,10,2,-5509.11,-1798.18,397.338,5.99176,'527:0 69:0 529:0 '), +(529,'GolBolarFork',0,1,1,1,10,2,-5611.84,-1487.57,399.057,5.05517,'528:0 530:0 533:0 '), +(530,'GolBolarCamp',0,1,134,1,10,2,-5711.36,-1562.28,383.568,2.84075,'529:0 531:0 '), +(531,'GolBolarQuarry1',0,1,134,1,10,2,-5849.25,-1514.03,358.812,5.96152,'530:0 532:0 '), +(532,'GolBolarQuarry2',0,1,134,1,10,2,-5737.17,-1683.52,362.196,6.04595,'531:0 '), +(533,'TundridHills',0,1,804,1,10,2,-5651.13,-1033.73,410.816,4.75985,'529:0 534:0 536:0 556:0 '), +(534,'Vagash1',0,1,1,1,10,2,-5422.73,-1195.14,450.063,2.06791,'533:0 535:0 '), +(535,'Vagash2',0,1,803,1,10,2,-5417.7,-1264.17,446.78,2.22146,'534:0 '), +(536,'DunMoroghS1',0,1,1,1,10,2,-5892.71,-628.276,400.121,5.26175,'533:0 537:0 '), +(537,'DwarfGnomeStartExit1',0,1,1,1,10,2,-5975.18,-476.82,406.774,5.36188,'536:0 9:0 538:0 '), +(538,'DwarfGnomeStartExit2',0,1,1,1,10,2,-6013.25,-224.693,412.196,4.79638,'537:0 539:0 '), +(539,'DwarfGnomeStartExit3',0,1,800,1,10,2,-5890.34,69.4737,372.157,4.07706,'538:0 540:0 545:0 546:0 '), +(540,'ColdridgePass1',0,1,800,1,10,2,-6066.08,42.2314,408.342,0.120498,'539:0 541:0 '), +(541,'ColdridgePass2',0,1,800,4,10,2,-6231.65,125.6,430.753,5.63871,'540:0 542:0 '), +(542,'DwarfGnomeStart1',0,1,132,1,10,3,-6261.08,369.552,383.537,4.94484,'541:0 1:0 543:0 544:0 '), +(543,'DwarfGnomeStart2',0,1,132,1,10,2,-6482.65,496.244,386.328,5.80485,'542:0 544:0 '), +(544,'DwarfGnomeStart3',0,1,132,1,10,2,-6269.69,741.137,386.893,4.77403,'542:0 543:0 '), +(545,'GrizzledDen',0,1,136,1,10,2,-5690.95,-281.229,364.314,5.54297,'539:0 9:0 '), +(546,'DunMoroghSW1',0,1,1,1,10,2,-5756.39,127.948,368.817,4.07511,'539:0 547:0 548:0 '), +(547,'OldIcebeard',0,1,801,1,10,2,-5602.44,-28.5472,416.22,2.31387,'546:0 '), +(548,'DunMoroghW1',0,1,135,1,10,2,-5661.62,365.277,393.274,4.27146,'546:0 549:0 550:0 551:0 '), +(549,'FrostmaneHold',0,1,135,1,10,2,-5549.4,568.067,394.761,1.30462,'548:0 551:0 '), +(550,'GnomereganS',0,1,133,1,10,2,-5183.81,585.068,404.291,4.35,'548:0 551:0 '), +(551,'IceflowLake',0,1,211,1,10,2,-5250.86,115.774,394.041,2.85773,'548:0 549:0 550:0 552:0 555:0 '), +(552,'ShimmerRidge1',0,1,802,1,10,2,-5266.4,-149.752,437.903,6.01894,'551:0 553:0 '), +(553,'ShimmerRidge2',0,1,802,1,10,2,-5088.11,-166.104,442.035,0.727311,'552:0 554:0 555:0 '), +(554,'ShimmerRidge3',0,1,802,1,10,2,-5043,-267.85,441.4,2.19994,'553:0 '), +(555,'ShimmerRidge4',0,1,802,1,10,2,-5021.64,-132.237,411.899,3.77858,'553:0 551:0 '), +(556,'DunmoroghC1',0,1,1,1,10,2,-5391.34,-928.482,393.467,4.06525,'533:0 557:0 '), +(557,'IronforgeRamp',0,1,1,1,10,2,-5259.37,-493.579,386.436,3.14828,'556:0 9:0 558:0 '), +(558,'IronforgeRampMid',0,1,809,1,10,2,-5197.66,-730.697,445.722,5.89324,'557:0 559:0 '), +(559,'IronforgeRampTop',0,1,809,1,10,2,-5060.46,-745.984,480.27,3.47972,'558:0 8:0 '), +(560,'IronforgeBank',0,1537,1537,1,10,3,-4909.88,-969.761,501.463,5.46481,'8:0 '), +(561,'SGgate',0,51,1959,43,54,0,-6413.58,-2007.56,244.634,2.74852,'520:0 562:0 563:0 '), +(562,'DustfireValleyMid',0,51,1959,43,54,0,-6590.48,-1884.27,245.713,1.74321,'561:0 580:0 585:0 '), +(563,'SGexitE',0,51,51,43,54,0,-6906.38,-1818.59,241.661,5.89992,'561:0 564:0 566:0 593:0 '), +(564,'GrimesiltDigSite',0,51,247,43,54,0,-7019.65,-1698.57,241.667,5.46992,'563:0 565:0 566:0 567:0 568:0 '), +(565,'TannerCamp',0,51,1958,43,54,0,-7225.75,-1752.49,244.286,0.924438,'564:0 566:0 567:0 568:0 '), +(566,'SGtower4',0,51,1444,43,54,0,-6980,-1505.92,242.742,3.93841,'563:0 564:0 565:0 567:0 568:0 '), +(567,'SGtower3',0,51,1444,43,54,0,-7036.7,-1320.2,244.272,4.7631,'565:0 564:0 566:0 568:0 '), +(568,'SGsouth1',0,51,1444,43,54,0,-7155.86,-1317.76,242.155,5.03211,'565:0 569:0 567:0 564:0 566:0 '), +(569,'BRM_SG1',0,51,1445,43,54,0,-7215.95,-1057.94,242.73,1.72755,'571:0 568:0 570:0 572:0 573:0 '), +(570,'BRM_SG2',0,25,25,46,60,0,-7399.25,-1106.95,278.077,0.229401,'569:0 622:0 '), +(571,'SGwest1',0,51,51,43,54,0,-7085,-941.753,268.272,4.123,'569:0 572:0 573:0 '), +(572,'SGtower2',0,51,51,43,54,0,-7001.07,-1109.84,243.97,2.78587,'571:0 569:0 573:0 '), +(573,'SGwest2',0,51,51,43,54,0,-6974.99,-1007.32,241.667,3.29324,'571:0 569:0 572:0 574:0 588:0 '), +(574,'CauldronRampTop',0,51,246,43,54,0,-6974.25,-1172.02,228.781,3.87051,'573:0 575:0 '), +(575,'CauldronRampMid1',0,51,246,43,54,0,-7024.61,-1220.88,207.974,5.71816,'574:0 576:0 '), +(576,'CauldronRampMid2',0,51,246,43,54,0,-6955.19,-1270.62,180.686,0.528642,'575:0 577:0 581:0 '), +(577,'CauldronRampBottom',0,51,246,43,54,0,-6866.28,-1410.17,172.855,2.20156,'576:0 578:0 '), +(578,'CauldronRamp2Bottom',0,51,246,43,54,0,-6712.76,-1610.77,196.339,2.17329,'577:0 579:0 '), +(579,'CauldronRamp2Mid',0,51,246,43,54,0,-6668.28,-1664.76,229.053,1.92432,'578:0 580:0 '), +(580,'CauldronRamp2Top',0,51,246,43,54,0,-6680,-1730.07,255.913,4.54363,'562:0 579:0 '), +(581,'SlagPit1',0,51,1443,43,54,0,-6850.01,-1218.8,177.395,3.61294,'576:0 582:0 '), +(582,'SlagPit2',0,51,1443,43,54,0,-6764.59,-1174.15,187.213,1.12951,'581:0 583:0 '), +(583,'SlagPit3',0,51,1443,43,54,0,-6439.47,-1311.02,180.938,2.63197,'582:0 584:0 '), +(584,'SlagPit4',0,51,1443,43,54,0,-6631.29,-1289.33,208.714,0.00481129,'583:0 '), +(585,'SG_hubN1',0,51,51,43,54,0,-6686.77,-1347.22,247.961,4.79378,'562:0 586:0 588:0 589:0 '), +(586,'CauldronTopN',0,51,246,43,54,0,-6862.1,-1188.37,240.366,5.51045,'585:0 '), +(587,'FirewatchRidgeBottom',0,51,1442,43,54,0,-6612.66,-860.886,244.297,0.237204,'588:0 589:0 591:0 '), +(588,'FirewatchRidgeS',0,51,1442,43,54,0,-6817.17,-866.771,248.462,4.87941,'573:0 585:0 587:0 '), +(589,'ThoriumPointRampBottom',0,51,1442,43,54,0,-6608.41,-1025.59,244.328,1.21161,'587:0 585:0 590:0 '), +(590,'ThoriumPointRampTop',0,51,1446,43,54,1,-6473.64,-1104.63,303.285,2.64303,'589:0 180:0 '), +(591,'FirewatchRidgeTop1',0,51,1442,43,54,0,-6473.38,-887.709,324.088,3.80543,'587:0 592:0 '), +(592,'FirewatchRidgeTop2',0,51,1442,43,54,0,-6502.46,-1018.74,344.372,1.2407,'591:0 '), +(593,'SGexitEmid',0,51,51,43,54,0,-6952.2,-2064.74,282.478,5.11429,'563:0 594:0 '), +(594,'BLexitW',0,3,3,33,45,0,-6885.86,-2233.35,242.64,0.361067,'593:0 595:0 24:0 '), +(595,'ApocryphansRest',0,3,337,33,45,0,-6892.26,-2477.47,247.238,1.71982,'594:0 87:0 596:0 597:0 600:0 601:0 602:0 '), +(596,'CampCaggS',0,3,344,33,45,0,-7270.91,-2407.34,268.195,5.1163,'595:0 597:0 '), +(597,'CampCaggE',0,3,3,33,45,0,-7140.34,-2650.51,243.568,0.467924,'595:0 596:0 602:0 603:0 '), +(598,'KargathE',0,3,3,33,45,0,-6600.53,-2373.56,254.037,0.026153,'600:0 599:0 24:0 '), +(599,'KargathNE',0,3,3,33,45,0,-6431.02,-2454.24,321.326,3.51922,'598:0 600:0 '), +(600,'DustbowlW',0,3,1878,33,45,0,-6718.55,-2572.24,241.801,0.471881,'595:0 598:0 599:0 601:0 602:0 '), +(601,'DustbowlNE',0,3,1878,33,45,0,-6653.03,-2889.09,241.667,2.73974,'595:0 600:0 602:0 620:0 '), +(602,'BL_hubC',0,3,1879,33,45,0,-6902.56,-2958.24,244.772,1.67356,'600:0 597:0 601:0 595:0 608:0 603:0 '), +(603,'AgmondsendS',0,3,345,33,45,0,-7141.97,-3251.77,246.326,0.774282,'597:0 602:0 604:0 605:0 607:0 '), +(604,'BL_elemsS',0,3,3,33,45,0,-7319.09,-3144.54,317.901,5.93436,'603:0 '), +(605,'AgmondsEnd',0,3,345,33,45,0,-7033.01,-3313.39,238.277,2.5061,'603:0 606:0 607:0 609:0 '), +(606,'BL_elemsSE',0,3,3,33,45,0,-7339.37,-3450.9,320.961,0.385524,'605:0 '), +(607,'CampBoff',0,3,342,33,45,0,-7039.11,-3655.92,244.152,1.66966,'603:0 605:0 608:0 609:0 '), +(608,'BL_hubN1',0,3,1877,33,45,0,-6667.51,-3285.91,241.069,2.14484,'25:0 602:0 607:0 609:0 620:0 '), +(609,'BL_hubNE1',0,3,1898,33,45,0,-6775.23,-3555.53,245.004,5.79028,'605:0 607:0 608:0 610:0 617:0 620:0 '), +(610,'LethlorRavineEntranceS',0,3,339,38,45,0,-6742.54,-3895.6,264.574,1.61394,'609:0 611:0 '), +(611,'LethlorRavineS',0,3,339,38,45,0,-7002.17,-3938.58,263.889,5.47219,'610:0 612:0 '), +(612,'LethlorRavineC',0,3,339,38,45,0,-6820.98,-4135.08,263.933,2.32276,'611:0 613:0 '), +(613,'LethlorRavineN',0,3,339,38,45,0,-6424.74,-4106.41,263.889,3.0143,'612:0 614:0 '), +(614,'LethlorRavineNW',0,3,339,38,45,0,-6513.4,-4004.6,264.394,5.30492,'613:0 615:0 '), +(615,'LethlorRavineEntranceN',0,3,339,38,45,0,-6396.09,-3976.18,268.618,3.37284,'614:0 616:0 '), +(616,'LethlorRavineExitN',0,3,1898,38,45,0,-6467.07,-3837.32,315.608,5.41801,'615:0 617:0 '), +(617,'CampKoshS',0,3,1898,33,45,0,-6360.66,-3685.6,245.154,4.17511,'616:0 609:0 618:0 619:0 '), +(618,'CampKosh',0,3,341,33,45,0,-6249.45,-3750.01,243.041,2.72409,'617:0 619:0 '), +(619,'HammertoesDigsite',0,3,346,33,45,0,-6349.91,-3447.28,241.681,0.369852,'617:0 618:0 620:0 621:0 '), +(620,'BL_hubN2',0,3,338,33,45,0,-6494.59,-3248.21,242.617,1.06886,'25:0 601:0 608:0 609:0 619:0 '), +(621,'MakersTerrace',0,3,1897,33,45,0,-6056.99,-3302.11,258.645,3.45646,'86:0 505:0 619:0 '), +(622,'BRM_SG3',0,25,25,46,60,0,-7496.19,-1063.83,264.543,4.72301,'570:0 623:0 628:0 '), +(623,'BRM_chain1',0,25,25,46,60,0,-7501.48,-1151.43,269.644,1.34305,'622:0 624:0 627:0 '), +(624,'BRM_chain2',0,25,25,46,60,0,-7599.78,-1110.62,249.93,1.94977,'623:0 625:0 '), +(625,'BRM_chain3',0,25,25,46,60,0,-7523.43,-1048.09,180.912,0.471264,'624:0 626:0 '), +(626,'BRM_chain4',0,25,25,46,60,0,-7383.99,-1012.09,173.658,3.00809,'625:0 '), +(627,'BRM_BRS1',0,25,25,46,60,0,-7609.9,-1226.43,233.401,0.518375,'623:0 628:0 '), +(628,'BRM_BS1',0,25,25,46,60,0,-7697.61,-1089.8,217.609,3.00611,'627:0 622:0 629:0 '), +(629,'BRM_BS2',0,25,25,46,60,0,-7769.06,-1129.81,215.084,0.38328,'628:0 630:0 '), +(630,'BRM_BS3',0,25,25,46,60,0,-7993.75,-1138.96,163.061,0.128021,'629:0 631:0 637:0 639:0 641:0 '), +(631,'DracodarNW',0,46,2421,48,56,0,-7968.74,-817.038,131.202,4.58672,'630:0 632:0 636:0 '), +(632,'AltarOfStormsRoad',0,46,46,48,56,0,-7803.25,-717,176.761,6.28318,'631:0 633:0 '), +(633,'AltarOfStormsFork',0,46,255,48,56,0,-7679.67,-712.976,183.628,3.3461,'632:0 634:0 635:0 '), +(634,'AltarOfStormsVendor',0,46,255,48,56,0,-7644.75,-636.535,200.452,4.00749,'633:0 '), +(635,'AltarOfStorms1',0,46,255,48,56,0,-7626.75,-709.048,183.359,5.20123,'633:0 2368:0 '), +(636,'DracodarW',0,46,2421,48,56,0,-8170.67,-727.141,135.21,5.81979,'631:0 637:0 '), +(637,'DracodarS',0,46,2421,48,56,0,-8255.07,-1040.7,147.3,0.143321,'630:0 636:0 638:0 639:0 '), +(638,'DracodarS_hill1',0,46,2421,48,56,0,-8419.15,-933.413,214.705,5.70788,'637:0 '), +(639,'DracodarSE',0,46,46,48,56,0,-8254.03,-1168.68,144.64,0.0510463,'630:0 637:0 640:0 641:0 '), +(640,'DracodarS_hill2',0,46,46,48,56,0,-8431.62,-1227.92,207.816,0.498725,'639:0 '), +(641,'DracodarHub',0,46,46,48,56,0,-8073.86,-1401.78,132.05,4.03301,'630:0 639:0 642:0 644:0 645:0 '), +(642,'BlackrockStrongholdOutside',0,46,46,48,56,0,-7757.29,-1591.7,133.263,1.43923,'641:0 643:0 644:0 646:0 '), +(643,'BlackrockStrongholdInside',0,46,252,48,56,0,-7698.11,-1443.02,139.787,4.00159,'642:0 '), +(644,'PillarOfAshS',0,46,46,48,56,0,-8232.79,-1737.58,147.888,6.17911,'641:0 642:0 645:0 658:0 '), +(645,'PillarOfAshE',0,46,253,48,56,0,-8102.12,-1922.65,134.695,1.31201,'641:0 644:0 646:0 647:0 658:0 '), +(646,'ThaurissanNW',0,46,250,48,56,0,-7704.8,-2045.32,133.437,1.61634,'642:0 645:0 139:0 647:0 648:0 '), +(647,'DreadmaulRockW',0,46,249,48,56,0,-7971.45,-2449.46,130.882,1.47692,'646:0 645:0 648:0 652:0 654:0 658:0 659:0 660:0 '), +(648,'DreadmaulRockNW',0,46,249,48,56,0,-7665.96,-2453.95,147.044,2.94798,'647:0 646:0 138:0 649:0 654:0 '), +(649,'DreadmaulRockNE',0,46,249,48,56,0,-7765.85,-2707.5,172.836,1.14313,'648:0 650:0 655:0 '), +(650,'DreadmaulRockSE',0,46,2420,48,56,0,-8161.75,-2869.02,134.8,0.000368118,'649:0 138:0 651:0 652:0 659:0 '), +(651,'MorgansVigil',0,46,2418,48,56,3,-8379.57,-2741.96,186.492,6.13436,'650:0 652:0 659:0 '), +(652,'DreadmaulRockTop1',0,46,249,48,56,0,-7971.64,-2664.1,198.213,2.32517,'647:0 650:0 651:0 138:0 653:0 '), +(653,'DreadmaulRockTop2',0,46,249,48,56,0,-7873.63,-2613.13,221.072,2.92403,'652:0 '), +(654,'DreadmaulRockNWW',0,46,249,48,56,0,-7776.13,-2493.23,160.084,5.03518,'648:0 647:0 '), +(655,'DreadmaulRockInside1',0,46,249,48,56,0,-7848.98,-2661.75,172.955,1.73534,'649:0 656:0 657:0 '), +(656,'DreadmaulRockInside2',0,46,249,48,56,0,-7961.48,-2603.36,173.834,6.08133,'655:0 '), +(657,'DreadmaulRockInside3',0,46,249,48,56,0,-7975.04,-2695.45,157.958,5.80842,'655:0 '), +(658,'DreadmaulPassW',0,46,46,48,56,0,-8216.14,-2308.1,151.442,1.03911,'644:0 645:0 647:0 659:0 '), +(659,'DreadmaulPassN',0,46,46,48,56,0,-8150.16,-2629.44,133.659,1.18246,'647:0 650:0 651:0 658:0 660:0 '), +(660,'DreadmaulPass',0,46,2417,48,56,0,-8431.68,-2546.4,133.207,6.07628,'647:0 659:0 661:0 '), +(661,'RedridgeExitN',0,44,44,13,25,0,-8900,-2574.56,131.851,0.152413,'660:0 75:0 663:0 '), +(662,'RendersRockInside',0,44,998,13,25,0,-8738.68,-2205.37,149.754,2.59501,'75:0 '), +(663,'AlthersMillEntrance',0,44,97,13,25,0,-9153.73,-2628.6,109.561,0.156359,'661:0 664:0 690:0 '), +(664,'RedridgeHub1',0,44,44,13,25,0,-9099.79,-2451.23,120.464,0.197585,'663:0 665:0 667:0 669:0 '), +(665,'RedridgeBridgeN',0,44,69,13,25,2,-9287.68,-2284.47,67.5443,5.81789,'664:0 10:0 672:0 673:0 '), +(666,'RedridgeW',0,44,44,13,25,2,-9312.11,-1873.26,82.1473,5.08747,'10:0 672:0 '), +(667,'RedridgeCanyons1',0,44,95,13,25,2,-8916.14,-2305.07,134.682,3.96044,'664:0 668:0 670:0 '), +(668,'RedridgeCanyons2',0,44,95,13,25,2,-8864.57,-2150.9,133.327,3.71892,'667:0 669:0 670:0 '), +(669,'RedridgeCanyons3',0,44,69,13,25,2,-9145.97,-2198.54,119.516,4.7478,'668:0 664:0 670:0 '), +(670,'RedridgeCanyons4',0,44,95,13,25,2,-9135.91,-2029.37,127.775,5.01877,'667:0 668:0 669:0 77:0 '), +(671,'RethbanCavernsInside',0,44,98,13,25,0,-8826.83,-1947.01,133.091,2.11555,'77:0 '), +(672,'RedridgeBridgeS',0,44,44,13,25,2,-9503.49,-2289.12,74.7234,1.13183,'665:0 666:0 674:0 675:0 '), +(673,'EverstillN',0,44,68,13,25,2,-9263.45,-2458.04,56.1636,1.20646,'665:0 '), +(674,'ThreeCorners',0,44,1002,13,25,0,-9607.09,-2055.97,65.077,4.99696,'672:0 675:0 692:0 693:0 694:0 '), +(675,'RedridgeS1',0,44,1001,13,25,0,-9713.82,-2284.16,63.9369,5.46531,'672:0 674:0 676:0 677:0 '), +(676,'RedridgeS2',0,44,68,13,25,0,-9620.32,-2514.61,59.4453,0.974015,'675:0 677:0 '), +(677,'RedridgeS3',0,44,1001,13,25,0,-9624.96,-2717.09,56.3955,1.53951,'676:0 675:0 678:0 679:0 691:0 '), +(678,'StonewatchS',0,44,70,18,25,0,-9468.55,-3007.78,135.551,1.52185,'677:0 689:0 '), +(679,'RedridgeS4',0,44,997,13,25,0,-9751.37,-3185.18,58.6091,5.63735,'677:0 680:0 681:0 '), +(680,'RedridgeE1',0,44,997,13,25,0,-9611,-3315.1,49.8404,2.09716,'679:0 681:0 682:0 683:0 684:0 '), +(681,'RedridgeE2',0,44,71,13,25,0,-9499.43,-3249.9,50.6,3.02197,'679:0 680:0 '), +(682,'RedridgeE3',0,44,44,18,25,2,-9598.71,-3503.57,121.964,2.30923,'680:0 683:0 '), +(683,'RedridgeE4',0,44,71,18,25,1,-9465.03,-3460.8,116.076,2.91242,'680:0 682:0 74:0 684:0 '), +(684,'RedridgeE5',0,44,71,18,25,0,-9469.61,-3328.04,5.51622,4.94268,'680:0 683:0 '), +(685,'RedridgeNE',0,44,1000,18,25,0,-9116.4,-3271.35,104.33,2.00332,'74:0 686:0 687:0 '), +(686,'RedridgeE6',0,44,1000,18,25,0,-9317.33,-3210.41,107.191,5.54352,'685:0 74:0 687:0 '), +(687,'StonewatchFork',0,44,70,18,25,0,-9195.1,-3020.88,94.6252,4.57746,'686:0 685:0 688:0 689:0 690:0 '), +(688,'StonewatchTower',0,44,999,18,25,0,-9297.56,-2958.62,128.754,5.64364,'687:0 689:0 '), +(689,'StonewatchTop',0,44,2099,18,25,0,-9364.92,-3072.52,164.756,3.2698,'687:0 688:0 678:0 '), +(690,'AlthersMillCenter',0,44,97,13,25,0,-9215.61,-2770.74,89.3399,4.66193,'687:0 663:0 '), +(691,'EverstillE',0,44,44,13,25,0,-9457.68,-2870.36,85.486,2.24488,'677:0 '), +(692,'ThreeCornersCamp1',0,44,1002,13,25,0,-9472.89,-1956.22,83.561,4.19346,'674:0 693:0 694:0 '), +(693,'RedridgeExitW1',0,44,1002,13,25,2,-9620.34,-1809.71,51.8565,1.49408,'692:0 674:0 694:0 696:0 '), +(694,'RedridgeExitW2',0,44,1002,13,25,0,-9831.1,-1766.69,23.8242,2.51983,'692:0 674:0 693:0 695:0 792:0 '), +(695,'ElwynnHubS1',0,12,798,1,10,2,-9928.36,-1115.12,24.1788,6.21189,'705:0 704:0 694:0 696:0 706:0 '), +(696,'ElwynnHubE1',0,12,12,1,10,2,-9659.48,-1341.54,48.8561,4.72993,'693:0 697:0 698:0 704:0 695:0 '), +(697,'EastvaleLC1',0,12,88,1,10,2,-9405,-1343.43,50.0284,2.89799,'696:0 698:0 699:0 '), +(698,'StoneCairnLakeSE',0,12,86,1,10,2,-9299.52,-1180.36,69.4951,3.31622,'696:0 697:0 699:0 701:0 797:0 '), +(699,'StoneCairnLakeNE',0,12,86,1,10,2,-8956.22,-1264.96,77.7779,2.94511,'700:0 697:0 698:0 797:0 '), +(700,'StoneCairnLakeNW',0,12,86,1,10,2,-8860.62,-826.796,71.9141,4.57088,'699:0 701:0 797:0 '), +(701,'StoneCairnLakeSW',0,12,12,1,10,2,-9355.59,-802.598,64.4981,0.692979,'89:0 700:0 708:0 698:0 705:0 706:0 707:0 797:0 '), +(702,'JasperlodeMineInside1',0,12,54,1,10,2,-9125.33,-585.357,58.3507,3.90606,'89:0 703:0 '), +(703,'JasperlodeMineInside2',0,12,54,1,10,2,-9049.44,-618.138,53.1456,4.05725,'702:0 '), +(704,'ElwynnHubE2',0,12,12,1,10,2,-9613.79,-1100.85,40.5009,4.62273,'696:0 705:0 695:0 '), +(705,'ElwynnHubE3',0,12,12,1,10,2,-9618.95,-1024.81,40.4731,4.99973,'704:0 695:0 701:0 706:0 707:0 '), +(706,'BrackwellPumpkinPatch',0,12,62,1,10,2,-9777.88,-877.924,39.5328,5.4101,'705:0 701:0 695:0 707:0 '), +(707,'ElwynnHubC1',0,12,12,1,10,2,-9609.57,-527.364,55.1285,4.48725,'89:0 708:0 701:0 705:0 706:0 716:0 '), +(708,'CrystalLakeE',0,12,18,1,10,2,-9464.16,-422.645,58.9952,3.63313,'89:0 701:0 707:0 709:0 '), +(709,'CrystalLakeW',0,12,18,1,10,2,-9470.07,-173.045,59.9322,4.58858,'708:0 710:0 7:0 '), +(710,'NorthshireExit',0,12,12,1,10,2,-9133.8,-66.2742,82.4151,1.35237,'709:0 16:0 13:0 7:0 '), +(711,'EchoRidgeMineInside',0,12,34,1,10,2,-8560.22,-214.447,85.0045,2.95145,'70:0 '), +(712,'TradeDistrict',0,1519,1519,1,10,3,-8794.44,645.505,94.4595,3.54245,'16:0 '), +(713,'ForestsEdgeS',0,12,60,1,10,2,-10070.2,658.114,37.3319,5.88491,'72:0 88:0 717:0 '), +(714,'FargodeepMineOutside',0,12,57,1,10,2,-9868.23,221.066,14.0194,6.19474,'7:0 71:0 72:0 715:0 '), +(715,'FargodeepMineInside',0,12,57,1,10,2,-9779.25,104.966,4.57989,3.01191,'714:0 '), +(716,'ElwynnHubS2',0,12,12,1,10,2,-9808.32,-269.092,40.0067,5.15408,'71:0 707:0 '), +(717,'ElwynnExitW',0,12,60,1,10,2,-9747.07,741.601,25.5892,4.55914,'7:0 88:0 72:0 713:0 718:0 '), +(718,'WestfallExitNE',0,40,916,8,20,2,-9853.3,918.261,30.2216,5.34454,'717:0 719:0 745:0 746:0 '), +(719,'Longshore1',0,40,2,8,20,2,-9616.19,1059.16,5.79699,0.013632,'720:0 718:0 745:0 '), +(720,'Longshore2',0,40,2,8,20,2,-9634.2,1389.29,9.09694,4.71817,'719:0 721:0 '), +(721,'Longshore3',0,40,2,8,20,2,-9648.12,1575.23,3.54258,4.75547,'720:0 722:0 '), +(722,'Longshore4',0,40,2,8,20,2,-9776.1,1654.24,11.5179,2.15188,'721:0 723:0 742:0 743:0 '), +(723,'Longshore5',0,40,2,8,20,2,-9985.75,1913.6,4.86301,5.35756,'722:0 724:0 742:0 '), +(724,'Longshore6',0,40,2,8,20,2,-10334,2061.94,3.69692,5.05125,'723:0 725:0 739:0 '), +(725,'Longshore7',0,40,2,8,20,2,-10695.4,2102.07,8.28685,6.14098,'724:0 726:0 738:0 '), +(726,'Longshore8',0,40,2,8,20,2,-10975.2,2108.77,-0.213533,6.27058,'725:0 727:0 737:0 '), +(727,'Longshore9',0,40,2,8,20,2,-11307.8,1926.18,9.26031,4.25015,'726:0 728:0 '), +(728,'Longshore10',0,40,2,8,20,2,-11466.2,1725.82,8.61195,0.641238,'727:0 729:0 '), +(729,'DaggerHillsW',0,40,920,8,20,2,-11264.1,1714.56,39.9062,4.76851,'728:0 730:0 737:0 '), +(730,'DaggerHillsC1',0,40,920,8,20,2,-11258.8,1470.77,88.9353,4.93148,'729:0 731:0 '), +(731,'DaggerHillsC2',0,40,920,8,20,2,-11211.1,1297.89,91.1699,1.90966,'730:0 732:0 733:0 '), +(732,'DaggerHillsNook',0,40,920,8,20,2,-11235.2,1182.36,91.6522,1.1871,'731:0 733:0 '), +(733,'DaggerHillsE',0,40,920,8,20,2,-11062.7,1163.02,43.0261,2.75789,'736:0 732:0 731:0 734:0 735:0 750:0 '), +(734,'DustPlains',0,40,922,8,20,2,-11152,739.176,32.5228,1.40505,'733:0 735:0 750:0 '), +(735,'DeadAcre',0,40,917,8,20,2,-10770.8,864.294,33.1505,2.913,'736:0 733:0 734:0 746:0 750:0 '), +(736,'WestfallHubC1',0,40,40,8,20,2,-10842.5,1196.94,34.875,1.14192,'12:0 15:0 735:0 733:0 738:0 741:0 750:0 '), +(737,'DemontsPlace',0,40,921,8,20,2,-11087.9,1894.97,35.4372,5.06105,'726:0 729:0 12:0 738:0 '), +(738,'AlexstonFarmstead',0,40,219,8,20,2,-10615,1671.77,41.41,4.1441,'737:0 725:0 12:0 741:0 742:0 736:0 '), +(739,'GoldCoastQuarry',0,40,113,8,20,2,-10403.7,1909.99,9.96272,4.64675,'724:0 740:0 741:0 '), +(740,'GoldCoastQuarryInside',0,40,113,8,20,2,-10575.3,1990.25,-8.03668,3.01509,'739:0 '), +(741,'WestfallHubC2',0,40,40,8,20,2,-10495.4,1349.24,42.6842,1.76042,'738:0 739:0 15:0 742:0 745:0 736:0 '), +(742,'WestfallHubC3',0,40,918,8,20,2,-10274.9,1408.84,38.9079,3.90454,'741:0 738:0 723:0 722:0 15:0 743:0 745:0 '), +(743,'JangolodeMine',0,40,111,8,20,2,-10017,1466.37,41.0745,6.08639,'742:0 722:0 744:0 745:0 '), +(744,'JangolodeMineInside',0,40,111,8,20,2,-9887.11,1427.15,40.0346,0.896887,'743:0 '), +(745,'SaldeansFarm',0,40,107,8,20,2,-10154.1,1116.55,36.8816,1.65283,'15:0 718:0 719:0 741:0 742:0 743:0 746:0 '), +(746,'WestfallHubE1',0,40,40,8,20,2,-10333.7,859.579,39.774,0.563072,'15:0 718:0 735:0 745:0 752:0 '), +(747,'DefiasHideout',0,1581,1581,15,22,2,-11112,1483.69,32.39,3.56723,'12:0 748:0 '), +(748,'Deadmines1',0,1581,1581,15,22,2,-11252.8,1533.82,28.6803,0.814416,'747:0 749:0 '), +(749,'Deadmines2',0,1581,1581,15,22,2,-11214.4,1638.37,27.2613,1.55073,'748:0 '), +(750,'WestfallExitSE',0,40,40,8,20,2,-10870.3,667.353,30.8385,4.48578,'733:0 734:0 735:0 736:0 15:0 751:0 '), +(751,'DuskwoodExitW',0,10,10,18,30,0,-10857.5,557.367,30.4883,6.27453,'750:0 752:0 14:0 753:0 755:0 '), +(752,'HushedBankN',0,10,1097,18,30,0,-10326.3,623.959,26.42,3.17024,'746:0 751:0 757:0 758:0 763:0 '), +(753,'AddlesSteadW',0,10,536,18,30,0,-11054.9,270.776,25.2035,6.01338,'751:0 14:0 754:0 '), +(754,'AddlesSteadE',0,10,536,18,30,0,-10997.4,179.443,30.4059,1.04377,'753:0 14:0 766:0 '), +(755,'RHCemetaryS',0,10,492,18,30,0,-10587.6,294.965,31.0441,0.0129175,'751:0 14:0 756:0 757:0 765:0 766:0 '), +(756,'RHCemetaryNE',0,10,492,24,30,0,-10398,201.223,34.2462,5.21064,'755:0 757:0 758:0 762:0 764:0 765:0 '), +(757,'RHCemetaryW',0,10,492,22,30,0,-10427.4,409.679,46.6267,4.40525,'752:0 755:0 756:0 758:0 759:0 '), +(758,'RHCemetaryN',0,10,243,24,30,0,-10308.2,348.036,59.693,3.07006,'752:0 757:0 756:0 '), +(759,'DawningWoodCatacombs1',0,10,2098,18,30,0,-10261.5,383.964,10.414,2.92673,'757:0 760:0 '), +(760,'DawningWoodCatacombs2',0,10,2098,18,30,0,-10234.6,284.99,2.79944,4.82935,'759:0 761:0 '), +(761,'DawningWoodCatacombs3',0,10,2098,18,30,0,-10220.1,166.144,0.047382,3.23144,'760:0 762:0 '), +(762,'DawningWoodCatacombs4',0,10,2098,18,30,0,-10338.4,136.513,4.82896,0.380849,'756:0 761:0 '), +(763,'DuskwoodNW1',0,10,799,18,30,0,-10173.2,350.099,32.3759,4.62356,'752:0 764:0 '), +(764,'DuskwoodNW2',0,10,799,18,30,0,-10183.8,-55.3832,27.6626,5.67208,'763:0 756:0 765:0 793:0 '), +(765,'DuskwoodC1',0,10,10,18,30,0,-10482,-16.5716,51.5694,0.81242,'764:0 755:0 756:0 766:0 '), +(766,'DuskwoodC2',0,10,10,18,30,0,-10750.8,67.4617,28.3337,1.80202,'765:0 755:0 14:0 754:0 767:0 '), +(767,'DuskwoodC3',0,10,10,18,30,0,-10910.1,-371.894,39.8351,3.06651,'766:0 768:0 769:0 773:0 774:0 775:0 779:0 795:0 '), +(768,'DuskwoodExitS',0,10,10,18,30,0,-11268.2,-368.936,61.3823,6.14723,'767:0 858:0 '), +(769,'VulGolOgreMound1',0,10,93,24,30,0,-11005.9,-176.407,14.5532,5.12818,'767:0 770:0 771:0 '), +(770,'VulGolOgreMound2',0,10,93,24,30,0,-10959.3,-38.5543,13.6432,4.44096,'769:0 771:0 '), +(771,'VulGolOgreMound3',0,10,93,24,30,0,-11083.6,-80.7448,16.7954,5.39522,'769:0 770:0 772:0 '), +(772,'VulGolOgreMoundInside',0,10,93,24,30,0,-11234.1,-172.07,4.22438,4.5174,'771:0 '), +(773,'YorgenFarmsteadW',0,10,245,24,30,0,-11096.9,-452.028,32.1764,4.54422,'767:0 774:0 '), +(774,'YorgenFarmsteadE',0,10,245,24,30,0,-11052.8,-598.054,29.3447,1.47723,'767:0 773:0 776:0 '), +(775,'DuskwoodC4',0,10,10,18,30,0,-10908,-739.892,54.8364,0.583838,'767:0 776:0 778:0 779:0 '), +(776,'RottingOrchardW',0,10,241,18,30,0,-10986.3,-770.892,55.1122,1.55183,'774:0 775:0 777:0 778:0 '), +(777,'RottingOrchardS',0,10,241,18,30,0,-11103.5,-891.729,62.1149,0.69773,'776:0 778:0 '), +(778,'DuskwoodC5',0,10,10,18,30,0,-10823,-837.935,55.75,1.63037,'775:0 776:0 777:0 779:0 780:0 '), +(779,'DuskwoodC6',0,10,10,18,30,0,-10768,-644.008,42.2621,2.90859,'767:0 775:0 778:0 786:0 '), +(780,'DuskwoodE1',0,10,42,18,30,0,-10757.1,-1156.72,24.9918,1.69202,'778:0 124:0 782:0 783:0 '), +(781,'RolandsDoomInside',0,10,2161,24,30,0,-11157.5,-1167.04,42.5151,0.315576,'124:0 '), +(782,'TranquilGardensCemetery',0,10,121,18,30,0,-11023.4,-1315.7,53.1833,6.14911,'780:0 783:0 '), +(783,'DuskwoodE2',0,10,10,18,30,0,-10813.8,-1366.37,42.2527,0.00140238,'780:0 782:0 3:0 784:0 794:0 '), +(784,'DarkshireExitE',0,10,10,18,30,0,-10539.7,-1338.09,48.0906,1.65268,'785:0 783:0 3:0 '), +(785,'DuskwoodExitE',0,10,10,18,30,0,-10442.9,-1486.98,73.8977,5.13748,'784:0 798:0 '), +(786,'DuskwoodC7',0,10,242,24,30,0,-10451.9,-825.607,50.4132,2.61635,'779:0 787:0 788:0 793:0 '), +(787,'DuskwoodC8',0,10,242,24,30,0,-10647.9,-908.344,50.934,5.93661,'786:0 3:0 788:0 '), +(788,'DuskwoodNE1',0,10,242,18,30,0,-10219.2,-1021.98,31.9202,2.92658,'786:0 787:0 3:0 789:0 790:0 793:0 '), +(789,'ManorMismantle',0,10,1098,24,30,0,-10332.7,-1264.03,35.3024,1.53643,'788:0 3:0 790:0 '), +(790,'DuskwoodNE2',0,10,10,18,30,0,-10181.4,-1145.35,24.304,3.28393,'788:0 789:0 791:0 793:0 '), +(791,'DuskwoodNE3',0,10,10,18,30,0,-10067.1,-1379.68,29.9246,1.99195,'790:0 792:0 '), +(792,'DuskwoodExitNE',0,10,10,18,30,0,-9963.01,-1637.21,27.2574,2.08816,'694:0 791:0 '), +(793,'DuskwoodN',0,10,799,18,30,0,-10020.5,-660.012,39.1578,1.66406,'790:0 788:0 764:0 786:0 '), +(794,'Naraxis',0,10,10,18,30,0,-10619.4,-1502.44,90.534,3.1465,'783:0 '), +(795,'TwilightGroveEntrance',0,10,10,50,60,0,-10718.7,-425.681,126.691,0.442773,'767:0 796:0 '), +(796,'TwilightGrove',0,10,856,50,60,0,-10419.3,-421.597,45.6561,3.03852,'795:0 '), +(797,'HeroesVigil',0,12,56,1,10,0,-9101.04,-1034.1,72.9837,5.20227,'698:0 699:0 700:0 701:0 '), +(798,'DWPExitW',0,41,2697,50,60,0,-10464.2,-1734.78,86.7801,1.49361,'785:0 171:0 802:0 '), +(799,'DeadwindRavineSW',0,41,2558,50,60,0,-10919.4,-1957.74,114.777,4.67839,'171:0 800:0 '), +(800,'TheViceW',0,41,2561,50,60,0,-10843.3,-2118.87,121.161,1.51324,'799:0 172:0 79:0 '), +(801,'DWPExitE',0,41,2938,50,60,0,-10593.5,-2125.78,90.9212,3.83802,'172:0 170:0 802:0 811:0 '), +(802,'DeadsmansCrossingE',0,41,41,50,60,0,-10437,-2037.61,94.6245,1.59492,'798:0 801:0 810:0 '), +(803,'GroshgokCompoundInside',0,41,2937,50,60,0,-11169.4,-2483.11,105.139,0.944598,'79:0 '), +(804,'KarazhanOutskirts',0,41,2562,50,60,0,-11148.9,-2130,55.9803,1.2403,'79:0 805:0 806:0 '), +(805,'Karazhan',0,41,2562,50,60,0,-11115.2,-2008.72,48.4017,4.31317,'804:0 806:0 '), +(806,'KarazhanCellarEntrance',0,41,2837,50,60,0,-11173.4,-2033.69,47.0759,0.798522,'804:0 805:0 807:0 '), +(807,'KarazhanCellar1',0,41,2837,50,60,0,-11100.4,-1963.77,1.93936,4.49657,'806:0 808:0 '), +(808,'KarazhanCellar2',0,41,2837,50,60,0,-11159.5,-1898.41,-17.7918,6.21463,'807:0 809:0 '), +(809,'KarazhanCellar3',0,41,2837,50,60,0,-11033.9,-1910.04,-32.9889,4.98353,'808:0 '), +(810,'DeadmansCrossingBottom',0,41,41,50,60,0,-10282.6,-2019.49,51.0186,2.21028,'802:0 '), +(811,'SSExitW',0,8,8,33,45,0,-10546.2,-2376.39,84.2413,1.87648,'801:0 812:0 '), +(812,'SSW1',0,8,8,33,45,0,-10381.8,-2424.26,52.7345,2.75809,'811:0 813:0 '), +(813,'SSW2',0,8,8,33,45,0,-10427.8,-2549.06,24.1947,5.68762,'812:0 814:0 830:0 '), +(814,'IthariusCaveOutside',0,8,1777,33,45,0,-10561.3,-2508.68,22.0709,5.83489,'813:0 815:0 816:0 '), +(815,'IthariusCave',0,8,1777,33,45,0,-10677.4,-2531.21,28.912,6.2649,'814:0 '), +(816,'SSCRW1',0,8,1780,33,45,0,-10387.5,-2710.03,21.6778,1.56036,'814:0 817:0 818:0 819:0 830:0 '), +(817,'MistyValley',0,8,116,33,45,0,-10110,-2413.91,29.9136,4.35049,'816:0 818:0 '), +(818,'SSW3',0,8,8,33,45,0,-10320.4,-2794.27,21.9908,1.07734,'817:0 816:0 819:0 820:0 829:0 830:0 '), +(819,'Harborage',0,8,657,33,45,2,-10113,-2807.71,22.1444,3.06047,'818:0 816:0 820:0 829:0 '), +(820,'SSN1',0,8,1798,33,45,0,-10099.4,-3266.23,20.4381,1.58785,'818:0 819:0 821:0 827:0 828:0 829:0 '), +(821,'SSN2',0,8,76,33,45,0,-9974.74,-3687.45,21.6788,2.09247,'820:0 822:0 823:0 827:0 828:0 829:0 '), +(822,'SSNE1',0,8,2403,33,45,0,-9634.53,-3969.85,0.0000146627,2.75613,'821:0 823:0 '), +(823,'SSNE2',0,8,300,33,45,0,-10034.8,-4306.49,1.78832,0.101491,'822:0 821:0 824:0 826:0 827:0 '), +(824,'SSE',0,8,300,33,45,0,-10512,-4371.71,6.04597,6.27469,'823:0 825:0 80:0 826:0 827:0 '), +(825,'SSSE',0,8,300,33,45,0,-11038.3,-4116.28,1.98503,5.82703,'186:0 824:0 80:0 '), +(826,'PoolOfTearsE',0,8,1778,33,45,0,-10444.5,-4087.57,23.9872,1.47592,'80:0 186:0 823:0 824:0 827:0 '), +(827,'PoolOfTearsN',0,8,8,33,45,0,-10147.3,-3802.05,22.1649,2.27113,'820:0 821:0 823:0 824:0 826:0 828:0 829:0 '), +(828,'PoolOfTearsW',0,8,8,33,45,0,-10377.4,-3577.61,22.0023,3.94206,'820:0 821:0 827:0 80:0 51:0 829:0 '), +(829,'SSC',0,8,1798,33,45,0,-10292.9,-3192.2,22.1489,5.69742,'818:0 819:0 820:0 821:0 827:0 828:0 51:0 830:0 '), +(830,'SSExitS',0,8,8,33,45,0,-10553.9,-3043.33,24.794,0.0857489,'813:0 816:0 818:0 829:0 51:0 831:0 '), +(831,'SSExitSS',0,4,4,43,54,0,-10690.8,-2978.8,37.8508,5.87412,'830:0 834:0 '), +(832,'StagalbogInside1',0,8,1817,33,45,0,-10966.8,-3697.03,11.0105,4.21105,'80:0 833:0 '), +(833,'StagalbogInside2',0,8,1817,33,45,0,-10894.6,-3613.42,16.0499,6.1176,'832:0 '), +(834,'BLExitN',0,4,4,43,54,0,-10811.4,-2992.21,41.134,0.16765,'831:0 835:0 839:0 846:0 '), +(835,'BLNW1',0,4,1437,43,54,0,-11009.6,-2785.5,4.70615,5.63206,'834:0 836:0 839:0 840:0 '), +(836,'DreadmaulHoldEntrance',0,4,1437,43,54,0,-10916.9,-2714.76,7.63624,3.85903,'835:0 837:0 838:0 '), +(837,'DreadmaulHoldRight',0,4,1437,43,54,0,-10814.2,-2705.35,8.14439,3.20125,'836:0 838:0 '), +(838,'DreadmaulHoldLeft',0,4,1437,43,54,0,-10859.8,-2615.61,8.07541,4.52661,'836:0 837:0 '), +(839,'BLNW2',0,4,4,43,54,0,-11080.5,-2903.3,9.18506,0.371865,'835:0 834:0 196:0 '), +(840,'BLW1',0,4,4,43,54,0,-11268.2,-2725.08,11.1047,5.91718,'835:0 841:0 842:0 '), +(841,'DreadmaulPost',0,4,1439,43,54,0,-11528.8,-2858.71,8.50437,6.19557,'840:0 196:0 842:0 856:0 '), +(842,'BLAltarOfStormsBottom',0,4,4,43,54,0,-11501.6,-2718.85,5.65372,0.900431,'840:0 841:0 843:0 856:0 '), +(843,'BLAltarOfStormsMid',0,4,4,47,54,0,-11381.5,-2566.03,75.8033,3.74358,'842:0 844:0 '), +(844,'BLAltarOfStormsTop',0,4,1441,47,54,1,-11255.9,-2556.41,97.0494,2.9896,'843:0 '), +(845,'BLE1',0,4,4,43,54,0,-11149.6,-3232.6,8.07553,2.30354,'196:0 846:0 850:0 '), +(846,'NethergardeOutside',0,4,1438,43,54,0,-10961.7,-3200.5,45.5708,0.954623,'834:0 845:0 847:0 '), +(847,'NethergardeInside1',0,4,1438,43,54,2,-11008.9,-3340.48,64.7225,4.78147,'846:0 848:0 '), +(848,'NethergardeInside2',0,4,1438,43,54,3,-10993.2,-3453.24,64.8707,3.21459,'847:0 849:0 '), +(849,'NethergardeInside3',0,4,1438,43,54,2,-11112.2,-3438.11,79.0946,6.1402,'848:0 '), +(850,'BLE2',0,4,1440,43,54,0,-11310.9,-3410.18,7.46828,4.16649,'845:0 851:0 852:0 '), +(851,'BLE2Inside',0,4,1440,43,54,0,-11225.8,-3486.48,8.69295,2.17551,'850:0 '), +(852,'BLE3',0,4,4,43,54,0,-11438.8,-3304.34,7.30604,5.76674,'196:0 850:0 853:0 854:0 '), +(853,'BLSE',0,4,72,43,54,0,-11632.5,-3381.23,14.577,0.215935,'852:0 854:0 855:0 '), +(854,'BLS1',0,4,4,43,54,0,-11607.8,-3099.85,7.81021,5.30924,'196:0 852:0 853:0 855:0 '), +(855,'BLS2',0,4,72,43,54,0,-11765.8,-2959.84,7.91609,5.25623,'853:0 854:0 856:0 2370:0 '), +(856,'BLS3',0,4,4,43,54,0,-11716.6,-2785.99,8.27783,3.20635,'855:0 841:0 842:0 857:0 '), +(857,'TaintedScar1',0,4,73,55,60,0,-11907.8,-2658.64,-2.15637,5.48008,'856:0 '), +(858,'SVExitN',0,33,33,33,45,0,-11362.1,-380.82,64.9035,0.170793,'768:0 859:0 '), +(859,'SVExitNFork',0,33,33,33,45,0,-11397.1,-284.376,58.1739,3.29667,'858:0 860:0 861:0 '), +(860,'RebelCamp',0,33,99,33,45,3,-11314.2,-182.813,75.1397,3.66777,'859:0 '), +(861,'SVEntranceN',0,33,33,33,45,0,-11511.9,-302.586,38.7986,0.353382,'859:0 863:0 864:0 862:0 '), +(862,'NesingwarysExpeditionCamp',0,33,100,33,45,1,-11616,-50.0157,10.9823,4.49439,'861:0 879:0 880:0 881:0 901:0 902:0 907:0 '), +(863,'SVNBridge1N',0,33,33,33,45,0,-11604.1,-282.333,37.221,5.64107,'861:0 879:0 '), +(864,'SVNHubN1',0,33,33,33,45,0,-11643.7,-473.703,17.1608,0.3141,'861:0 865:0 873:0 '), +(865,'KurzensCompound',0,33,101,33,45,0,-11604,-644.056,29.157,3.77181,'864:0 866:0 872:0 '), +(866,'TheStockpile1',0,33,106,33,45,0,-11461.5,-750.28,30.6105,4.24893,'865:0 867:0 '), +(867,'TheStockpile2',0,33,106,33,45,0,-11514.8,-845.839,22.0076,3.08261,'866:0 868:0 869:0 '), +(868,'TheStockpile3',0,33,106,33,45,0,-11418.4,-794.929,14.9653,3.89354,'867:0 '), +(869,'TheStockpile4',0,33,106,33,45,0,-11506.4,-946.763,29.2275,0.414209,'867:0 870:0 '), +(870,'TheStockpile5',0,33,106,33,45,0,-11406.8,-896.737,18.0813,0.89682,'869:0 871:0 '), +(871,'TheStockpile6',0,33,106,33,45,0,-11337.1,-985.396,27.3203,1.9202,'870:0 '), +(872,'SVNHubN2',0,33,33,33,45,0,-11729.7,-799.127,29.6214,0.89486,'865:0 873:0 875:0 913:0 '), +(873,'VentureCoBaseCamp',0,33,1760,33,45,0,-11959.6,-531.751,11.3978,5.11834,'872:0 864:0 874:0 875:0 913:0 '), +(874,'SVNHub1',0,33,33,33,45,0,-12225,-546.002,28.8839,0.0407319,'873:0 875:0 911:0 913:0 915:0 '), +(875,'SVE1',0,33,33,33,45,0,-12149.1,-965.968,32.4681,1.43677,'872:0 873:0 874:0 876:0 878:0 913:0 '), +(876,'MoshoggOgreMound',0,33,105,33,45,0,-12352.7,-972.262,13.1171,5.38338,'875:0 877:0 878:0 913:0 '), +(877,'MoshoggOgreMoundInside',0,33,105,33,45,0,-12364,-1147.48,0.104103,2.92311,'876:0 '), +(878,'MoshoggOgreMoundUpper',0,33,105,33,45,0,-12466.5,-881.644,39.1084,5.72306,'875:0 876:0 914:0 918:0 '), +(879,'SVNBridge1C',0,33,33,33,45,0,-11710.1,-210.005,39.5643,5.90174,'863:0 880:0 862:0 '), +(880,'SVNBridge1S',0,33,33,33,45,0,-11818.3,-43.4252,39.7487,5.27146,'879:0 862:0 903:0 905:0 907:0 '), +(881,'SVNHubW1',0,33,33,33,45,0,-11518.9,255.102,25.1192,4.23669,'862:0 882:0 883:0 901:0 '), +(882,'SVNHubW2',0,33,33,33,45,0,-11502.5,373.117,53.2392,3.3217,'881:0 883:0 '), +(883,'ZulKundaNE',0,33,33,33,45,0,-11650.7,401.397,42.8581,5.68183,'881:0 882:0 884:0 889:0 890:0 '), +(884,'ZulKundaE',0,33,33,33,45,0,-11785.5,436.17,47.4037,6.2807,'883:0 885:0 889:0 899:0 '), +(885,'ZulKundaSE',0,33,33,33,45,0,-11863.3,560.263,47.0214,5.06137,'884:0 886:0 887:0 889:0 '), +(886,'ZulKundaS',0,33,33,33,45,0,-11831.5,711.922,45.1329,4.4252,'885:0 888:0 '), +(887,'SavageCoastNW1',0,33,301,33,45,0,-11924.1,793.032,3.06387,4.97496,'885:0 894:0 185:0 897:0 898:0 '), +(888,'ZulKunda1',0,33,102,33,45,0,-11690.4,742.837,49.7495,3.41987,'886:0 889:0 891:0 '), +(889,'ZulKunda2',0,33,102,33,45,0,-11694.8,565.959,49.7011,1.34053,'883:0 884:0 885:0 888:0 890:0 891:0 '), +(890,'ZulKunda3',0,33,102,33,45,0,-11550.4,601.183,50.5784,3.11945,'883:0 889:0 891:0 '), +(891,'ZulKunda4',0,33,102,33,45,0,-11622.9,760.15,39.7329,0.007312,'888:0 889:0 890:0 892:0 893:0 '), +(892,'ZulKunda5',0,33,102,33,45,0,-11528.7,724.898,59.4101,3.50037,'891:0 '), +(893,'ZulKundaW',0,33,122,33,45,0,-11572.6,840.524,8.14386,2.72086,'891:0 894:0 '), +(894,'SavageCoastNW2',0,33,122,33,45,0,-11692.5,954.651,3.70656,5.00441,'893:0 887:0 185:0 897:0 '), +(895,'YojambaIsleW',0,33,3357,33,45,0,-11810.6,1364.62,0.0261903,4.46053,'185:0 '), +(896,'YojambaIsleN',0,33,3357,33,45,0,-11748.3,1318.44,5.22585,2.37531,'185:0 '), +(897,'VileReefIsle',0,33,301,33,45,0,-12154.2,871.091,18.6659,5.996,'894:0 887:0 '), +(898,'SavageCoastW1',0,33,301,33,45,0,-12016.1,440.588,3.35169,0.661185,'887:0 899:0 900:0 '), +(899,'SVNWHub1',0,33,33,33,45,0,-11888.7,290.463,12.6077,0.998902,'898:0 884:0 900:0 901:0 902:0 903:0 '), +(900,'SavageCoastW2',0,33,33,33,45,0,-12196.8,238.239,2.19021,4.81004,'898:0 899:0 904:0 37:0 '), +(901,'SVNHunW3',0,33,100,33,45,0,-11656.6,59.5536,17.3151,1.50941,'899:0 881:0 862:0 902:0 '), +(902,'TkashiRuins',0,33,126,33,45,0,-11843.2,59.0602,14.1655,3.34528,'899:0 901:0 862:0 903:0 905:0 '), +(903,'KalaiRuins',0,33,125,33,45,0,-12069.3,66.3927,-5.18214,3.5436,'902:0 899:0 880:0 904:0 906:0 '), +(904,'SVNHub2',0,33,33,33,45,0,-12283.3,28.601,18.2879,0.246879,'903:0 900:0 37:0 912:0 '), +(905,'SVNBridge2N',0,33,33,33,45,0,-11907.9,-50.0313,39.7259,0.870484,'902:0 880:0 906:0 907:0 '), +(906,'SVNBridge2S',0,33,33,33,45,0,-12090.1,-139.679,35.2928,0.399245,'903:0 905:0 910:0 '), +(907,'SVCHub1',0,33,33,33,45,0,-11853.5,-167.599,15.3333,0.51312,'862:0 880:0 905:0 '), +(908,'SavageCoastW3',0,33,301,33,45,0,-12543.1,74.9728,0.873061,0.513523,'37:0 912:0 '), +(909,'SavageCoastW4',0,33,1578,33,45,0,-12691.2,142.464,3.092,5.80512,'921:0 924:0 925:0 936:0 '), +(910,'SVNBridge3W',0,33,33,33,45,0,-12171.4,-240.719,29.8621,0.766786,'906:0 911:0 912:0 '), +(911,'SVNBridge3E',0,33,33,33,45,0,-12157.4,-431.52,30.3485,1.45204,'874:0 910:0 913:0 '), +(912,'MizjahRuins',0,33,129,33,45,0,-12468.7,-147.093,13.8431,5.95827,'904:0 908:0 910:0 916:0 '), +(913,'SVNHub3',0,33,1740,33,45,0,-12127.6,-649.027,14.8876,2.30225,'874:0 876:0 911:0 873:0 875:0 872:0 '), +(914,'BaliamahRuins',0,33,127,33,45,0,-12540.7,-734.478,39.4424,6.20568,'878:0 915:0 917:0 918:0 '), +(915,'SVNHub4',0,33,33,33,45,0,-12417,-580.502,11.0755,6.10556,'914:0 874:0 916:0 '), +(916,'SVNHub5',0,33,33,33,45,0,-12522,-370.721,12.7107,0.882666,'912:0 915:0 917:0 921:0 '), +(917,'ZiatajaiRuins',0,33,128,33,45,0,-12701.1,-464.07,30.0552,6.19787,'914:0 916:0 918:0 920:0 '), +(918,'ZulMamweN',0,33,33,33,45,0,-12776.9,-784.489,63.0348,0.421265,'878:0 914:0 917:0 919:0 '), +(919,'ZulMamweC',0,33,103,33,45,0,-12985.7,-833.459,69.9343,0.193503,'918:0 920:0 '), +(920,'ZulMamweW',0,33,103,33,45,0,-12943.6,-608.837,53.0521,6.05178,'917:0 919:0 '), +(921,'SVSHub1',0,33,33,33,45,0,-12826,-301.024,9.96047,5.46666,'916:0 922:0 909:0 925:0 '), +(922,'SVSVentureCoMine1',0,33,33,33,45,0,-12978,-452.477,53.6008,5.60214,'921:0 923:0 '), +(923,'SVSVentureCoMine2',0,33,33,33,45,0,-13088.9,-466.483,47.2022,3.61705,'922:0 '), +(924,'GurubashiArenaOuterN',0,33,1577,33,45,0,-12949,251.702,18.4183,2.2214,'909:0 937:0 '), +(925,'STCHub1',0,33,1577,33,45,0,-13136.8,-184.248,-3.10173,5.92455,'909:0 921:0 926:0 929:0 '), +(926,'CrystalveinMine1',0,33,310,33,45,0,-13322.7,-420.202,15.4509,2.04861,'925:0 927:0 928:0 929:0 '), +(927,'CrystalveinMine2',0,33,310,33,45,0,-13158.5,-564.518,4.64271,0.568135,'926:0 928:0 '), +(928,'CrystalveinMine3',0,33,310,33,45,0,-13170,-467.696,3.57551,3.1305,'926:0 927:0 '), +(929,'STCHub2',0,33,1577,33,45,0,-13246.3,-110.492,19.5979,5.04296,'926:0 925:0 930:0 '), +(930,'GurubashiOuterSE',0,33,1741,33,45,0,-13280.6,57.3154,17.1498,4.95263,'929:0 931:0 932:0 933:0 934:0 '), +(931,'GurubashiOuterSSE',0,33,1741,33,45,0,-13402.5,96.0163,23.7905,6.03724,'930:0 932:0 933:0 934:0 935:0 943:0 944:0 '), +(932,'RuinsOfJubuwal',0,33,477,33,45,0,-13382.8,-24.5642,22.0332,0.894086,'930:0 931:0 '), +(933,'GurubashiArenaInside1',0,33,2177,33,45,0,-13216,312.587,21.8574,3.50161,'930:0 931:0 934:0 '), +(934,'GurubashiArenaInside2',0,33,2177,33,45,0,-13163.8,257.166,21.8574,3.62531,'930:0 931:0 933:0 '), +(935,'STCHub3',0,33,1577,33,45,0,-13475.6,312.292,31.942,5.22555,'931:0 938:0 942:0 '), +(936,'SSavageCoast1',0,33,1578,33,45,0,-12862.3,459.666,6.403,4.74253,'909:0 937:0 '), +(937,'GurubashiArenaOuterW',0,33,1741,33,45,0,-13076.6,428.328,24.6507,6.12678,'924:0 936:0 938:0 '), +(938,'GurubashiArenaOuterSW',0,33,1577,33,45,0,-13273.2,479.65,3.7542,5.77924,'935:0 937:0 939:0 940:0 '), +(939,'BloodsailCompoundW',0,33,1739,33,45,0,-13331.9,777.87,2.16424,3.85895,'938:0 940:0 941:0 '), +(940,'BloodsailCompoundC',0,33,1739,33,45,0,-13470.3,687.069,8.46535,3.18977,'938:0 939:0 941:0 '), +(941,'SSavageCoast2',0,33,1578,33,45,0,-13705.9,620.54,10.152,3.17013,'939:0 940:0 966:0 968:0 '), +(942,'STCHub4',0,33,1577,33,45,0,-13618.7,330.576,43.7429,5.59271,'935:0 943:0 968:0 '), +(943,'STCHub5',0,33,1577,33,45,0,-13719.1,129.331,23.7103,3.72188,'931:0 942:0 944:0 964:0 965:0 '), +(944,'STCHub6',0,33,1577,33,45,0,-13572.2,-93.8439,42.7481,1.44857,'931:0 943:0 945:0 '), +(945,'RuinsOfAboraz',0,33,311,33,45,0,-13627.7,-351.772,12.434,1.10692,'944:0 946:0 '), +(946,'CrystalShore1',0,33,302,33,45,0,-13867.2,-89.5993,18.4363,2.92315,'945:0 947:0 964:0 '), +(947,'CrystalShore2',0,33,302,33,45,0,-14086.6,-142.781,3.55938,1.30916,'946:0 948:0 '), +(948,'WildShore1',0,33,43,33,45,0,-14260.2,-15.7846,3.81308,5.84287,'947:0 949:0 '), +(949,'WildShore2',0,33,43,33,45,0,-14293.2,109.305,7.5176,0.82025,'948:0 950:0 962:0 '), +(950,'WildShore3',0,33,43,33,45,0,-14585.8,177.319,2.91916,6.15507,'949:0 951:0 952:0 955:0 958:0 '), +(951,'WildShore4',0,33,43,33,45,0,-14708.6,511.328,2.52661,5.16547,'950:0 952:0 '), +(952,'WildShoreShips1',0,33,43,33,45,0,-14894,302.458,3.67936,4.08476,'950:0 951:0 953:0 954:0 955:0 '), +(953,'WildShoreShips1Left',0,33,43,33,45,0,-14940,357.129,0.221882,0.0694,'952:0 '), +(954,'WildShoreShips1Right',0,33,43,33,45,0,-15009.9,266.91,0.198072,0.0485881,'952:0 '), +(955,'WildShoreShips2',0,33,43,33,45,0,-14843.8,75.9812,2.07268,0.570875,'950:0 952:0 956:0 957:0 '), +(956,'WildShoreShips2Center',0,33,43,33,45,0,-14926.8,110.764,0.2001,4.9102,'955:0 '), +(957,'JagueroIsle1',0,33,297,33,45,0,-14817.1,-427.547,1.62653,0.804531,'955:0 958:0 '), +(958,'JagueroIsle2',0,33,297,33,45,0,-14556.9,-277.709,10.043,3.68499,'950:0 957:0 '), +(959,'BootyBayW',0,33,35,33,45,0,-14300.4,523.178,8.69884,4.45859,'36:0 960:0 '), +(960,'BootyBayS',0,33,35,33,45,1,-14462.8,467.081,15.1246,5.4423,'36:0 959:0 '), +(961,'BootyBayEntrance',0,33,1577,33,45,0,-14249.5,333.168,24.6767,2.49077,'36:0 962:0 967:0 '), +(962,'STCHub7',0,33,1577,33,45,0,-14217.2,239.309,20.2817,3.77098,'949:0 961:0 963:0 '), +(963,'STCHub8',0,33,1577,33,45,0,-14073.6,266.586,17.42,5.16308,'962:0 964:0 965:0 '), +(964,'MistvaleValley',0,33,1737,33,45,0,-13952.8,86.1666,15.8298,5.2161,'76:0 943:0 946:0 963:0 '), +(965,'STCHub9',0,33,1577,33,45,0,-13879.6,258.837,17.7662,5.83459,'943:0 963:0 '), +(966,'SSavageCoast3',0,33,1578,33,45,0,-13908.6,676.67,10.0579,5.32409,'967:0 941:0 969:0 '), +(967,'SSavageCoast4',0,33,1578,33,45,0,-14050.8,500.109,2.8225,0.635261,'961:0 966:0 '), +(968,'SSavageCoast5',0,33,1578,33,45,0,-13683.6,505.174,34.349,3.2526,'941:0 942:0 969:0 970:0 '), +(969,'SSavageCoast6',0,33,1577,33,45,0,-13859.9,575.635,44.9464,6.06235,'966:0 968:0 '), +(970,'SSavageCoast7',0,33,1577,33,45,0,-13849.3,494.156,89.7643,5.84244,'968:0 971:0 '), +(971,'SSavageCoast8',0,33,1738,33,45,0,-13806.7,377.83,94.1372,2.10198,'970:0 '), +(972,'Shadowglen',1,141,188,1,4,2,10281,1001.05,1342.98,5.96933,'33:0 62:0 '), +(973,'ShadowthreadCaveInside',1,141,257,1,10,2,10936.3,951.018,1322.87,0.852457,'62:0 '), +(974,'ShadowglenExit1',1,141,188,1,10,2,10236.2,699.749,1353.3,3.24203,'33:0 975:0 '), +(975,'ShadowglenExit2',1,141,141,1,10,2,9988.41,591.889,1315.9,0.829698,'974:0 976:0 983:0 '), +(976,'StarbreezeVillage',1,141,260,1,10,2,9825.13,399.226,1308.15,0.857567,'975:0 977:0 978:0 '), +(977,'StarbreezeBackyard',1,141,141,1,10,2,10038.9,286.037,1323.77,1.89429,'976:0 '), +(978,'TeldrassilS1',1,141,141,1,10,2,9396.17,568.108,1320.55,0.244948,'976:0 979:0 '), +(979,'TeldrassilS2',1,141,141,1,10,2,9232.75,931.478,1321.37,5.94693,'978:0 980:0 '), +(980,'TeldrassilS3',1,141,141,1,10,2,9171.08,1419.38,1320.31,5.13404,'979:0 981:0 '), +(981,'TeldrassilHubS1',1,141,141,1,10,2,9352.14,1496.95,1274.46,0.140488,'980:0 982:0 34:0 988:0 '), +(982,'GnarlpineHold',1,141,261,5,10,2,9106.12,1796.37,1324.94,4.59408,'981:0 '), +(983,'DolanaarRoadE',1,141,141,1,10,2,9883.36,802.735,1309.84,1.38768,'975:0 63:0 34:0 '), +(984,'FelRockInside',1,141,258,1,10,2,10112.3,1195.5,1312.02,5.06294,'63:0 '), +(985,'DolanaarRoadW',1,141,141,1,10,2,9956.16,1248.6,1307.74,2.3227,'34:0 64:0 989:0 '), +(986,'BanethilBarrowDenInside',1,141,262,4,10,2,9759.16,1553.23,1264.26,3.07786,'64:0 '), +(987,'TeldrassilHubW1',1,141,141,1,10,2,10032.9,1820.23,1320.1,4.58073,'64:0 988:0 989:0 990:0 992:0 '), +(988,'PoolsOfArlithrien',1,141,478,1,10,2,9596.69,1802.05,1293.91,5.64964,'981:0 987:0 '), +(989,'TeldrassilHubW2',1,141,141,1,10,2,10162.3,1440.21,1325.14,3.89821,'985:0 987:0 '), +(990,'OracleGladeRoad',1,141,141,4,10,2,10352,1874.33,1324.48,6.02465,'987:0 991:0 '), +(991,'OracleGlade',1,141,264,1,10,2,10884.7,2071.72,1327.43,2.94589,'990:0 '), +(992,'DarnassusEntrance',1,1657,1657,1,60,3,9945.1,2019.66,1351.87,4.64235,'987:0 '), +(993,'AuberdineSpawn',1,148,442,8,20,3,6553.45,522.918,11.8561,3.67005,'59:0 994:0 '), +(994,'LongWash1',1,148,453,8,20,2,6232.15,537.866,7.35703,5.18,'59:0 993:0 995:0 1008:0 '), +(995,'LongWash2',1,148,2077,8,20,2,5552.17,453.898,19.6886,6.27955,'994:0 996:0 1005:0 1008:0 '), +(996,'TwilightShore1',1,148,2078,14,20,2,5012.98,536.041,5.96794,4.54499,'995:0 997:0 1005:0 95:0 '), +(997,'TwilightShore2',1,148,2078,14,20,2,4688.48,865.878,2.79823,2.76606,'996:0 998:0 999:0 '), +(998,'TwilightShore3',1,148,2078,14,20,2,4503.92,906.145,4.30626,5.45958,'997:0 1002:0 1038:0 '), +(999,'RemtravelsExcavation',1,148,450,8,20,2,4714.44,550.759,26.823,1.95474,'997:0 1000:0 95:0 94:0 '), +(1000,'RemtravelsExcavationInside1',1,148,450,14,20,2,4620.8,642.65,6.43919,5.10026,'999:0 1001:0 '), +(1001,'RemtravelsExcavationInside2',1,148,450,14,20,2,4567.99,561.83,1.27184,0.69615,'1000:0 '), +(1002,'DarkshoreExitSW',1,148,2077,8,20,2,4243.34,412.704,58.3351,0.245327,'998:0 1003:0 1027:0 '), +(1003,'DarkshoreExitFork',1,148,2077,8,20,2,4395.16,213.188,52.2559,2.29326,'1002:0 1004:0 94:0 1006:0 '), +(1004,'DarkshoreExitSE',1,148,2077,8,20,2,4141.67,41.4988,23.6074,3.67163,'1003:0 1028:0 '), +(1005,'WindbendRiverN',1,148,2077,8,20,2,5261.19,254.236,30.0406,3.18665,'995:0 996:0 95:0 1008:0 1009:0 '), +(1006,'MastersGlaiveRoad',1,148,2077,8,20,2,4568.13,285.307,57.6068,1.63547,'94:0 95:0 1003:0 1007:0 '), +(1007,'MastersGlaive',1,148,449,8,20,2,4541.58,449.011,32.0175,5.01071,'1006:0 '), +(1008,'AmetharanRoad',1,148,2077,8,20,2,5720.14,311.438,20.1904,4.41184,'59:0 994:0 995:0 1005:0 1009:0 1010:0 '), +(1009,'Ametharan',1,148,447,8,20,2,5586.01,84.3345,27.1266,1.57459,'1005:0 1008:0 1010:0 '), +(1010,'AmetharanN',1,148,447,8,20,2,5801.69,83.785,34.5622,2.98046,'59:0 1008:0 1009:0 1011:0 '), +(1011,'DarkshoreMoonkins',1,148,148,8,20,2,6267.1,23.9018,32.4976,3.19251,'59:0 1010:0 1012:0 1013:0 '), +(1012,'DarkshoreNRoad1',1,148,148,8,20,2,6568,183.3,33.0481,3.37511,'59:0 1011:0 1013:0 1014:0 '), +(1013,'BashalAran',1,148,446,8,20,2,6724.32,-182.155,32.4813,2.68198,'1011:0 1012:0 1014:0 '), +(1014,'DarkshoreNRoad2',1,148,148,8,20,2,6818.81,102.669,19.5599,2.84101,'1012:0 1013:0 1015:0 '), +(1015,'DarkshoreNRoad3',1,148,148,8,20,2,6851.5,-74.339,19.6655,5.58399,'1014:0 1016:0 1022:0 '), +(1016,'DarkshoreNRoad4',1,148,148,8,20,2,7119.42,-81.8027,14.4199,3.09232,'1015:0 1017:0 1022:0 '), +(1017,'DarkshoreNRoad5',1,148,148,8,20,2,7256.24,-291.22,21.0422,4.79468,'1016:0 1018:0 1022:0 '), +(1018,'DarkshoreNRoad6',1,148,148,8,20,2,7251.65,-503.552,27.2626,1.57453,'1017:0 1019:0 1023:0 1026:0 1024:0 '), +(1019,'CliffspringFalls1',1,148,445,8,20,2,6945.23,-647.544,58.2647,0.698808,'1018:0 96:0 1022:0 '), +(1020,'CliffspringFallsInside1',1,148,445,8,20,2,6751.15,-678.916,69.6518,0.322209,'96:0 '), +(1021,'CliffspringFallsInside2',1,148,445,8,20,2,6801.08,-758.228,69.6369,1.04478,'96:0 '), +(1022,'CliffspringRiverCamp',1,148,456,8,20,2,6874.54,-444.951,41.5568,5.10331,'1015:0 1016:0 1017:0 1019:0 '), +(1023,'TowerOfAlthalaxx',1,148,444,14,20,2,7134.02,-794.131,67.6245,1.1665,'1018:0 1024:0 '), +(1024,'RuinsOfMathystra1',1,148,443,14,20,2,7266.69,-945.767,35.1327,5.29378,'1018:0 1023:0 1025:0 1026:0 '), +(1025,'RuinsOfMathystra2',1,148,443,14,20,2,7600.47,-1097.96,49.7759,2.73731,'1026:0 1024:0 '), +(1026,'RuinsOfMathystra3',1,148,443,14,20,2,7493.36,-767.521,12.9545,3.62677,'1018:0 1025:0 1024:0 '), +(1027,'ZoramStrandFork',1,331,331,18,30,2,3817.2,679.137,5.10897,5.73165,'1002:0 1035:0 1036:0 1039:0 '), +(1028,'AshenvaleNW_hub1',1,331,331,18,30,0,3974.7,-4.75098,16.9651,0.475045,'157:0 1004:0 1029:0 '), +(1029,'AshenvaleNW_hub2',1,331,331,18,30,0,3657.91,209.708,2.09702,5.65554,'156:0 1028:0 1030:0 1031:0 '), +(1030,'AshenvaleNW_hub3',1,331,331,18,30,0,3467.65,456.674,-1.34147,5.28246,'1029:0 1031:0 1032:0 1033:0 1035:0 '), +(1031,'AshenvaleNW_hub4',1,331,413,18,30,0,3455.92,219.392,12.6662,4.49903,'156:0 1029:0 1030:0 1033:0 '), +(1032,'AshenvaleNW_hub5',1,331,441,18,30,0,3249.97,704.667,5.38086,5.25498,'1030:0 1034:0 '), +(1033,'MaestrasPost',1,331,413,18,30,0,3259.03,232.17,11.012,5.89391,'156:0 1030:0 1031:0 1034:0 1040:0 '), +(1034,'LakeFalathim',1,331,441,18,30,0,3042.8,537.807,4.22352,5.78789,'1032:0 1033:0 '), +(1035,'ZoramStrandExit',1,331,331,18,30,0,3657.05,656.795,4.96231,0.51393,'158:0 1027:0 1030:0 1036:0 '), +(1036,'ZoramStrandC',1,331,414,18,30,0,3801.56,849.009,0.513784,2.44601,'158:0 1027:0 1035:0 1037:0 1039:0 '), +(1037,'ZoramStrandS',1,331,414,18,30,0,3530.82,1106.94,3.5186,5.37358,'158:0 1036:0 '), +(1038,'ZoramStrandNWIsle',1,331,414,18,30,0,4170.75,1326.83,19.2012,4.42522,'998:0 1039:0 '), +(1039,'ZoramStrandN',1,331,414,18,30,0,4069.2,824.531,3.14713,3.15877,'1027:0 1036:0 1038:0 '), +(1040,'MaestraHighway1',1,331,413,18,30,0,3098.29,226.932,38.2729,0.113369,'1033:0 1041:0 '), +(1041,'MaestraHighway2',1,331,413,18,30,0,3010.21,148.647,65.398,0.16052,'1040:0 1042:0 '), +(1042,'MaestraHighway3',1,331,331,18,30,0,2859.2,196.478,94.7169,5.92141,'1041:0 1043:0 '), +(1043,'MaestraHighway4',1,331,331,18,30,0,2812.81,41.9868,95.6736,1.28951,'1042:0 1044:0 1058:0 '), +(1044,'ThistlefurVillageExit',1,331,331,18,30,0,2868.2,-73.4622,101.174,1.57029,'1043:0 1045:0 1046:0 1056:0 1058:0 '), +(1045,'ThistlefurVillageRoad1',1,331,331,18,30,0,3135.26,-172.008,106.895,2.338,'1044:0 1046:0 1047:0 1051:0 '), +(1046,'ThistlefurVillageRoad2',1,331,331,18,30,0,3105.59,-62.1211,99.9184,3.67319,'1044:0 1045:0 '), +(1047,'ThistlefurVillage1',1,331,2301,18,30,0,3406.44,-322.711,134.117,2.65215,'1045:0 1048:0 '), +(1048,'ThistlefurVillage2',1,331,2301,18,30,0,3405.57,-442.447,153.072,1.32875,'1047:0 1049:0 1051:0 '), +(1049,'ThistlefurVillage3',1,331,2301,18,30,0,3502.9,-501.921,187.011,2.83083,'1048:0 1050:0 '), +(1050,'ThistlefurVillage4',1,331,2301,18,30,0,3340.18,-745.556,165.112,0.56888,'1049:0 '), +(1051,'ThistlefurVillage5',1,331,331,18,30,0,3151.47,-574.826,152.496,0.537465,'1045:0 1048:0 1052:0 '), +(1052,'IrisLakeFork',1,331,424,18,30,0,2898.98,-769.452,163.783,4.51548,'1051:0 1053:0 1054:0 1055:0 '), +(1053,'IrisLake',1,331,424,18,30,0,2942.58,-1048.01,198.163,1.57223,'1052:0 '), +(1054,'AshenvaleC_hub1',1,331,331,18,30,0,2507.45,-866.571,135.152,0.193841,'1052:0 1055:0 1065:0 '), +(1055,'AshenvaleC_hub2',1,331,415,18,30,0,2438.14,-533.1,114.408,0.47461,'38:0 1052:0 1054:0 1057:0 1059:0 '), +(1056,'AshenvaleC_hub3',1,331,415,18,30,2,2850.97,-220.421,105.829,4.13457,'38:0 1044:0 '), +(1057,'AshenvaleC_hub4h',1,331,415,18,30,0,2496.08,-368.618,107.608,4.35054,'1055:0 1058:0 '), +(1058,'AshenvaleC_hub5h',1,331,415,18,30,0,2723.63,-97.6203,94.1401,0.260605,'1043:0 1044:0 1057:0 1060:0 '), +(1059,'RuinsOfStardust',1,331,418,18,30,0,2078.15,-208.159,97.3023,6.04115,'1055:0 1063:0 '), +(1060,'FireScarShrine1',1,331,417,18,30,0,2256.66,75.4186,101.15,2.42832,'1058:0 1061:0 '), +(1061,'FireScarShrine2',1,331,417,18,30,0,2216.37,200.114,132.147,0.0014348,'1060:0 1062:0 '), +(1062,'FireScarShrine3',1,331,417,18,30,0,2267.87,408.396,133.106,0.831985,'1061:0 '), +(1063,'MystralLakeW',1,331,421,18,30,0,2023,-843.478,97.5457,1.65663,'1064:0 1059:0 165:0 168:0 '), +(1064,'MystralLakeC',1,331,421,18,30,0,1984.95,-1113.44,95.5274,1.49563,'1063:0 165:0 1067:0 '), +(1065,'AshenvaleC_hub6',1,331,331,18,30,0,2450.24,-1258.76,125.028,2.45577,'1054:0 1066:0 1068:0 1071:0 '), +(1066,'AshenvaleC_hub7',1,331,331,18,30,0,2235.23,-1257.52,78.4907,4.80606,'166:0 1065:0 165:0 '), +(1067,'AshenvaleC_hub8',1,331,331,18,30,0,1948.15,-1576.73,62.1809,2.79343,'167:0 1064:0 1069:0 '), +(1068,'AshenvaleC_hub9',1,331,426,18,30,0,2312.84,-1688.24,121.859,1.16373,'166:0 1065:0 1072:0 1074:0 '), +(1069,'AshenvaleC_hub10',1,331,331,18,30,0,2066.34,-1671.01,66.4345,2.13565,'166:0 1067:0 1070:0 '), +(1070,'AshenvaleE_hub1',1,331,2457,18,30,0,2042.45,-1869.26,98.5045,1.0065,'1069:0 1074:0 1106:0 '), +(1071,'AshenvaleC_hub11',1,331,331,18,30,0,2610.58,-1273.73,147.484,1.45233,'1065:0 1072:0 '), +(1072,'AshenvaleC_hub12',1,331,426,18,30,0,2492.08,-1690.32,135.531,1.25401,'1068:0 1071:0 1073:0 '), +(1073,'AshenvaleC_hub13',1,331,2457,18,30,0,2524.32,-1885.69,143.053,1.82734,'1072:0 1074:0 1075:0 '), +(1074,'AshenvaleC_hub14',1,331,2457,18,30,0,2243.34,-1738.27,110.775,5.1594,'1068:0 1073:0 1070:0 '), +(1075,'AshenvaleExitN1',1,331,2457,18,30,0,2722.91,-1983,158.23,2.65788,'1073:0 1076:0 1078:0 '), +(1076,'AshenvaleExitN2',1,331,2457,18,30,0,3188.58,-1706.76,164.247,3.95184,'1075:0 1077:0 '), +(1077,'AshenvaleExitN3',1,331,2457,18,30,0,3297.65,-1543.44,164.945,5.56976,'1076:0 1118:0 '), +(1078,'AshenvaleC_hub14',1,331,2457,18,30,0,2434.66,-2007.27,137.863,5.44996,'159:0 1075:0 '), +(1079,'FalfarenRiverN1',1,331,433,18,30,0,2635.78,-2457.48,169.635,2.50866,'159:0 1080:0 1086:0 '), +(1080,'FalfarenRiverN2',1,331,433,18,30,0,2714.01,-2536.19,182.43,2.13757,'1079:0 1081:0 160:0 '), +(1081,'NightRunE',1,331,428,18,30,0,2790.65,-2428.46,206.042,3.93612,'1080:0 1082:0 160:0 '), +(1082,'NightRun',1,331,428,18,30,0,2635.93,-2273.51,202.702,5.29878,'1081:0 1083:0 1084:0 '), +(1083,'NightRunC',1,331,428,18,30,0,2508.34,-2113.14,200.284,6.10971,'1082:0 1084:0 '), +(1084,'NightRunW1',1,331,428,18,30,0,2796.44,-2157.58,201.403,3.2489,'1082:0 1083:0 '), +(1085,'ForestSongEntranceW',1,331,2358,18,30,0,2816.28,-3116.79,176.969,1.10278,'160:0 161:0 1088:0 1089:0 '), +(1086,'AshenvaleE_hub2',1,331,2457,18,30,0,2468.98,-2717.07,150.406,4.36767,'1079:0 1087:0 1114:0 '), +(1087,'AshenvaleE_hub3',1,331,2457,18,30,0,2544.03,-2863.47,160.596,1.768,'1086:0 1088:0 '), +(1088,'SatyrnaarEntrance',1,331,2457,18,30,0,2634.38,-3046.12,161.142,6.24673,'1085:0 1087:0 1089:0 1090:0 1096:0 '), +(1089,'Satyrnaar',1,331,430,18,30,0,2728.6,-2942.91,140.775,5.09219,'1085:0 1088:0 '), +(1090,'AshenvaleE_hub4',1,331,2457,18,30,0,2718.15,-3174.63,154.002,4.95083,'1088:0 1091:0 '), +(1091,'AshenvaleE_hub5',1,331,2457,18,30,0,2747.9,-3325.14,129.722,0.481917,'161:0 1090:0 1092:0 1096:0 1097:0 '), +(1092,'AshenvaleE_hub6',1,331,2457,18,30,0,2858.75,-3503.4,106.63,0.432834,'1091:0 1093:0 1095:0 '), +(1093,'BoughShadowEntrance',1,331,2358,18,30,0,3097.87,-3414.65,140.149,2.35511,'161:0 1092:0 1094:0 '), +(1094,'BoughShadow',1,331,438,50,60,0,3193.56,-3715.88,121.867,1.87405,'1093:0 '), +(1095,'AshenvaleExitE1',1,331,2457,18,30,0,2885.88,-3697.95,96.0233,1.45779,'1092:0 1269:0 '), +(1096,'WarsongLumberCampW1',1,331,437,18,30,0,2228.79,-3249.63,101.566,0.607582,'1088:0 1091:0 1097:0 1099:0 '), +(1097,'WarsongLumberCampE1',1,331,437,18,30,0,2451.74,-3379.23,102.331,4.70735,'1091:0 1096:0 1098:0 1356:0 '), +(1098,'KargathiaKeep',1,331,2637,18,30,2,2437.92,-3546.25,98.3118,2.20389,'1097:0 '), +(1099,'FelfireHillFork',1,331,434,18,30,0,2080.86,-3145.14,101.473,3.35095,'1116:0 162:0 1096:0 1356:0 '), +(1100,'DemonFallRidgeW1',1,331,436,18,30,0,1674.45,-3019.31,126.383,5.53436,'162:0 1101:0 '), +(1101,'DemonFallRidgeW2',1,331,436,18,30,0,1590.49,-2969.19,181.724,5.65806,'1100:0 1102:0 '), +(1102,'DemonFallRidgeW3',1,331,436,18,30,0,1540.54,-2824.95,181.724,2.53218,'1101:0 1103:0 '), +(1103,'DemonFallRidgeW4',1,331,436,18,30,0,1461.22,-2803.04,146.324,2.97199,'1102:0 '), +(1104,'DemonFallRidgeE1',1,331,435,18,30,0,1627.48,-3316.31,144.676,0.784629,'162:0 1105:0 '), +(1105,'DemonFallRidgeE2',1,331,435,18,30,0,1721.33,-3476.04,144.293,2.30437,'1104:0 '), +(1106,'AshenvaleE_hub7',1,331,433,18,30,0,1988.27,-1943.15,97.1296,1.1247,'1070:0 1107:0 '), +(1107,'AshenvaleE_hub8',1,331,2457,18,30,0,1962.79,-2012.8,96.2461,1.06579,'1106:0 1108:0 '), +(1108,'AshenvaleE_hub9',1,331,2457,18,30,0,1921.52,-2170.42,94.4681,2.67153,'1107:0 1109:0 1113:0 '), +(1109,'AshenvaleE_hub10',1,331,2457,18,30,0,1650,-2198.36,90.4962,0.191633,'163:0 1108:0 1110:0 1111:0 1112:0 '), +(1110,'AshenvaleExitSh',1,331,2457,18,30,0,1372.44,-2259.8,89.962,3.46083,'1111:0 1109:0 1112:0 169:0 1518:0 '), +(1111,'AshenvaleExitSa',1,17,1703,8,25,2,1283.04,-2337.27,95.7661,5.44005,'1109:0 1110:0 1519:0 '), +(1112,'AshenvaleE_hub11',1,331,422,18,30,0,1407.02,-2005.8,95.7095,5.15141,'1109:0 1110:0 '), +(1113,'SplintertreePostRoadW',1,331,431,18,30,0,1994.45,-2424.98,91.2477,1.28488,'164:0 175:0 1108:0 1114:0 '), +(1114,'SplintertreePostFork',1,331,2457,18,30,0,2176.5,-2659.1,114.31,6.03653,'175:0 1086:0 1113:0 1115:0 '), +(1115,'SplintertreePostRoadE',1,331,2457,18,30,0,2082.36,-2724.41,108.011,0.790059,'164:0 1114:0 1116:0 '), +(1116,'FelfireHillBridgeW',1,331,434,18,30,0,2039.97,-2914.67,108.448,1.43407,'1099:0 1115:0 1356:0 '), +(1117,'DorDanilarrowDenInside',1,331,432,18,30,0,1800.96,-2749.28,74.2595,4.55915,'164:0 '), +(1118,'FelwoodExitS',1,361,361,46,56,0,3743.81,-1481.83,196.177,3.29581,'1077:0 1119:0 '), +(1119,'EmeraldSanctuaryFork',1,361,2479,46,56,0,3880.67,-1320.46,216.16,2.5595,'1118:0 1120:0 1123:0 1124:0 '), +(1120,'DeadwoodVillage1',1,361,1761,46,56,0,3687.18,-1161.35,207.07,5.5283,'1119:0 1121:0 1122:0 '), +(1121,'DeadwoodVillage2',1,361,1761,46,56,0,3530.84,-1083.16,232.998,0.383932,'1120:0 1122:0 '), +(1122,'DeadwoodVillage3',1,361,361,46,56,0,3799.15,-1018.83,233.051,3.60798,'1120:0 1121:0 1124:0 1149:0 '), +(1123,'EmeraldSanctuary',1,361,2479,46,56,1,4008.37,-1323.24,254.74,2.01756,'1119:0 1124:0 '), +(1124,'FelwoodRoad1',1,361,361,46,56,0,3917.77,-1102.56,251.192,4.86854,'1119:0 1122:0 1123:0 1125:0 1149:0 '), +(1125,'FelwoodRoad2',1,361,361,46,56,0,4111.01,-1000.91,273.994,3.79844,'1124:0 1126:0 1149:0 1155:0 '), +(1126,'FelwoodRoad3',1,361,361,46,56,0,4217.57,-895.05,283.719,4.15774,'1125:0 1127:0 1149:0 1156:0 1157:0 1158:0 '), +(1127,'FelwoodRoad4',1,361,361,46,56,0,4284.53,-848.098,284.982,3.75914,'1126:0 1128:0 '), +(1128,'FelwoodRoad5',1,361,361,46,56,0,4421.12,-848.39,292.935,3.21525,'1127:0 1129:0 1157:0 '), +(1129,'FelwoodRoad6',1,361,361,46,56,0,4543.91,-853.393,298.894,3.13867,'1128:0 1130:0 '), +(1130,'FelwoodRoad7',1,361,361,46,56,0,4626.9,-811.164,299.948,3.55493,'1129:0 1131:0 1158:0 '), +(1131,'FelwoodRoad8',1,361,361,46,56,0,4816.57,-743.65,300.125,3.49799,'1130:0 1132:0 1162:0 '), +(1132,'FelwoodRoad9',1,361,361,46,56,0,4924.41,-736.286,307.085,3.21525,'1131:0 1133:0 1161:0 '), +(1133,'FelwoodRoad10',1,361,361,46,56,0,5067.29,-754.16,325.077,3.06602,'1132:0 1134:0 1162:0 '), +(1134,'FelwoodRoad11',1,361,361,46,56,0,5190.8,-721.312,342.065,3.32245,'1133:0 1135:0 1163:0 '), +(1135,'FelwoodRoad12',1,361,1765,46,56,0,5321.67,-714.318,345.44,3.1693,'1134:0 1136:0 1175:0 '), +(1136,'FelwoodRoad13',1,361,361,46,56,0,5528.6,-586.974,357.722,3.70533,'1135:0 1137:0 1175:0 1176:0 '), +(1137,'FelwoodRoad14',1,361,361,46,56,0,5829.53,-657.626,373.409,2.93956,'1136:0 1138:0 1176:0 1177:0 '), +(1138,'FelwoodRoad15',1,361,361,46,56,0,6212.38,-740.556,409.705,2.87084,'1137:0 1139:0 '), +(1139,'FelwoodRoad16',1,361,361,46,56,0,6248.94,-853.467,415.845,1.8714,'1138:0 1140:0 1180:0 1188:0 '), +(1140,'FelwoodRoad17',1,361,361,46,56,0,6316.22,-1000.95,421.045,1.77912,'1139:0 1141:0 1181:0 '), +(1141,'FelwoodRoad18',1,361,361,46,56,0,6517.96,-1128.86,435.787,2.40548,'1140:0 1142:0 1187:0 '), +(1142,'FelwoodRoad19',1,361,361,46,56,0,6589.42,-1238.14,446.438,2.19734,'1141:0 1143:0 1181:0 149:0 '), +(1143,'FelwoodRoad20',1,361,361,46,56,0,6642.07,-1424.29,466.141,1.83997,'1142:0 1144:0 149:0 1190:0 '), +(1144,'FelwoodRoad21',1,361,361,46,56,0,6582.2,-1624.29,494.577,1.32358,'1143:0 1145:0 1190:0 '), +(1145,'FelwoodRoad22',1,361,361,46,56,0,6510.95,-1745.71,510.835,1.13311,'1144:0 1146:0 1183:0 '), +(1146,'FelwoodRoad23',1,361,361,46,56,0,6528.61,-1901.05,539.009,1.63379,'1145:0 1147:0 1191:0 1193:0 '), +(1147,'FelwoodRoad24',1,361,361,46,56,0,6558.12,-2008.36,559.656,1.52776,'1146:0 1148:0 1193:0 '), +(1148,'FelwoodRoad25',1,361,361,46,56,0,6560.77,-2085.29,574.689,1.48063,'1147:0 1191:0 148:0 '), +(1149,'JadefireGlenEntrance',1,361,361,46,56,0,3926.58,-911.03,268.957,1.31568,'1122:0 1124:0 1125:0 1126:0 1150:0 1157:0 '), +(1150,'JadefireGlen1',1,361,2480,46,56,0,3811.45,-783.703,310.11,5.52543,'1149:0 1151:0 1152:0 1153:0 '), +(1151,'JadefireGlen2',1,361,2480,46,56,0,3718.85,-847.307,312.709,0.532263,'1150:0 '), +(1152,'JadefireGlen3',1,361,2480,46,56,0,3912.89,-762.669,314.637,3.43039,'1150:0 '), +(1153,'JadefireGlen4',1,361,2480,46,56,0,3856.5,-672.112,328.888,4.11173,'1150:0 1154:0 '), +(1154,'JadefireGlen5',1,361,2480,46,56,0,4036.91,-552.243,344.95,3.72492,'1153:0 '), +(1155,'FelwoodSide1_1',1,361,361,46,56,0,4213.32,-1147.96,320.929,1.94598,'1125:0 1156:0 '), +(1156,'FelwoodSide1_2',1,361,361,46,56,0,4310.85,-1068.85,323.86,2.16393,'1126:0 1155:0 '), +(1157,'FelwoodSide2_1',1,361,361,46,56,0,4140.08,-676.318,285.659,4.10583,'1126:0 1128:0 1149:0 '), +(1158,'RuinsOfConstellas1',1,361,2481,46,56,0,4508.94,-658.169,260.072,5.34478,'1126:0 1130:0 1159:0 '), +(1159,'RuinsOfConstellas2',1,361,2481,46,56,0,4578.21,-555.417,290.438,2.46629,'1158:0 1160:0 '), +(1160,'RuinsOfConstellas3',1,361,2481,46,56,0,4580.93,-222.976,301.195,4.57508,'1159:0 '), +(1161,'Jaedenar1',1,361,1763,46,56,0,4922.62,-656.651,308.014,3.98916,'1132:0 1162:0 '), +(1162,'Jaedenar2',1,361,1763,46,56,0,4883.95,-595.824,308.56,5.16136,'1131:0 1133:0 1161:0 1163:0 1165:0 1166:0 '), +(1163,'BloodvenomPostEntrance',1,361,361,46,56,0,5228.11,-538.087,328.617,3.14997,'1134:0 1162:0 1164:0 '), +(1164,'BloodvenomPost',1,361,1997,46,56,0,5222.93,-365.293,325.373,4.62848,'150:0 1163:0 '), +(1165,'ShrineOfTheDeceiver',1,361,1771,46,56,0,4783.38,-551.646,274.445,4.62258,'1162:0 '), +(1166,'Jaedenar3',1,361,1763,46,56,0,4729.93,-453.696,349.944,4.46628,'1162:0 1167:0 '), +(1167,'Jaedenar4',1,361,1763,46,56,0,4856.52,-382.228,350.288,4.8845,'1166:0 1168:0 '), +(1168,'ShadowHold1',1,361,1770,46,56,0,4986.27,-445.68,316.676,5.65694,'1167:0 1169:0 '), +(1169,'ShadowHold2',1,361,1770,46,56,0,5098.64,-485.812,296.677,4.68186,'1168:0 1170:0 '), +(1170,'ShadowHold3',1,361,1770,46,56,0,5178.54,-485.604,301.227,5.33099,'1169:0 1171:0 '), +(1171,'ShadowHold4',1,361,1770,46,56,0,5275.41,-671.399,253.46,3.4162,'1170:0 1172:0 '), +(1172,'ShadowHold5',1,361,1770,46,56,0,5339.64,-532.836,254.255,4.57192,'1171:0 1173:0 1174:0 '), +(1173,'ShadowHold6',1,361,1770,46,56,0,5427.58,-486.39,248.303,2.98345,'1172:0 '), +(1174,'ShadowHold7',1,361,1770,46,56,0,5435.52,-425.111,272.429,5.11384,'1172:0 '), +(1175,'ShatterScarVale1',1,361,1766,46,56,0,5476.34,-722.154,342.284,2.91904,'1135:0 1136:0 1176:0 1178:0 '), +(1176,'ShatterScarVale2',1,361,1766,46,56,0,5601.44,-768.8,344.125,2.60488,'1136:0 1137:0 1175:0 1178:0 '), +(1177,'ShatterScarVale3',1,361,1766,46,56,0,5560.13,-1032.72,379.177,1.06354,'1137:0 1178:0 1179:0 '), +(1178,'ShatterScarVale4',1,361,1766,46,56,0,5457.81,-881.575,366.112,0.423435,'1175:0 1176:0 1177:0 '), +(1179,'ShatterScarVale5',1,361,1766,46,56,0,5754.61,-1189.42,406.559,5.86624,'1177:0 1180:0 '), +(1180,'IrontreeWoods1',1,361,1767,46,56,0,6121.79,-1182.38,370.89,3.93611,'1139:0 1179:0 1181:0 1182:0 '), +(1181,'IrontreeWoods2',1,361,1767,46,56,0,6394.76,-1179.55,386.014,6.27855,'1140:0 1142:0 1180:0 '), +(1182,'IrontreeWoods3',1,361,1767,46,56,0,6130.52,-1612.09,477.459,1.51512,'1180:0 1183:0 '), +(1183,'IrontreeWoods4',1,361,1767,46,56,0,6296.99,-1561.48,453.656,3.98913,'1145:0 1182:0 149:0 '), +(1184,'IrontreeCavern1',1,361,1768,46,56,0,6352.66,-1649.89,424.433,0.128896,'149:0 1185:0 1186:0 '), +(1185,'IrontreeCavern2',1,361,1768,46,56,0,6439.2,-1697.67,413.417,3.51397,'1184:0 '), +(1186,'IrontreeCavern3',1,361,1768,46,56,0,6348.65,-1752.82,421.668,2.4419,'1184:0 '), +(1187,'JadefireRun1',1,361,2618,46,56,0,6621.19,-889.352,485.149,4.30328,'1141:0 1188:0 '), +(1188,'JadefireRun2',1,361,2618,46,56,0,6459.98,-779.685,474.056,3.5611,'1139:0 1187:0 1189:0 '), +(1189,'JadefireRun3',1,361,2618,46,56,0,6292.94,-589.199,467.536,5.59331,'1188:0 '), +(1190,'FelwoodSide3_1',1,361,361,46,56,0,6839.58,-1594.78,491.498,3.27443,'1143:0 1144:0 '), +(1191,'TalonbranchGladeEntrance',1,361,1998,46,56,0,6265.97,-1990.07,570.371,0.397118,'1146:0 1148:0 1192:0 '), +(1192,'TalonbranchGlade',1,361,1998,46,56,3,6170.94,-1933.43,569.997,5.3569,'1191:0 '), +(1193,'FelpawVillage1',1,361,1762,46,56,0,6806.95,-1993.95,564.972,2.74938,'1146:0 1147:0 1194:0 1195:0 '), +(1194,'FelpawVillage2',1,361,1762,46,56,0,6789.17,-1850.3,554.16,4.19844,'1193:0 1195:0 '), +(1195,'FelpawVillage3',1,361,1762,46,56,0,6929.55,-1812.01,575.12,3.87447,'1193:0 1194:0 '), +(1196,'TimbermawHoldInside',1,361,1769,46,56,0,7017.44,-2122.43,586.887,0.262027,'142:0 148:0 1197:0 '), +(1197,'MoongladeExitS',1,493,493,46,60,0,7409.39,-2177.56,520.829,3.84148,'1196:0 1198:0 1199:0 '), +(1198,'MoongladeFlightH',1,493,493,46,60,5,7479.01,-2119.01,492.215,1.99187,'1197:0 1199:0 '), +(1199,'MoongladeMainRoad1',1,493,493,46,60,0,7621.21,-2207.23,468.939,2.73996,'1197:0 1198:0 1200:0 '), +(1200,'MoongladeMainRoad2',1,493,493,46,60,0,7521.64,-2483.68,454.141,1.37142,'1199:0 1201:0 '), +(1201,'MoongladeFlightA',1,493,493,46,60,3,7441.03,-2497.31,462.16,0.100638,'1200:0 '), +(1202,'FrostfireHotSprings1',1,618,2246,53,60,0,6669.75,-2350.35,569.688,6.11089,'142:0 1203:0 1204:0 1205:0 '), +(1203,'FrostfireHotSprings2',1,618,618,53,60,0,6516.03,-2438.96,575.469,0.575811,'1202:0 1204:0 1205:0 1206:0 '), +(1204,'FrostfireHotSprings3',1,618,2246,53,60,0,6739.97,-2534.28,547.608,2.35473,'1202:0 1203:0 1205:0 1206:0 1207:0 '), +(1205,'FrostfireHotSprings4',1,618,2246,53,60,0,6844.94,-2510.17,562.168,2.83383,'1202:0 1203:0 1204:0 1207:0 '), +(1206,'WinterspringWRoad1',1,618,618,53,60,0,6552.68,-2761.31,569.286,1.6412,'1203:0 1204:0 1208:0 1209:0 '), +(1207,'FrostfireHotSprings5',1,618,2246,53,60,0,6778.35,-2683.46,542.964,1.10124,'1204:0 1205:0 '), +(1208,'WinterspringWRoad2',1,618,618,53,60,0,6544.07,-3009.5,594.631,1.59877,'1206:0 1209:0 1211:0 1213:0 '), +(1209,'TimbermawPost1',1,618,2243,53,60,0,6443.26,-2975.94,574.619,1.46329,'1206:0 1208:0 1210:0 '), +(1210,'TimbermawPost2',1,618,2243,53,60,0,6501.28,-3395.1,596.264,1.54182,'1209:0 1212:0 '), +(1211,'WinterspringWRoad3',1,618,618,53,60,0,6612.45,-3218.46,610.975,1.85402,'1208:0 1212:0 1213:0 '), +(1212,'WinterspringWRoad4',1,618,618,53,60,0,6645.3,-3475.9,667.925,1.61251,'1210:0 1211:0 1214:0 '), +(1213,'FrostfireHotSprings6',1,618,2246,53,60,0,6782.88,-3070.45,577.01,3.04979,'1208:0 1211:0 '), +(1214,'WinterspringWRoad5',1,618,618,53,60,0,6657.52,-3668.1,695.82,1.56341,'1212:0 1215:0 1216:0 1221:0 '), +(1215,'WinterspringWRoad6',1,618,2252,53,60,0,6397.07,-3892.14,680.982,0.597365,'1214:0 1216:0 1217:0 '), +(1216,'LakeKeltheril',1,618,2251,53,60,0,6536.55,-4131.94,665.606,1.20213,'1214:0 1215:0 143:0 1218:0 '), +(1217,'Mazthoril1',1,618,2245,53,60,0,6183.02,-4265.44,745.257,1.04309,'1215:0 1218:0 1219:0 '), +(1218,'EverlookFork1',1,618,2255,53,60,0,6655.73,-4560.81,717.041,1.46131,'56:0 1216:0 1217:0 1239:0 1240:0 '), +(1219,'Mazthoril2',1,618,2245,53,60,0,5991.6,-4416.44,712.225,0.648425,'1217:0 1220:0 1255:0 1259:0 '), +(1220,'Mazthoril3',1,618,2245,53,60,0,6111.88,-4459.18,665.247,2.8927,'1219:0 1254:0 1255:0 '), +(1221,'WinterspringCFork',1,618,618,53,60,0,6913.24,-4134.15,695.074,1.72442,'1214:0 1222:0 1224:0 1225:0 '), +(1222,'WinterspringNRoad1',1,618,618,53,60,0,7189.47,-4145.52,708.652,2.01894,'1221:0 1223:0 1224:0 1225:0 1226:0 '), +(1223,'StarfallVillage1',1,618,2253,53,60,2,7163.35,-3949.34,749.855,4.52632,'1222:0 1224:0 '), +(1224,'StarfallVillage2',1,618,2253,53,60,2,7095.04,-4017.7,744.806,3.57795,'1221:0 1222:0 1223:0 '), +(1225,'WinterspringN_hub1',1,618,618,53,60,0,7243.63,-4428.68,637.464,1.30573,'147:0 1221:0 1222:0 1235:0 '), +(1226,'WinterspringNRoad2',1,618,618,53,60,0,7437.77,-4106.34,703.256,3.39493,'1222:0 1227:0 1231:0 1233:0 '), +(1227,'WinterspringNRoad3',1,618,2241,53,60,0,7658.36,-4006.12,703.193,3.60307,'1226:0 1228:0 1229:0 1230:0 '), +(1228,'FrostsaberRock',1,618,2241,53,60,0,8080.15,-3838.54,691.237,3.49507,'1227:0 1229:0 1230:0 '), +(1229,'FrostsaberRockSide1',1,618,2241,53,60,0,7601.67,-3828.53,689.193,5.16206,'1227:0 1228:0 '), +(1230,'FrostsaberRockSide2',1,618,2241,53,60,0,7960.17,-4207.36,694.89,1.05442,'1227:0 1228:0 1231:0 1232:0 '), +(1231,'FrostsaberRockSide3',1,618,2241,53,60,0,7696.88,-4240.08,674.756,0.182632,'1226:0 1230:0 1232:0 1233:0 '), +(1232,'FrostsaberRockSide4',1,618,2241,53,60,0,7898.77,-4343.01,714.863,1.26058,'1230:0 1231:0 1234:0 '), +(1233,'WinterspringN_hub2',1,618,618,53,60,0,7541.26,-4433.34,604.622,1.91639,'1226:0 1231:0 1234:0 1235:0 '), +(1234,'WinterspringN_hub3',1,618,618,53,60,0,7841.47,-4633.94,711.459,1.96351,'1232:0 1233:0 '), +(1235,'WinterspringN_hub4',1,618,618,53,60,0,7409.79,-4690.69,629.373,2.1363,'1225:0 1233:0 1236:0 1238:0 '), +(1236,'WinterspringN_hub5',1,618,618,53,60,0,7593.76,-4979.89,701.375,2.05382,'1235:0 1237:0 1238:0 '), +(1237,'HiddenGrove',1,618,2242,53,60,0,7765.82,-4824.05,695.683,4.13316,'1236:0 '), +(1238,'WinterfallVillage1',1,618,2244,53,60,0,7043.19,-4998.21,715.354,0.445711,'1235:0 1236:0 1243:0 '), +(1239,'EverlookFork2',1,618,2255,53,60,0,6611.3,-4739.92,698.015,0.695072,'1218:0 1241:0 1247:0 1254:0 '), +(1240,'EverlookFlightH',1,618,2255,53,60,5,6816.77,-4612.99,710.669,6.21445,'56:0 1218:0 1242:0 '), +(1241,'EverlookFlightA',1,618,2255,53,60,3,6790.08,-4744.29,701.5,3.20441,'1239:0 1242:0 '), +(1242,'WinterspringN_hub6',1,618,618,53,60,0,6927.32,-4786.97,694.458,2.5918,'1240:0 1241:0 1243:0 '), +(1243,'WinterfallVillageOutside',1,618,2244,53,60,0,6939.52,-5010.6,692.554,1.59042,'1238:0 1242:0 1244:0 '), +(1244,'WinterfallVillage2',1,618,2244,53,60,0,6808.46,-5094.96,695.933,0.427246,'1243:0 1245:0 '), +(1245,'WinterfallVillage3',1,618,2244,53,60,0,6729.74,-5155.43,735.364,6.18029,'1244:0 1246:0 '), +(1246,'WinterfallVillage4',1,618,2244,53,60,0,6716.73,-5241.35,779.049,1.49343,'1245:0 '), +(1247,'IceThistleHills1',1,618,2247,53,60,0,6422.5,-5063.45,747.183,0.955424,'1239:0 1248:0 1249:0 '), +(1248,'IceThistleHills2',1,618,2247,53,60,0,6075.66,-5048.11,793.011,6.23722,'1247:0 1255:0 '), +(1249,'IceThistleHillsCave1',1,618,2247,53,60,0,6558.26,-5114.63,770.071,4.80583,'1247:0 1250:0 '), +(1250,'IceThistleHillsCave2',1,618,2247,53,60,0,6647.69,-5270.98,753.532,0.248568,'1249:0 1251:0 1252:0 '), +(1251,'IceThistleHillsCave3',1,618,2247,53,60,0,6547.52,-5279.93,751.433,0.29569,'1250:0 '), +(1252,'IceThistleHillsCave4',1,618,2247,53,60,0,6591.19,-5377.61,757.91,2.11585,'1250:0 1253:0 '), +(1253,'IceThistleHillsCave5',1,618,2247,53,60,0,6749.19,-5302.24,746.013,3.42748,'1252:0 '), +(1254,'WinterspringSFork',1,618,618,53,60,0,6181.52,-4918.26,736.9,1.70352,'1220:0 1239:0 1255:0 '), +(1255,'WinterspringSRoad1',1,618,618,53,60,0,5738.96,-4759.97,772.327,5.63249,'144:0 1219:0 1220:0 1248:0 1254:0 1256:0 1258:0 '), +(1256,'OwlWingThicket1',1,618,2250,53,60,0,5704.51,-4972.1,807.203,1.24015,'1255:0 1257:0 '), +(1257,'OwlWingThicket2',1,618,2250,53,60,0,5516.17,-4932.2,843.306,5.86024,'1256:0 '), +(1258,'WinterspringSRoad2',1,618,618,53,60,0,5542.21,-4699,784.197,5.83863,'144:0 1255:0 1263:0 '), +(1259,'DunMandarrW',1,618,2248,53,60,0,5726.18,-4420.5,778.036,0.0455333,'144:0 1219:0 '), +(1260,'FrostwhisperGorge1',1,618,2249,53,60,0,5376.4,-4513.45,720.025,0.126024,'144:0 1261:0 '), +(1261,'FrostwhisperGorge2',1,618,2249,53,60,0,5296.85,-4663.41,691.461,1.09206,'1260:0 1262:0 '), +(1262,'FrostwhisperGorge3',1,618,2249,53,60,0,5259.63,-4868.84,691.501,1.4769,'1261:0 '), +(1263,'FrostwhisperGorgeBridge1',1,618,618,53,60,0,5329.29,-4753.88,815.467,6.28158,'1258:0 1264:0 '), +(1264,'FrostwhisperGorgeBridge2',1,618,618,53,60,1,5184.44,-4704.74,823.398,6.16966,'1263:0 1265:0 '), +(1265,'DarkwhisperGorge1',1,618,618,53,60,0,5076.75,-4593.36,846.068,5.72001,'1264:0 1266:0 1267:0 1268:0 '), +(1266,'DarkwhisperGorge2',1,618,2249,53,60,0,5094.49,-4876.76,867.355,1.66931,'1265:0 '), +(1267,'DarkwhisperGorge3',1,618,2249,53,60,0,5323.19,-4418.49,854.383,3.62103,'1265:0 '), +(1268,'DarkwhisperGorge4',1,618,2256,53,60,0,4938.26,-4535.16,855.372,3.64264,'1265:0 '), +(1269,'AzsharaExitW',1,16,878,43,54,0,2800.83,-3813.24,83.7107,0.929092,'1095:0 1270:0 '), +(1270,'TalrendisPointRoad',1,16,3137,43,54,0,2749.62,-3926.91,89.3218,0.361656,'1269:0 145:0 1271:0 1273:0 '), +(1271,'ShadowsongShrine1',1,16,878,43,54,0,2935.67,-3982.68,124.392,2.73551,'1270:0 1272:0 '), +(1272,'ShadowsongShrine2',1,16,1235,43,54,0,2850.78,-4024.75,140.874,0.32041,'1271:0 '), +(1273,'AzsharaRoad1',1,16,16,43,54,0,2815.94,-4122.46,94.8896,2.21715,'1270:0 1274:0 1279:0 '), +(1274,'AzsharaRoad2',1,16,16,43,54,0,2941.29,-4227.56,95.186,2.45278,'1273:0 1275:0 1280:0 '), +(1275,'AzsharaRoad3',1,16,16,43,54,0,3057.29,-4322.31,90.9214,2.58827,'1274:0 1276:0 1280:0 '), +(1276,'AzsharaRoad4',1,16,16,43,54,0,3229.99,-4491.16,92.7121,1.9737,'1275:0 1277:0 1280:0 1284:0 '), +(1277,'AzsharaRoad5',1,16,16,43,54,0,3333.33,-4589.69,92.2964,2.31141,'1276:0 1278:0 1283:0 1286:0 1326:0 '), +(1278,'AzsharaRoad6',1,16,16,43,54,0,3451.9,-4750.56,108.662,2.10326,'1277:0 1286:0 1287:0 1288:0 1324:0 1327:0 '), +(1279,'AzsharaE1',1,16,16,43,54,0,2997.33,-4106.35,99.9666,3.2087,'1273:0 1280:0 1283:0 '), +(1280,'AzsharaE2',1,16,1236,43,54,0,3118.77,-4225.25,100.015,2.98486,'1274:0 1275:0 1276:0 1279:0 1281:0 1283:0 '), +(1281,'AzsharaE3',1,16,1236,43,54,0,3300.36,-4275.9,126.16,2.80817,'1280:0 1282:0 '), +(1282,'AzsharaE4',1,16,1236,43,54,0,3271.81,-4373.57,125.961,1.43765,'1281:0 '), +(1283,'AzsharaE5',1,16,1236,43,54,0,3373.1,-4206.33,105.482,3.57,'1277:0 1279:0 1280:0 1284:0 1285:0 '), +(1284,'AzsharaE6',1,16,16,43,54,0,3582.46,-4138.13,104.333,3.80758,'1276:0 1283:0 1285:0 '), +(1285,'Valormok',1,16,1237,43,54,5,3613.75,-4414.24,112.731,2.85922,'1283:0 1284:0 1286:0 '), +(1286,'AzsharaE7',1,16,16,43,54,0,3590.09,-4639.97,116.675,1.6988,'1277:0 1278:0 1285:0 1287:0 '), +(1287,'AzsharaE8',1,16,1224,43,54,0,3744.78,-4813.74,138.923,2.88199,'1278:0 1286:0 1289:0 '), +(1288,'AzsharaE9',1,16,1221,43,54,0,3499.39,-4902.72,144.155,1.64696,'1278:0 1289:0 '), +(1289,'AzsharaE10',1,16,1221,43,54,0,3759.42,-4967.77,142.273,2.79364,'1287:0 1288:0 1290:0 '), +(1290,'AzsharaN1',1,16,1216,43,54,0,4036.22,-5259.02,107.607,2.29688,'1289:0 1291:0 1292:0 1322:0 '), +(1291,'AzsharaN2',1,16,1216,43,54,0,4056.25,-5430.36,110.698,6.13747,'1290:0 1294:0 1293:0 1322:0 '), +(1292,'AzsharaN3',1,16,1216,43,54,0,4443.1,-5422.68,110.062,2.83485,'1290:0 1293:0 '), +(1293,'AzsharaN4',1,16,1225,43,54,0,4501.55,-5509.11,112.466,2.9016,'1291:0 1292:0 1294:0 1295:0 '), +(1294,'AzsharaN5',1,16,1225,43,54,0,4344.93,-5684.07,101.657,1.91789,'1291:0 1293:0 1296:0 1297:0 1322:0 '), +(1295,'AzsharaN6',1,16,1219,43,54,0,4731.82,-5683.23,101.157,2.58744,'1293:0 1296:0 1297:0 '), +(1296,'AzsharaN7',1,16,1220,43,54,0,4463.44,-6017.87,99.0046,1.97484,'1294:0 1295:0 1299:0 1300:0 '), +(1297,'AzsharaN8',1,16,1219,43,54,0,4794.73,-5948.23,94.5572,2.9291,'1294:0 1295:0 1298:0 1300:0 '), +(1298,'AzsharaN9',1,16,1219,43,54,0,4752.23,-6324.43,112.261,1.52126,'1297:0 1300:0 1301:0 '), +(1299,'AzsharaN10',1,16,1220,43,54,0,4304.26,-6172.26,132.068,0.476687,'1296:0 1300:0 '), +(1300,'AzsharaN11',1,16,1220,43,54,0,4483.15,-6277.92,109.808,2.02983,'1296:0 1297:0 1298:0 1299:0 1302:0 '), +(1301,'AzsharaN12',1,16,1219,43,54,0,4815.57,-6641.1,96.3628,1.82366,'1298:0 1302:0 1304:0 '), +(1302,'AzsharaN13',1,16,1219,43,54,0,4579.43,-6593.47,100.233,1.80599,'1300:0 1301:0 1303:0 '), +(1303,'AzsharaN14',1,16,2497,43,54,0,4335.22,-6872.83,94.6868,0.889033,'1302:0 1304:0 1305:0 1306:0 '), +(1304,'AzsharaN15',1,16,2497,43,54,0,4772.55,-7077.06,93.7659,1.50753,'1301:0 1303:0 1306:0 1308:0 '), +(1305,'AzsharaN16',1,16,1228,43,54,0,4163.23,-7077.25,104.42,0.918482,'1303:0 '), +(1306,'AzsharaN17',1,16,2497,43,54,0,4378.53,-7190.76,114.567,1.36616,'1303:0 1304:0 1307:0 '), +(1307,'AzsharaN18',1,16,2497,43,54,0,4547.1,-7477.55,77.7456,1.89042,'1306:0 1308:0 1312:0 1313:0 '), +(1308,'AzsharaN19',1,16,2497,43,54,0,4697.79,-7545.77,76.1055,2.41466,'1304:0 1307:0 1309:0 1311:0 '), +(1309,'AzsharaN20',1,16,1230,43,54,0,4879.55,-7649.88,-0.264126,2.8859,'1308:0 1310:0 1311:0 '), +(1310,'AzsharaN21',1,16,1230,43,54,0,4981.1,-7379.41,2.45741,4.29372,'1309:0 '), +(1311,'AzsharaN22',1,16,1230,43,54,0,4636.23,-7784.83,-0.404812,0.517923,'1308:0 1309:0 1312:0 1313:0 '), +(1312,'AzsharaN23',1,16,1229,43,54,0,4265.54,-7853.88,3.0357,0.839938,'1307:0 1311:0 1313:0 '), +(1313,'AzsharaN24',1,16,1228,43,54,0,4328.17,-7549.27,10.4959,5.60927,'1307:0 1311:0 1312:0 1314:0 1316:0 '), +(1314,'AzsharaN25',1,16,1228,43,54,0,4248.34,-7195.83,18.9926,3.96189,'1313:0 1315:0 '), +(1315,'AzsharaN26',1,16,1226,43,54,0,4103.25,-7187.96,10.5715,6.16297,'1314:0 1316:0 1317:0 '), +(1316,'AzsharaN27',1,16,1226,43,54,0,4038.51,-7321.54,1.12717,0.769242,'1313:0 1315:0 1317:0 '), +(1317,'AzsharaN28',1,16,1226,43,54,0,3934.81,-7205.7,26.486,5.91163,'1315:0 1316:0 1318:0 1320:0 1321:0 '), +(1318,'TempleOfArkkoran1',1,16,1226,43,54,0,3807.79,-7280.66,26.4812,1.14034,'1317:0 1319:0 '), +(1319,'TempleOfArkkoran2',1,16,1226,43,54,0,3768.46,-7134.44,26.4806,5.96857,'1318:0 1320:0 '), +(1320,'TempleOfArkkoran3',1,16,1226,43,54,0,3902.48,-7060.61,26.4811,4.31335,'1317:0 1319:0 '), +(1321,'TempleOfArkkoran4',1,16,1226,43,54,0,3858.12,-7171.88,24.03,5.83506,'1317:0 '), +(1322,'AzsharaRamp1Top',1,16,1225,43,54,0,4108.38,-5554.42,99.4778,1.77221,'1290:0 1291:0 1294:0 1323:0 '), +(1323,'AzsharaRamp1Bottom',1,16,1228,43,54,0,3897.1,-5695.14,17.2205,0.46452,'1322:0 1341:0 1342:0 '), +(1324,'AzsharaC1',1,16,1221,43,54,0,3389.18,-4881.96,119.527,0.94754,'1278:0 1325:0 '), +(1325,'AzsharaC2',1,16,1221,43,54,0,3548.39,-5142.38,83.2368,2.40053,'1324:0 1326:0 1328:0 46:0 1332:0 '), +(1326,'AzsharaC3',1,16,1221,43,54,0,3353.2,-4996.79,88.9565,1.84487,'1277:0 1325:0 '), +(1327,'AzsharaC4',1,16,1233,43,54,0,3111.76,-5099.04,133.69,0.892567,'1278:0 1328:0 1343:0 1344:0 '), +(1328,'AzsharaC5',1,16,1221,43,54,0,3288.43,-5313.47,92.0357,2.24738,'1325:0 1327:0 1329:0 '), +(1329,'AzsharaC6',1,16,1221,43,54,0,3069.52,-5522.7,98.46,0.58232,'1328:0 1330:0 1343:0 '), +(1330,'AzsharaC7',1,16,1231,43,54,0,3063.44,-5615.53,60.2217,1.67206,'1329:0 1331:0 1338:0 '), +(1331,'AzsharaC8',1,16,1231,43,54,0,3004.41,-5699.66,35.8993,0.918073,'1330:0 1334:0 '), +(1332,'AzsharaC9',1,16,1221,43,54,0,3744.95,-5305.2,85.9123,2.48299,'1325:0 1333:0 '), +(1333,'AzsharaC10',1,16,1228,43,54,0,3658.05,-5517.8,24.2321,1.28329,'1332:0 1339:0 1340:0 1341:0 '), +(1334,'AzsharaC11',1,16,1231,43,54,0,3087.73,-5859.39,-0.161575,2.13546,'1331:0 1335:0 1338:0 '), +(1335,'AzsharaC12',1,16,1231,43,54,0,2934.41,-5884.56,25.8109,0.120912,'1334:0 1336:0 '), +(1336,'AzsharaC13',1,16,1231,43,54,0,2980.62,-6084.05,0.213247,2.38089,'1335:0 1337:0 '), +(1337,'AzsharaC14',1,16,1231,43,54,0,2846.38,-6166.7,3.60284,0.643192,'1336:0 '), +(1338,'AzsharaC15',1,16,1231,43,54,0,3226.36,-5673.24,15.8216,3.93007,'1330:0 1334:0 1339:0 1340:0 '), +(1339,'AzsharaC16',1,16,1231,43,54,0,3427.33,-5505.43,26.9806,5.26131,'1333:0 1338:0 1340:0 1341:0 '), +(1340,'AzsharaC17',1,16,1231,43,54,0,3475.44,-5753.95,11.6531,1.76236,'1333:0 1338:0 1339:0 1341:0 '), +(1341,'AzsharaC18',1,16,1228,43,54,0,3704.73,-5723.53,4.41024,0.254392,'1323:0 1333:0 1339:0 1340:0 1342:0 '), +(1342,'AzsharaC19',1,16,1228,43,54,0,3948.84,-5885.08,8.49314,2.09811,'1323:0 1341:0 '), +(1343,'AzsharaS1',1,16,1234,43,54,0,2887.1,-5407.2,110.727,1.29111,'1327:0 1329:0 1344:0 1347:0 1346:0 '), +(1344,'AzsharaS2',1,16,1234,43,54,0,2671.51,-5134.67,110.162,6.23323,'1327:0 1343:0 1345:0 '), +(1345,'AzsharaS3',1,16,1234,43,54,0,2476.44,-5345.72,112.668,0.747221,'1344:0 1346:0 '), +(1346,'AzsharaS4',1,16,16,43,54,0,2551.54,-5739.78,99.3784,1.5499,'1343:0 1345:0 1347:0 1348:0 '), +(1347,'AzsharaS5',1,16,16,43,54,0,2773.38,-5801.37,102.296,1.23417,'1343:0 1346:0 '), +(1348,'AzsharaS6',1,16,16,43,54,0,2497.05,-6122.14,101.12,1.55774,'1346:0 1349:0 '), +(1349,'AzsharaS7',1,16,16,43,54,0,2591.64,-6535.29,102.94,1.79136,'1348:0 1350:0 1351:0 '), +(1350,'AzsharaS8',1,16,16,43,54,0,2738.28,-6626.16,105.229,0.130243,'1349:0 '), +(1351,'AzsharaS9',1,16,1232,43,54,0,2530.76,-6691.49,105.655,1.22588,'1349:0 1352:0 1353:0 '), +(1352,'AzsharaS10',1,16,1232,43,54,0,2594.6,-6859.47,124.355,1.98575,'1351:0 '), +(1353,'AzsharaS11',1,16,1232,43,54,0,2469.22,-6950.98,112.622,1.32995,'1351:0 1354:0 '), +(1354,'AzsharaS12',1,16,1232,43,54,0,2290.99,-6837.2,120.824,5.67516,'1353:0 1355:0 '), +(1355,'AzsharaS13',1,16,1232,43,54,0,2245.13,-6620.49,130.493,5.82242,'1354:0 '), +(1356,'NightsongWoodsSE',1,331,2457,18,30,0,2031.76,-3416.6,92.6375,6.12481,'1097:0 1099:0 1116:0 1357:0 '), +(1357,'SouthfuryRiver1',1,331,879,18,30,0,2157.71,-3580.65,40.0791,2.2096,'1356:0 1358:0 '), +(1358,'SouthfuryRiver2',1,331,879,18,30,0,1982.59,-3613.01,40.3535,1.3378,'1357:0 1359:0 '), +(1359,'SouthfuryRiver3',1,331,879,18,30,4,1751.38,-3745.84,39.7381,3.16817,'1358:0 1360:0 '), +(1360,'OrgrimmarExitW',1,17,815,8,25,4,1641.94,-3837.63,50.7719,5.47332,'1359:0 1454:0 '), +(1361,'DurotarN_hub1',1,14,14,1,10,4,1290.89,-4391.75,26.2836,3.73994,'30:0 1362:0 1377:0 1385:0 1386:0 '), +(1362,'DurotarN_hub2',1,14,14,1,10,4,1341.58,-4623.78,24.2112,1.68496,'1361:0 1363:0 1365:0 1377:0 '), +(1363,'SkullRock1',1,14,817,1,10,4,1448.11,-4879.66,11.5034,1.9029,'1362:0 1364:0 1365:0 '), +(1364,'SkullRock2',1,14,817,1,10,4,1445.58,-4687.97,-5.65162,4.9365,'1363:0 '), +(1365,'DurotarN_hub3',1,14,14,1,10,4,1100.2,-4908.62,14.577,0.165201,'1362:0 1363:0 1366:0 1367:0 '), +(1366,'DurotarN_hub4',1,14,375,1,10,4,985.89,-5115.68,1.41706,1.16658,'1365:0 1367:0 '), +(1367,'DurotarN_hub5',1,14,14,1,10,4,809.043,-4957.4,16.2794,2.83475,'1365:0 1366:0 1368:0 1370:0 '), +(1368,'DustwindCave1',1,14,14,1,10,4,778.919,-4756.62,37.5488,4.94158,'1367:0 1369:0 1370:0 '), +(1369,'DustwindCave2',1,14,371,1,10,4,931.181,-4715.15,19.9519,2.6443,'1368:0 '), +(1370,'DurotarN_hub6',1,14,14,1,10,4,512.237,-4785.91,29.1897,1.93467,'1367:0 1368:0 1371:0 1407:0 '), +(1371,'DurotarN_hub7',1,14,14,1,10,4,438.776,-4564.65,52.1313,5.05391,'1370:0 1372:0 1400:0 1403:0 '), +(1372,'DurotarN_hub8',1,14,14,1,10,4,599.139,-4404.63,18.4833,5.05392,'1371:0 66:0 67:0 1400:0 1403:0 '), +(1373,'DurotarCanyon1_2',1,14,410,1,10,4,754.968,-4457.2,15.6687,1.93155,'66:0 67:0 '), +(1374,'DurotarCanyon1_3',1,14,14,1,10,4,720.095,-4682.12,-6.58629,2.1711,'66:0 1375:0 1401:0 '), +(1375,'DurotarCanyon1_4',1,14,14,1,10,4,762.27,-4587.67,0.395604,5.89975,'1374:0 1376:0 1384:0 '), +(1376,'DurotarCanyon1_5',1,14,410,1,10,4,909.793,-4623.59,17.4591,2.58341,'1375:0 1377:0 1378:0 '), +(1377,'DurotarCanyon1_6',1,14,14,1,10,4,1089.79,-4544.84,18.7912,3.37862,'1361:0 1362:0 1376:0 1385:0 '), +(1378,'DurotarCanyon1_7',1,14,410,1,10,4,846.499,-4687.15,10.6635,4.45069,'1376:0 1379:0 '), +(1379,'DurotarCanyon1_8',1,14,370,1,10,4,822.93,-4819.09,11.1622,1.25216,'1378:0 1380:0 '), +(1380,'DurotarCanyon1_9',1,14,370,1,10,4,922.247,-4823.86,14.833,3.04091,'1379:0 1381:0 1382:0 '), +(1381,'DurotarCanyon1_10',1,14,370,1,10,4,984.82,-4668.01,26.8858,4.65295,'1380:0 '), +(1382,'DurotarCanyon1_11',1,14,370,1,10,4,1028.11,-4807.86,13.422,1.33267,'1380:0 1383:0 '), +(1383,'DurotarCanyon1_12',1,14,370,1,10,4,1112.67,-4667.33,19.0025,5.6445,'1382:0 '), +(1384,'DurotarCanyon1_13',1,14,14,1,10,4,863.608,-4507.22,5.89497,4.47623,'1375:0 1385:0 '), +(1385,'DurotarCanyon1_14',1,14,14,1,10,4,1076.54,-4421.72,18.043,3.13516,'67:0 1361:0 1377:0 1384:0 1387:0 '), +(1386,'RocktuskFarm',1,14,1296,1,10,4,1261.75,-4189.97,25.9612,5.01817,'1361:0 1387:0 1388:0 '), +(1387,'DurotarN_hub9',1,14,14,1,10,4,1103.69,-4095.77,17.1414,5.61704,'1385:0 1386:0 1388:0 1389:0 '), +(1388,'DurotarN_hub10',1,14,14,1,10,4,1240.84,-3994.36,19.8563,4.64119,'1386:0 1387:0 1389:0 1452:0 1453:0 '), +(1389,'DurotarN_hub11',1,14,814,1,10,4,932.462,-3903.15,18.4923,5.69476,'1387:0 1388:0 1390:0 1391:0 1451:0 1452:0 '), +(1390,'DurotarN_hub12',1,14,14,1,10,4,554.028,-3867,21.5181,6.15421,'1389:0 1391:0 1392:0 1405:0 1406:0 '), +(1391,'DurotarN_hub13',1,14,14,1,10,4,484.992,-4008.81,21.634,0.40509,'1389:0 1390:0 1392:0 1400:0 1405:0 1406:0 '), +(1392,'DurotarCanyon2_1',1,14,14,1,10,4,621.087,-4034.14,5.79289,6.22882,'67:0 1390:0 1391:0 1393:0 1400:0 '), +(1393,'DurotarCanyon2_2',1,14,369,1,10,4,805.35,-4026.74,-8.81716,3.20306,'1392:0 1394:0 1398:0 '), +(1394,'DurotarCanyon2_3',1,14,369,1,10,4,924.692,-4010.46,-13.3326,3.34246,'1393:0 1395:0 '), +(1395,'DurotarCanyon2_4',1,14,369,1,10,4,969.211,-4144.86,-6.8125,2.00533,'1394:0 1396:0 1397:0 '), +(1396,'DurotarCanyon2_5',1,14,369,1,10,4,858.51,-4186.73,-13.9605,0.410964,'1395:0 '), +(1397,'DurotarCanyon2_6',1,14,369,1,10,4,955.695,-4276.78,-6.40429,1.49874,'1395:0 '), +(1398,'DurotarCanyon2_7',1,14,369,1,10,4,832.014,-4096.6,-12.8537,2.28805,'1393:0 1399:0 '), +(1399,'DurotarCanyon2_8',1,14,369,1,10,4,733.597,-4112.95,-9.91997,0.344189,'1398:0 '), +(1400,'RazormaneGrounds',1,14,816,1,10,4,394.471,-4227.01,25.6721,6.10902,'67:0 1371:0 1372:0 1391:0 1392:0 1403:0 1404:0 '), +(1401,'DurotarCanyon1_15',1,14,14,1,10,4,618.018,-4729.15,-9.7302,0.530719,'1374:0 28:0 '), +(1402,'DurotarRoadW1',1,14,14,1,10,4,229.555,-4564.93,14.4257,4.87206,'28:0 1403:0 1409:0 1437:0 '), +(1403,'DurotarRoadW2',1,14,14,1,10,4,311.231,-4367.13,23.2866,0.650496,'1371:0 1372:0 1400:0 1402:0 1404:0 1437:0 1440:0 '), +(1404,'DurotarRoadW3',1,14,14,1,10,4,290.25,-4093.37,30.0616,4.8897,'1400:0 1403:0 1405:0 1440:0 1441:0 1443:0 '), +(1405,'DurotarRoadW4',1,14,14,1,10,4,289.101,-3928.87,32.0584,5.95001,'1390:0 1391:0 1404:0 1406:0 1441:0 1443:0 '), +(1406,'DurotarWBridge',1,14,814,1,10,4,317.731,-3805.25,24.3367,6.02659,'1390:0 1391:0 1405:0 1443:0 1449:0 '), +(1407,'DurotarS_hub1',1,14,14,1,10,4,250.862,-4982.03,19.1509,0.356019,'1370:0 1408:0 1409:0 1410:0 1414:0 '), +(1408,'DurotarS_hub2',1,14,373,1,10,4,274.781,-5126.02,4.75879,2.02892,'1407:0 1414:0 '), +(1409,'DurotarRoadS1',1,14,14,1,10,4,119.098,-4745.19,15.4912,5.09591,'28:0 1402:0 1407:0 1410:0 1414:0 1415:0 '), +(1410,'DurotarRoadS2',1,14,14,1,10,4,-99.4238,-4745.37,21.6208,0.102741,'1407:0 1409:0 1411:0 1414:0 '), +(1411,'DurotarRoadS3',1,14,14,1,10,4,-278.577,-4798.87,28.7297,0.267683,'1410:0 1412:0 1414:0 1415:0 '), +(1412,'DurotarRoadS4',1,14,14,1,10,4,-448.465,-4791.29,35.8497,6.27205,'1411:0 1413:0 '), +(1413,'DurotarRoadS5',1,14,14,1,10,4,-640.871,-4736.39,33.6569,6.15227,'1412:0 31:0 1419:0 1421:0 1422:0 1429:0 '), +(1414,'TirigardeKeep1',1,14,372,1,10,4,-84.5912,-5061.01,15.5227,0.185219,'1407:0 1408:0 1409:0 1410:0 1411:0 1415:0 '), +(1415,'TirigardeKeep2',1,14,372,1,10,4,-198.068,-5050.76,20.9866,3.49606,'1409:0 1411:0 1414:0 1416:0 1417:0 '), +(1416,'TirigardeKeep3',1,14,372,1,10,4,-225.492,-5115.3,49.3248,1.51096,'1415:0 '), +(1417,'TirigardeKeep4',1,14,372,1,10,4,-321.717,-5084.89,21.2127,0.285748,'1415:0 1418:0 '), +(1418,'TirigardeKeep5',1,14,373,1,10,4,-291.897,-5186.7,20.4791,1.8644,'1417:0 '), +(1419,'DurotarS_hub3',1,14,393,1,10,4,-716.008,-5088.67,9.54796,1.34603,'31:0 1413:0 1420:0 1444:0 '), +(1420,'DurotarS_hub4',1,14,393,1,10,4,-920.843,-4982.52,11.1974,5.87385,'31:0 1419:0 1421:0 1422:0 1445:0 '), +(1421,'DurotarS_hub5',1,14,393,1,10,4,-1100.19,-4784.19,8.13918,5.95632,'31:0 1413:0 1420:0 1422:0 '), +(1422,'DurotarCanyon3_1',1,14,14,1,10,4,-978.237,-4704.6,20.1681,1.62878,'31:0 1413:0 1420:0 1421:0 1423:0 '), +(1423,'DurotarCanyon3_2',1,14,366,1,10,4,-986.47,-4574.42,25.5859,4.28734,'1422:0 1424:0 1425:0 1427:0 '), +(1424,'DurotarCanyon3_3',1,14,366,1,10,4,-1067.61,-4604.24,25.8221,3.64331,'1423:0 '), +(1425,'DurotarCanyon3_4',1,14,366,1,10,4,-1047.58,-4451.84,27.4603,4.97458,'1423:0 1426:0 '), +(1426,'DurotarCanyon3_5',1,14,366,1,10,4,-965.059,-4407.81,29.5222,3.58441,'1425:0 '), +(1427,'DurotarCanyon3_6',1,14,366,1,10,4,-981.936,-4503.15,25.7079,1.87618,'1423:0 1428:0 '), +(1428,'DurotarCanyon3_7',1,14,366,1,10,4,-904.997,-4477.99,29.6937,3.30756,'1427:0 '), +(1429,'ValleyOfTrials1',1,14,363,1,10,4,-603.24,-4597.62,41.2213,4.5544,'1413:0 1430:0 '), +(1430,'ValleyOfTrials2',1,14,363,1,10,4,-580.729,-4526.65,41.4449,4.54262,'1429:0 1431:0 '), +(1431,'ValleyOfTrials3',1,14,363,1,10,4,-589.381,-4297.57,37.9694,5.56165,'1430:0 29:0 1432:0 1433:0 1434:0 1436:0 '), +(1432,'ValleyOfTrials4',1,14,363,1,10,4,-746.092,-4261.15,43.9991,6.03879,'1431:0 '), +(1433,'ValleyOfTrials5',1,14,363,1,10,4,-409.694,-4455.21,51.0433,2.32778,'1431:0 1434:0 1436:0 '), +(1434,'ValleyOfTrials6',1,14,363,1,10,4,-197.604,-4331.89,68.4747,3.01697,'1431:0 1433:0 1435:0 1436:0 '), +(1435,'ValleyOfTrials7',1,14,365,1,10,4,-87.1332,-4210.24,49.4149,4.1067,'1434:0 '), +(1436,'ValleyOfTrials8',1,14,363,1,10,4,-403.018,-4206.27,53.0641,5.51453,'1431:0 1433:0 1434:0 '), +(1437,'DurotarSW_hub1',1,14,816,1,10,4,162.168,-4433.07,35.4298,5.87386,'1402:0 1403:0 1438:0 1439:0 '), +(1438,'DurotarSW_hub2',1,14,816,1,10,4,80.4142,-4475.95,43.2596,0.578313,'1437:0 1439:0 '), +(1439,'DurotarSW_hub3',1,14,816,1,10,4,107.992,-4332.41,52.3223,5.13559,'1437:0 1438:0 1440:0 '), +(1440,'DurotarSW_hub4',1,14,816,1,10,4,29.2317,-4205.25,67.7773,5.8385,'1403:0 1404:0 1439:0 1441:0 '), +(1441,'DurotarSW_hub5',1,14,14,1,10,4,-53.7095,-4041.71,67.3331,0.00298548,'1404:0 1405:0 1440:0 1442:0 1443:0 '), +(1442,'DurotarSW_hub6',1,14,814,1,10,4,-173.855,-3864.96,35.2069,5.30244,'1441:0 1443:0 1476:0 1474:0 '), +(1443,'DurotarSW_hub7',1,14,14,1,10,4,72.4652,-3876.98,41.5453,3.44105,'1404:0 1405:0 1406:0 1441:0 1442:0 1474:0 '), +(1444,'EchoIsles1',1,14,368,1,10,4,-808.575,-5363.62,2.20208,1.32598,'1419:0 1445:0 1448:0 '), +(1445,'EchoIsles2',1,14,368,1,10,4,-1142.01,-5152.05,1.44042,0.283367,'1420:0 1444:0 1446:0 '), +(1446,'EchoIsles3',1,14,368,1,10,4,-1236.39,-5359.85,5.43454,0.742817,'1445:0 1447:0 1448:0 '), +(1447,'EchoIsles4',1,14,368,1,10,4,-1232.75,-5606.75,4.85907,1.49091,'1446:0 1448:0 '), +(1448,'EchoIsles5',1,14,368,1,10,4,-1035.61,-5519.3,7.91372,2.83395,'1444:0 1446:0 1447:0 '), +(1449,'BarrensNWBridge',1,17,17,8,25,4,313.815,-3718.22,26.5065,4.61206,'1406:0 1450:0 '), +(1450,'BarrensNW1',1,17,379,8,25,4,319.615,-3637.99,27.1854,4.5885,'1449:0 1451:0 1473:0 1474:0 '), +(1451,'BarrensNW2',1,17,17,8,25,4,824.833,-3721.02,27.1993,3.19834,'1389:0 1450:0 1452:0 '), +(1452,'BarrensNW3',1,17,17,8,25,0,1017.81,-3739.15,27.7416,5.18933,'1388:0 1389:0 1451:0 1453:0 1455:0 1459:0 '), +(1453,'BarrensNW4',1,17,17,8,25,4,1260.21,-3830.17,30.3266,3.3515,'1388:0 1452:0 1454:0 '), +(1454,'BarrensNW5',1,17,815,8,25,4,1550.39,-3863.76,40.7872,0.119578,'1360:0 1453:0 '), +(1455,'BarrensNW6',1,17,381,12,25,0,1205.41,-3622.44,82.8357,4.34975,'1452:0 1456:0 1457:0 '), +(1456,'BarrensNW7',1,17,381,12,25,0,1348.99,-3642.47,111.642,2.66035,'1455:0 1457:0 '), +(1457,'BarrensNW8',1,17,381,12,25,0,1351.11,-3571.19,92.4931,1.99552,'1455:0 1456:0 1458:0 '), +(1458,'BarrensNW9',1,17,381,8,25,0,1340.7,-3455.1,93.8483,3.69787,'1457:0 '), +(1459,'BarrensNW10',1,17,17,12,25,0,1103.13,-3455.42,87.1314,4.31322,'1452:0 1460:0 1462:0 1465:0 '), +(1460,'BarrensNW11',1,17,17,12,25,0,724.802,-3585.11,91.2962,0.655228,'1459:0 1461:0 '), +(1461,'BarrensNW12',1,17,17,8,25,0,383.869,-3439.47,95.2678,5.6425,'1460:0 1470:0 1471:0 '), +(1462,'BarrensNW13',1,17,17,12,25,0,1223.08,-3084.31,93.9201,4.30731,'1459:0 1463:0 1464:0 1466:0 '), +(1463,'BarrensNW14',1,17,17,18,25,0,1229.76,-2928.45,141.696,4.49974,'1462:0 1464:0 '), +(1464,'BarrensNW15',1,17,17,12,25,0,1012.81,-2999.42,92.4922,0.109361,'1462:0 1463:0 1465:0 1466:0 1467:0 1469:0 '), +(1465,'BarrensNW16',1,17,382,12,25,0,955.029,-3084.95,93.7057,5.40099,'1459:0 1464:0 1466:0 '), +(1466,'BarrensNW17',1,17,382,12,25,0,1020.82,-3080.5,105.204,1.82743,'1462:0 1464:0 1465:0 '), +(1467,'BarrensNW18',1,17,17,8,25,0,898.48,-2740.8,99.757,5.46973,'1464:0 1468:0 1469:0 '), +(1468,'BarrensNW19',1,17,17,8,25,0,803.624,-2654.79,91.6669,5.87813,'1467:0 1469:0 1496:0 1497:0 '), +(1469,'BarrensNW20',1,17,17,8,25,0,605.003,-2881.18,91.6679,0.109375,'1464:0 1467:0 1468:0 1470:0 1471:0 1493:0 1496:0 '), +(1470,'BarrensNW21',1,17,17,8,25,0,470.698,-3149.25,95.5705,1.16378,'1461:0 1469:0 1471:0 1472:0 '), +(1471,'BarrensNW22',1,17,17,8,25,0,382.399,-3074.79,91.8682,5.88794,'1461:0 1469:0 1470:0 1472:0 1477:0 '), +(1472,'BarrensNW23',1,17,17,8,25,0,146.635,-3204.39,80.1468,0.405853,'1470:0 1471:0 1473:0 1474:0 1477:0 1478:0 1479:0 '), +(1473,'BarrensNW24',1,17,17,8,25,0,204.31,-3418.93,30.6025,4.75106,'1450:0 1472:0 1474:0 '), +(1474,'BarrensNW25',1,17,17,8,25,0,18.7822,-3604.62,27.213,0.267903,'1442:0 1443:0 1450:0 1472:0 1473:0 1476:0 '), +(1475,'BarrensNW26',1,17,391,8,25,0,-701.915,-3931.25,25.2456,2.31781,'35:0 1476:0 '), +(1476,'BarrensNW27',1,17,17,8,25,0,-364.212,-3750.15,28.5958,0.262038,'1442:0 1475:0 1474:0 '), +(1477,'BarrensNW28',1,17,17,8,25,0,111.71,-2964.41,95.9003,5.8816,'1471:0 1472:0 1478:0 1491:0 1492:0 '), +(1478,'BarrensNW29',1,17,1699,8,25,0,-7.39568,-3036.98,91.6667,5.57137,'1472:0 1477:0 1479:0 1480:0 1489:0 1491:0 '), +(1479,'BarrensNW30',1,17,1699,8,25,0,-80.6494,-3175.83,92.6429,0.527153,'1472:0 1478:0 1480:0 '), +(1480,'BarrensNW31',1,17,1699,8,25,0,-43.7595,-3360.78,91.6672,1.44018,'1478:0 1479:0 1481:0 '), +(1481,'BarrensNW32',1,17,1699,8,25,0,-298.484,-3473.6,91.6745,0.415233,'1480:0 1482:0 1483:0 '), +(1482,'BarrensNW33',1,17,17,8,25,0,-636.173,-3699.63,92.7051,0.694045,'1481:0 1483:0 '), +(1483,'BarrensNW34',1,17,17,8,25,0,-807.155,-3308.81,91.6657,5.92872,'1482:0 1481:0 1485:0 1486:0 1484:0 '), +(1484,'BarrensNW35',1,17,17,8,25,0,-1029.31,-3412.99,75.3336,4.91947,'35:0 1483:0 1485:0 1563:0 '), +(1485,'BarrensNW36',1,17,17,8,25,0,-949.557,-3245.5,94.7159,4.38735,'1483:0 1486:0 1536:0 1538:0 1484:0 1563:0 '), +(1486,'BarrensNW37',1,17,17,8,25,0,-679.296,-2946.69,95.7877,4.08497,'1487:0 1488:0 1483:0 1485:0 1535:0 1536:0 1537:0 1539:0 '), +(1487,'BarrensNW38',1,17,17,8,25,0,-494.28,-2779.02,91.6669,0.741136,'1486:0 32:0 1488:0 '), +(1488,'BarrensNW39',1,17,1699,8,25,0,-242.594,-2859.38,91.9719,3.12681,'1486:0 1487:0 1489:0 1490:0 1491:0 '), +(1489,'BarrensNW40',1,17,1699,8,25,0,-191.258,-3014.29,91.6673,2.33356,'1478:0 1488:0 1491:0 '), +(1490,'BarrensNW41',1,17,458,8,25,0,-264.197,-2688.78,95.0971,4.53071,'32:0 1488:0 1491:0 1525:0 '), +(1491,'BarrensNW42',1,17,17,8,25,0,72.2048,-2789.84,95.8713,3.29567,'1490:0 1488:0 1489:0 1477:0 1478:0 1492:0 1525:0 '), +(1492,'BarrensNW43',1,17,458,8,25,0,205.138,-2695.31,91.667,3.68445,'1477:0 1491:0 1493:0 1494:0 1495:0 '), +(1493,'BarrensNW44',1,17,458,8,25,0,400.957,-2643.1,91.667,3.49596,'1469:0 1492:0 1494:0 1496:0 '), +(1494,'BarrensNW45',1,17,17,8,25,0,266.964,-2535.13,91.6658,3.36441,'1492:0 1493:0 1495:0 '), +(1495,'BarrensNW46',1,17,17,8,25,0,78.9108,-2561.24,92.6826,5.54193,'1492:0 1494:0 1524:0 '), +(1496,'BarrensNW47',1,17,458,8,25,0,575.696,-2568.22,95.8126,5.44179,'1468:0 1469:0 1493:0 1497:0 '), +(1497,'BarrensNW48',1,17,458,8,25,0,687.068,-2366.64,91.6681,4.22717,'1468:0 1496:0 1498:0 1499:0 '), +(1498,'BarrensNW49',1,17,458,8,25,0,804.041,-2282.18,91.6685,3.41822,'1497:0 1499:0 1500:0 1519:0 '), +(1499,'BarrensNW50',1,17,17,12,25,0,703.606,-2031.77,91.6667,4.72591,'1497:0 1498:0 1500:0 1509:0 1510:0 '), +(1500,'BarrensNW51',1,17,17,12,25,0,529.014,-2166.69,94.5542,0.545634,'1498:0 1499:0 1501:0 '), +(1501,'DreadmistPeak1',1,17,384,12,25,0,459.817,-2082.78,138.447,5.5015,'1500:0 1502:0 1504:0 '), +(1502,'DreadmistPeak2',1,17,384,12,25,0,375.749,-2068.56,138.668,6.1259,'1501:0 1503:0 '), +(1503,'DreadmistPeak3',1,17,384,12,25,0,324.847,-1976.99,98.671,5.27963,'1502:0 1510:0 1520:0 '), +(1504,'DreadmistPeak4',1,17,384,12,25,0,417.639,-2212.21,181.222,1.37424,'1501:0 1505:0 '), +(1505,'DreadmistPeak5',1,17,384,12,25,0,307.073,-2144.55,210.989,5.7273,'1504:0 1506:0 1507:0 '), +(1506,'DreadmistPeak6',1,17,2138,12,25,0,319.238,-2233.42,211.954,1.68053,'1505:0 '), +(1507,'DreadmistPeak7',1,17,384,8,25,0,353.055,-2209.97,222.18,1.93971,'1505:0 1508:0 '), +(1508,'DreadmistPeak8',1,17,384,17,25,0,322.312,-2277.26,243.384,1.42331,'1507:0 '), +(1509,'BarrensNW52',1,17,17,12,25,0,583.146,-1755.46,92.9558,1.45863,'1499:0 1510:0 1511:0 '), +(1510,'BarrensNW53',1,17,17,8,25,0,372.587,-1848.86,91.6675,6.01198,'1503:0 1499:0 1509:0 1520:0 1521:0 1522:0 '), +(1511,'BarrensNW54',1,17,383,12,25,0,535.439,-1429.14,91.6668,4.989,'1509:0 1512:0 1514:0 1515:0 1516:0 '), +(1512,'BarrensNW55',1,17,383,12,25,0,709.638,-1260.36,91.682,3.91105,'1511:0 1513:0 '), +(1513,'BarrensNW56',1,17,383,12,25,0,817.064,-1365.59,91.871,2.68583,'1512:0 '), +(1514,'BarrensNW57',1,17,383,12,25,0,532.937,-1155.43,91.6668,4.67093,'1511:0 '), +(1515,'BarrensNW58',1,17,383,12,25,0,296.616,-1482.58,91.6668,0.139182,'1511:0 1517:0 1516:0 1520:0 '), +(1516,'BarrensNW59',1,17,383,12,25,0,300.682,-1617.75,91.6668,0.681106,'1511:0 1515:0 1517:0 1520:0 '), +(1517,'BarrensNW60',1,17,17,8,25,0,42.8186,-1528.23,91.6728,3.42876,'1515:0 1516:0 1520:0 1523:0 1531:0 1533:0 '), +(1518,'BarrensExitN1h',1,17,1703,8,25,4,1261.05,-2223.39,92.0335,0.0904736,'1110:0 1519:0 '), +(1519,'BarrensExitN2',1,17,1703,8,25,0,1157.98,-2338.54,92.3068,2.80599,'1111:0 1498:0 1518:0 '), +(1520,'BarrensNW61',1,17,17,8,25,0,242.384,-1748.46,91.6668,5.32751,'1503:0 1510:0 1515:0 1516:0 1517:0 1521:0 '), +(1521,'BarrensNW62',1,17,17,8,25,0,130.568,-1864.4,92.6122,0.169403,'1510:0 1520:0 1522:0 1523:0 '), +(1522,'BarrensNW63',1,17,386,8,25,0,132.261,-1980.79,93.8709,0.723104,'1510:0 1521:0 1524:0 1528:0 '), +(1523,'BarrensNW64',1,17,17,8,25,0,-136.632,-1764.4,93.1031,0.640629,'1517:0 1521:0 1524:0 1528:0 1529:0 1530:0 1531:0 '), +(1524,'BarrensNW65',1,17,17,8,25,0,-73.4923,-2214.29,92.1839,1.03725,'1495:0 1522:0 1523:0 1527:0 1528:0 '), +(1525,'BarrensNW66',1,17,17,8,25,0,-246.525,-2527.3,91.7286,5.22342,'1490:0 1491:0 1527:0 1526:0 1528:0 '), +(1526,'BarrensNW67',1,17,380,8,25,4,-349.471,-2524,95.5815,4.46591,'32:0 1525:0 1527:0 '), +(1527,'BarrensNW68',1,17,17,8,25,0,-344.921,-2376.03,92.3735,5.15509,'1524:0 1525:0 1526:0 1528:0 1534:0 '), +(1528,'BarrensNW69',1,17,17,8,25,0,-281.4,-2113.94,95.7902,6.18005,'1522:0 1523:0 1524:0 1525:0 1527:0 1529:0 '), +(1529,'BarrensNW70',1,17,17,8,25,0,-430.244,-1826.78,95.7877,0.218878,'1523:0 1528:0 1530:0 1557:0 '), +(1530,'BarrensNW71',1,17,17,8,25,0,-515.745,-1537.11,91.6677,5.10603,'1523:0 1529:0 1531:0 1532:0 1557:0 '), +(1531,'BarrensNW72',1,17,17,8,25,0,-343.855,-1488.98,92.6671,5.38288,'1517:0 1523:0 1530:0 1532:0 '), +(1532,'BarrensExitW1h',1,17,1702,8,25,4,-390.512,-1368.33,91.7005,4.15177,'1530:0 1531:0 1533:0 '), +(1533,'BarrensExitW2h',1,17,17,13,25,0,-245.782,-1071.05,33.877,5.35494,'1517:0 1532:0 1619:0 1621:0 '), +(1534,'BarrensC1',1,17,17,8,25,0,-554.876,-2457.12,91.7172,0.415224,'1527:0 1535:0 '), +(1535,'BarrensC2',1,17,458,8,25,0,-733.228,-2616.64,95.7878,6.07401,'32:0 1486:0 1534:0 1536:0 1537:0 1539:0 '), +(1536,'BarrensC3',1,17,388,8,25,0,-1088.78,-2943.42,93.188,0.309157,'1485:0 1486:0 1535:0 1537:0 1538:0 1539:0 1563:0 '), +(1537,'BarrensC4',1,17,388,8,25,0,-1234.76,-2828.43,94.4675,5.62828,'1486:0 1535:0 1536:0 1542:0 1543:0 '), +(1538,'BarrensC5',1,17,17,8,25,0,-1482.86,-2989.48,91.6667,0.000901222,'1485:0 1536:0 1542:0 1543:0 1546:0 1545:0 1544:0 1563:0 '), +(1539,'BarrensC6',1,17,458,8,25,0,-950.333,-2486.75,94.2879,5.17669,'98:0 1486:0 1535:0 1536:0 1540:0 1541:0 '), +(1540,'BarrensC7',1,17,387,8,25,0,-1024.84,-2164.71,84.5132,0.52637,'98:0 1539:0 1541:0 '), +(1541,'BarrensC8',1,17,17,8,25,0,-1117.92,-2388.71,94.9693,5.93383,'1539:0 1540:0 1542:0 1556:0 '), +(1542,'BarrensC9',1,17,458,8,25,0,-1390.23,-2505.64,95.7878,4.84017,'1537:0 1538:0 1541:0 1543:0 1555:0 '), +(1543,'BarrensC10',1,17,458,8,25,0,-1623.03,-2514.87,91.6709,0.0433447,'1537:0 1538:0 1542:0 1546:0 1555:0 1544:0 '), +(1544,'BarrensC11',1,17,17,8,25,0,-1811.46,-3139.58,82.9337,0.885322,'1571:0 1545:0 1538:0 1543:0 1546:0 1568:0 1613:0 '), +(1545,'BarrensC12',1,17,1698,14,25,0,-1907.03,-2804.32,92.2989,1.69465,'1538:0 1546:0 1547:0 1548:0 1544:0 '), +(1546,'BarrensC13',1,17,1157,8,25,0,-1711.88,-2559.16,91.6722,0.463532,'1538:0 1543:0 1545:0 1548:0 1549:0 1544:0 '), +(1547,'BarrensC14',1,17,1698,14,25,0,-2248.79,-2613.16,92.2697,5.81799,'1545:0 1548:0 1550:0 1551:0 '), +(1548,'BarrensC15',1,17,1698,14,25,0,-2094.77,-2532.69,91.9084,5.93581,'1545:0 1546:0 1547:0 1549:0 1550:0 1551:0 1583:0 '), +(1549,'BarrensC16',1,17,1157,8,25,0,-1876.87,-2436.12,95.4993,5.38799,'1546:0 1548:0 1551:0 1552:0 1555:0 '), +(1550,'BarrensC17',1,17,1157,8,25,0,-2277.85,-2179.6,95.7945,5.15236,'174:0 1547:0 1548:0 1551:0 1552:0 1554:0 1583:0 1585:0 '), +(1551,'BarrensC18',1,17,1157,8,25,0,-2101.34,-2220.57,95.7877,5.73158,'174:0 1547:0 1548:0 1549:0 1550:0 1552:0 '), +(1552,'BarrensC19',1,17,1700,14,25,0,-1909.45,-2038.55,92.9508,3.72685,'1549:0 1550:0 1551:0 1553:0 1554:0 1555:0 1560:0 '), +(1553,'BarrensC20',1,17,1700,14,25,0,-2121.99,-1733.9,91.6668,3.88196,'174:0 1552:0 1554:0 1560:0 1584:0 '), +(1554,'BarrensC21',1,17,1700,14,25,0,-2085.34,-1938.99,95.871,2.79616,'174:0 1550:0 1552:0 1553:0 1560:0 '), +(1555,'BarrensC22',1,17,17,14,25,0,-1638.12,-2190.4,92.6765,5.08558,'1542:0 1543:0 1549:0 1552:0 1556:0 1558:0 1561:0 '), +(1556,'BarrensC23',1,17,17,8,25,0,-1266.05,-2043.5,92.6438,3.55996,'1541:0 1555:0 1557:0 1558:0 '), +(1557,'BarrensC24',1,17,17,8,25,0,-926.196,-1839.83,92.0056,5.03258,'98:0 1529:0 1530:0 1556:0 1558:0 '), +(1558,'BarrensC25',1,17,1700,14,25,0,-1425.03,-1796.65,92.0678,5.31103,'1555:0 1556:0 1557:0 1559:0 1561:0 '), +(1559,'BarrensC26',1,17,1700,14,25,0,-1440.3,-1560.56,95.9826,0.920663,'1558:0 1561:0 '), +(1560,'BarrensC27',1,17,1700,14,25,0,-1871.3,-1875.92,93.6595,4.45692,'1552:0 1553:0 1554:0 1561:0 '), +(1561,'BarrensC28',1,17,1700,14,25,0,-1612.86,-1781.52,91.7801,1.78067,'1555:0 1558:0 1559:0 1560:0 '), +(1562,'BarrensC29',1,17,391,8,25,0,-1376.36,-3840.81,18.7024,0.388567,'35:0 1565:0 1569:0 '), +(1563,'BarrensC30',1,17,17,8,25,0,-1136.17,-3317.73,91.835,0.363033,'1484:0 1485:0 1536:0 1538:0 1564:0 '), +(1564,'BarrensC31',1,17,17,8,25,0,-1232.04,-3640.85,93.8864,1.23286,'1563:0 1565:0 '), +(1565,'BarrensC32',1,17,17,12,25,0,-1358.56,-3651.68,91.8621,5.3562,'1562:0 1564:0 1566:0 '), +(1566,'BarrensC33',1,17,17,12,25,0,-1611.42,-3708.07,89.8631,0.284498,'1565:0 1567:0 1568:0 '), +(1567,'BarrensC34',1,17,17,12,25,0,-1630.78,-3593.7,93.0714,4.90264,'1566:0 1568:0 '), +(1568,'BarrensC35',1,17,17,12,25,0,-1755.9,-3578.38,93.1759,5.92169,'1544:0 1566:0 1567:0 '), +(1569,'BarrensC36',1,17,391,8,25,0,-1644.33,-3851.75,13.843,0.197311,'1562:0 1570:0 '), +(1570,'BarrensC37',1,17,385,8,25,0,-1877.52,-3684.16,8.56349,5.68766,'1569:0 1571:0 1572:0 1575:0 '), +(1571,'BarrensC38',1,17,385,8,25,0,-1962.03,-3412.38,56.1606,4.99848,'1544:0 1570:0 '), +(1572,'BarrensC39',1,17,385,8,25,0,-2093.49,-3786.65,1.11667,0.38229,'1570:0 1573:0 '), +(1573,'BarrensC40',1,17,385,33,45,0,-2168.16,-3893.99,0.105911,2.3988,'1572:0 1574:0 1900:0 '), +(1574,'BarrensC41',1,17,385,33,45,0,-2296.44,-3875.08,0.411085,0.0838384,'1573:0 1896:0 1900:0 '), +(1575,'NorthwatchHold1',1,17,385,12,25,4,-2006.43,-3674.7,21.8223,5.27337,'1570:0 1576:0 '), +(1576,'NorthwatchHold2',1,17,385,12,25,4,-2014.16,-3558.11,22.9401,4.40551,'1575:0 1577:0 '), +(1577,'NorthwatchHold3',1,17,385,12,25,4,-2105.62,-3592.85,59.4242,0.125087,'1576:0 1578:0 1579:0 '), +(1578,'NorthwatchHold4',1,17,385,8,25,4,-2106.59,-3663.32,96.1895,0.928159,'1577:0 '), +(1579,'NorthwatchHold5',1,17,385,12,25,4,-2186.42,-3696.81,90.2787,1.00002,'1577:0 1580:0 1581:0 '), +(1580,'NorthwatchHold6',1,17,385,12,25,4,-2222.08,-3775.47,130.862,0.551559,'1579:0 '), +(1581,'NorthwatchHold7',1,17,385,12,25,4,-2146.93,-3560.64,91.6658,4.29007,'1579:0 1582:0 '), +(1582,'NorthwatchHold8',1,17,385,12,25,4,-2088.88,-3507.98,130.084,4.00692,'1581:0 '), +(1583,'BarrensS1',1,17,1156,18,25,0,-2489.54,-2427.29,91.7094,6.28066,'1548:0 1550:0 1585:0 1586:0 '), +(1584,'BarrensS2',1,17,1156,18,25,0,-2509.21,-1790.84,91.6888,0.117235,'174:0 1553:0 1585:0 1586:0 1587:0 '), +(1585,'BarrensS3',1,17,1157,18,25,0,-2496.45,-2147.95,95.7846,6.25905,'174:0 1550:0 1583:0 1584:0 1586:0 '), +(1586,'BarrensS4',1,17,1157,18,25,0,-2759.08,-2184.15,95.793,0.0131506,'1583:0 1584:0 1585:0 1587:0 '), +(1587,'BarrensS5',1,17,1157,18,25,0,-2945.71,-2047.99,95.7879,0.507956,'1584:0 1586:0 1588:0 1589:0 1591:0 '), +(1588,'BarrensS6',1,17,1157,18,25,0,-3285.06,-2049.93,92.2466,6.10862,'1587:0 1589:0 1590:0 1591:0 1593:0 1594:0 1596:0 '), +(1589,'BarrensS7',1,17,390,18,25,0,-3079.79,-1941.98,94.9653,3.28119,'1587:0 1588:0 1590:0 '), +(1590,'BarrensS8',1,17,390,18,25,0,-3131.93,-1680.95,92.0185,4.74203,'1588:0 1589:0 '), +(1591,'BarrensS9',1,17,390,18,25,0,-3131.04,-2171.34,93.5897,1.45786,'1587:0 1588:0 1592:0 1596:0 '), +(1592,'BarrensS10',1,17,390,18,25,0,-3110.95,-2348.03,94.3578,1.81325,'1591:0 '), +(1593,'BarrensS11',1,17,1156,18,25,0,-3458.57,-1830.89,91.667,5.2042,'1588:0 1595:0 '), +(1594,'BarrensS12',1,17,1157,18,25,0,-3481.33,-2051.77,96.4547,0.0284251,'1588:0 1595:0 1596:0 '), +(1595,'BarrensS13',1,17,1157,18,25,0,-3653.21,-2015.41,91.6668,0.540897,'1593:0 1594:0 1596:0 1597:0 1599:0 1600:0 '), +(1596,'BarrensS14',1,17,1156,18,25,0,-3651.34,-2318.77,91.6674,0.625316,'1595:0 1588:0 1591:0 1594:0 1597:0 1607:0 1872:0 '), +(1597,'BarrensS15',1,17,1157,18,25,0,-3840.72,-2048.27,91.6821,6.27935,'1595:0 1596:0 1598:0 1599:0 1600:0 1607:0 '), +(1598,'BarrensS16',1,17,1157,18,25,0,-4067.36,-2048.82,91.6672,6.16353,'1597:0 1601:0 1602:0 1607:0 1610:0 '), +(1599,'BarrensS17',1,17,1701,18,25,0,-3730.53,-1594.06,93.2113,4.82441,'1595:0 1597:0 1600:0 1601:0 '), +(1600,'BarrensS18',1,17,1701,18,25,0,-3894.71,-1618.14,91.6668,0.128916,'1595:0 1597:0 1599:0 1601:0 '), +(1601,'BarrensS19',1,17,1701,18,25,0,-4039.89,-1841.83,94.2054,0.676733,'1598:0 1599:0 1600:0 1602:0 '), +(1602,'BarrensS20',1,17,1157,18,25,0,-4188.61,-1914.07,91.6668,5.68758,'1598:0 1601:0 1603:0 '), +(1603,'BarrensS21',1,17,1157,18,25,0,-4356.79,-1884.72,89.6352,5.97818,'1602:0 1604:0 1606:0 '), +(1604,'BarrensS22',1,17,1157,18,25,0,-4478.66,-1866.94,86.1087,1.26186,'1603:0 1605:0 '), +(1605,'BarrensRFK1',1,17,1717,18,25,0,-4476,-1686.51,81.5791,4.60765,'1604:0 '), +(1606,'BarrensRFD1',1,17,1316,30,40,0,-4487.33,-2054.23,75.9531,5.76807,'1603:0 '), +(1607,'BaelModan1',1,17,359,18,25,4,-3989.77,-2218.81,95.0004,6.06065,'1596:0 1597:0 1598:0 1608:0 '), +(1608,'BaelModan2',1,17,359,18,25,4,-4109.4,-2315.19,125.071,0.600164,'1607:0 1609:0 '), +(1609,'BaelModan3',1,17,2157,8,25,4,-4073.79,-2368.19,108.909,2.02644,'1608:0 '), +(1610,'BaelModan4',1,17,359,18,25,4,-4176.57,-2101.49,72.6592,0.294635,'1598:0 1611:0 '), +(1611,'BaelModan5',1,17,359,18,25,4,-4071.12,-2157.56,50.3844,2.75686,'1610:0 1612:0 '), +(1612,'BaelModan6',1,17,359,18,25,4,-4223.35,-2272.53,60.9001,0.575407,'1611:0 '), +(1613,'RaptorGrounds',1,17,1697,12,25,0,-2042.89,-3229.73,91.8767,0.499641,'1544:0 '), +(1614,'BarrensWC1',1,718,718,8,20,0,-692.325,-2048.77,65.8045,0.216115,'98:0 1615:0 '), +(1615,'BarrensWC2',1,718,718,12,25,0,-580.407,-2042.49,57.4486,4.80284,'1614:0 1616:0 '), +(1616,'BarrensWC3',1,718,718,12,25,0,-677.272,-2203.72,25.3109,0.830688,'1615:0 1617:0 '), +(1617,'BarrensWC4',1,718,718,12,25,0,-638.458,-2387.33,25.6848,1.69853,'1616:0 '), +(1618,'RatchetSpawn',1,17,392,8,20,1,-1057.17,-3641.62,23.878,5.32863,'35:0 '), +(1619,'BarrensExitW2_1a',1,17,17,13,25,2,-365.7,-970.989,26.4604,0.0515498,'1533:0 1620:0 '), +(1620,'BarrensExitW2_2',1,17,17,13,25,2,-257.953,-835.045,8.42398,4.38694,'1619:0 1621:0 1622:0 '), +(1621,'STMExitE',1,406,469,13,25,4,-231.912,-777.297,7.15901,4.25107,'1533:0 1620:0 1622:0 '), +(1622,'StonetalonSE1',1,406,469,13,25,0,-183.878,-696.708,0.285728,4.19806,'1620:0 1621:0 1623:0 '), +(1623,'StonetalonSE2',1,406,2538,13,25,0,7.63915,-645.484,-33.8698,3.50887,'1622:0 1624:0 1625:0 1626:0 1630:0 '), +(1624,'StonetalonSE3',1,406,469,13,25,0,-21.452,-771.961,1.19909,1.25477,'1623:0 1625:0 '), +(1625,'StonetalonSE4',1,406,469,13,25,0,173.964,-863.908,2.87119,2.42107,'1623:0 1624:0 '), +(1626,'StonetalonSE5',1,406,2537,13,25,0,127.03,-646.104,-0.157034,3.15149,'1623:0 1627:0 '), +(1627,'StonetalonSE6',1,406,2537,13,25,0,102.577,-522.122,3.25329,4.9677,'1626:0 1628:0 1629:0 '), +(1628,'StonetalonSE7',1,406,2537,13,25,0,219.214,-475.456,21.9453,3.60701,'1627:0 1629:0 '), +(1629,'StonetalonSE8',1,406,469,13,25,0,104.777,-261.978,5.20105,5.15819,'1627:0 1628:0 1632:0 1633:0 '), +(1630,'StonetalonSE9',1,406,2538,13,25,0,-62.1728,-442.081,-37.1112,5.01095,'1623:0 1631:0 '), +(1631,'StonetalonSE10',1,406,469,13,25,0,-29.9825,-291.416,-5.66732,4.59076,'1630:0 1632:0 '), +(1632,'StonetalonSE11',1,406,2539,13,25,0,-2.18294,-227.074,15.1619,4.29231,'1629:0 1631:0 1633:0 1640:0 '), +(1633,'StonetalonSE12',1,406,1076,13,25,0,59.7029,-127.555,15.3056,4.18039,'1629:0 1632:0 1634:0 '), +(1634,'StonetalonSE13',1,406,1076,13,25,0,109.545,-71.7213,22.4065,2.6092,'1633:0 1635:0 1641:0 '), +(1635,'StonetalonSE14',1,406,2540,13,25,0,23.5805,-29.4616,31.6752,5.8097,'1634:0 1636:0 '), +(1636,'StonetalonSE15',1,406,2540,13,25,0,-14.5426,41.0345,49.7486,5.30902,'1635:0 1637:0 '), +(1637,'StonetalonSE16',1,406,2540,13,25,0,-152.111,135.096,48.9698,5.51519,'1636:0 1638:0 '), +(1638,'StonetalonSE17',1,406,3157,13,25,0,-114.052,239.055,102.383,4.22518,'1637:0 1639:0 '), +(1639,'StonetalonSE18',1,406,3157,13,25,0,2.62312,403.85,101.011,3.79714,'1638:0 '), +(1640,'StonetalonSE19',1,406,2539,13,25,5,-235.218,-347.587,20.2574,0.490614,'1632:0 '), +(1641,'StonetalonSE20',1,406,1076,13,25,0,197.073,203.918,52.5873,4.43331,'1634:0 1642:0 '), +(1642,'StonetalonSE21',1,406,1076,13,25,0,274.453,322.855,41.911,4.60022,'1641:0 1643:0 '), +(1643,'StonetalonSE22',1,406,1076,13,25,0,447.877,347.349,47.8893,3.28665,'1642:0 1644:0 1646:0 '), +(1644,'StonetalonSE23',1,406,2541,13,25,0,426.687,491.945,98.4352,4.79266,'1643:0 1645:0 '), +(1645,'StonetalonSE24',1,406,2541,13,25,0,465.249,636.071,69.1319,4.43923,'1644:0 151:0 '), +(1646,'StonetalonSE25',1,406,1076,13,25,0,547.002,317.026,51.4155,2.61083,'1643:0 1647:0 1660:0 '), +(1647,'StonetalonSE26',1,406,461,13,25,0,944.851,205.469,22.8061,2.8916,'1646:0 1648:0 1649:0 1650:0 '), +(1648,'StonetalonE1',1,406,636,13,25,0,1111.38,-250.367,-1.11603,1.94521,'1647:0 153:0 1649:0 1651:0 1655:0 '), +(1649,'StonetalonE2',1,406,636,13,25,0,1154.28,55.6527,1.56437,2.89789,'1647:0 1648:0 152:0 1650:0 1651:0 1655:0 '), +(1650,'StonetalonE3',1,406,461,13,25,0,1307.08,338.017,30.2635,3.38681,'152:0 1647:0 1649:0 1651:0 '), +(1651,'StonetalonE4',1,406,461,13,25,0,1299.88,-37.5445,6.46784,2.5696,'152:0 1648:0 1649:0 1650:0 1655:0 '), +(1652,'StonetalonE5',1,406,463,13,25,0,1596.92,-164.112,37.8,1.7689,'152:0 1653:0 1656:0 '), +(1653,'StonetalonE6',1,406,461,13,25,0,1503.38,-325.077,23.7601,0.679167,'154:0 1652:0 1655:0 '), +(1654,'StonetalonE7',1,406,461,13,25,0,1239.88,-573.82,14.7456,2.30493,'153:0 1655:0 '), +(1655,'StonetalonE8',1,406,461,13,25,0,1274.45,-389.01,15.6582,4.91048,'153:0 154:0 1648:0 1649:0 1651:0 1653:0 1654:0 '), +(1656,'StonetalonE9',1,406,463,13,25,0,1689.34,-66.4877,90.3719,1.43312,'1652:0 1657:0 '), +(1657,'StonetalonE10',1,406,463,13,25,0,1683.31,88.067,136.952,4.78089,'1656:0 1658:0 '), +(1658,'StonetalonE11',1,406,463,13,25,0,1607.48,98.5127,98.5487,1.23875,'1657:0 1659:0 '), +(1659,'StonetalonE12',1,406,463,13,25,0,1607.14,183.852,104.64,5.9099,'1658:0 '), +(1660,'StonetalonC1',1,406,1076,13,25,0,741.152,432.351,64.3987,3.6401,'1646:0 1661:0 1662:0 '), +(1661,'StonetalonC2',1,406,1076,13,25,3,734.898,323.955,63.5237,1.26623,'1660:0 1662:0 '), +(1662,'StonetalonC3',1,406,1076,13,25,0,911.255,644.216,97.0932,4.02886,'1660:0 1661:0 45:0 1670:0 '), +(1663,'StonetalonC4',1,406,460,13,25,5,967.945,1059.83,107.154,4.48637,'45:0 1664:0 '), +(1664,'StonetalonC5',1,406,460,20,25,4,857.673,1068.45,134.552,6.12668,'1663:0 1665:0 '), +(1665,'StonetalonC6',1,406,406,20,25,4,770.751,1128.86,184.567,5.69273,'1664:0 1666:0 '), +(1666,'StonetalonC7',1,406,406,20,25,4,763.22,1201.45,167.109,4.77383,'1665:0 1667:0 '), +(1667,'StonetalonC8',1,406,406,20,25,4,612.567,1230.12,105.052,5.96764,'1666:0 1668:0 '), +(1668,'StonetalonC9',1,406,465,20,25,4,639.837,1320.23,57.7872,0.670119,'1667:0 1669:0 '), +(1669,'StonetalonC10',1,406,465,20,25,0,717.122,1407.26,-11.3895,4.23973,'1668:0 1698:0 1699:0 1703:0 '), +(1670,'StonetalonC11',1,406,1076,16,25,0,1254.55,723.188,177.709,3.60317,'1662:0 1671:0 1677:0 '), +(1671,'StonetalonC12',1,406,406,18,25,2,1149.35,812.103,216.145,0.074743,'1670:0 1672:0 '), +(1672,'StonetalonC13',1,406,406,18,25,2,1174.45,1039.29,211.338,4.51028,'1671:0 1673:0 '), +(1673,'StonetalonC14',1,406,406,18,25,2,1150.98,1125.82,230.66,4.8323,'1672:0 1674:0 '), +(1674,'StonetalonC15',1,406,406,18,25,2,1185.65,1218.77,221.734,4.2727,'1673:0 1675:0 '), +(1675,'StonetalonC16',1,406,406,18,25,2,1120.4,1212.75,204.833,0.0197749,'1674:0 1676:0 '), +(1676,'StonetalonC17',1,406,406,18,25,2,1150.62,1371.11,126.775,4.37873,'1675:0 1697:0 '), +(1677,'StonetalonC18',1,406,464,16,25,0,1370.73,733.802,156.258,3.23598,'1670:0 1678:0 1681:0 '), +(1678,'StonetalonC19',1,406,464,16,25,0,1446.65,663,145.249,2.57819,'1677:0 1679:0 1680:0 '), +(1679,'StonetalonC20',1,406,464,16,25,0,1553.51,543.727,164.574,2.16586,'1678:0 1680:0 '), +(1680,'StonetalonC21',1,406,464,16,25,0,1584.31,756.555,132.915,4.49848,'1678:0 1679:0 1681:0 1686:0 '), +(1681,'StonetalonC22',1,406,464,16,25,0,1440.45,891.877,138.324,4.65164,'1677:0 1680:0 1682:0 1683:0 '), +(1682,'StonetalonC23',1,406,464,16,25,0,1361.44,1063.47,166.157,5.2289,'1681:0 1683:0 '), +(1683,'StonetalonC24',1,406,464,16,25,0,1500.8,1046.96,144.855,4.37673,'1681:0 1682:0 1684:0 1685:0 '), +(1684,'StonetalonC25',1,406,464,16,25,0,1668.56,1003.04,136.669,2.88249,'1683:0 1685:0 1686:0 1687:0 '), +(1685,'StonetalonC26',1,406,406,16,25,0,1533.39,1167.23,150.198,5.27402,'1683:0 1684:0 1695:0 '), +(1686,'StonetalonC27',1,406,464,16,25,0,1837.17,772.144,145.217,3.08276,'1680:0 1684:0 1687:0 '), +(1687,'StonetalonC28',1,406,464,16,25,0,1832.42,946.127,147.601,2.87658,'1684:0 1686:0 1688:0 '), +(1688,'StonetalonN1',1,406,406,16,25,0,2089.45,985.763,224.629,0.496822,'1687:0 1689:0 1690:0 '), +(1689,'StonetalonN2',1,406,467,16,25,0,2389.72,1113.13,305.081,3.60307,'1688:0 '), +(1690,'StonetalonN3',1,406,467,16,25,0,2406.02,1262.32,293.173,4.48076,'39:0 1688:0 1691:0 1694:0 '), +(1691,'StonetalonN4',1,406,467,16,25,0,2308.04,1474.22,278.518,5.29758,'1692:0 1694:0 1690:0 '), +(1692,'StonetalonN5',1,406,468,16,25,0,2455.85,1706.87,321.807,3.7366,'1691:0 155:0 39:0 1694:0 '), +(1693,'StonetalonN6',1,406,468,16,25,4,2502.8,1923.14,341.618,4.8177,'155:0 '), +(1694,'StonetalonN7',1,406,467,18,25,0,2490.31,1474.07,262.723,0.177969,'39:0 1691:0 1692:0 1690:0 '), +(1695,'StonetalonW1',1,406,406,18,25,0,1531.08,1382.61,154.485,3.40792,'1685:0 1696:0 '), +(1696,'StonetalonW2',1,406,406,18,25,0,1396,1443.1,122.505,3.64354,'1695:0 1697:0 '), +(1697,'StonetalonW3',1,406,406,18,25,0,1250.91,1480.82,78.0749,3.62587,'1676:0 1696:0 1698:0 '), +(1698,'StonetalonW4',1,406,465,20,25,0,926.026,1574.39,-15.7829,5.87604,'1669:0 1697:0 1702:0 1703:0 '), +(1699,'StonetalonW5',1,406,465,20,25,0,546.937,1540.31,-5.73179,5.59329,'1669:0 1700:0 1703:0 '), +(1700,'StonetalonW6',1,406,465,20,25,0,498.082,1756.93,4.7637,5.0278,'1699:0 1701:0 1703:0 1704:0 '), +(1701,'StonetalonW7',1,406,465,20,25,0,684.965,1869.36,-7.54489,3.9518,'1700:0 1702:0 1703:0 '), +(1702,'StonetalonW8',1,406,465,20,25,0,905.075,1799.18,-4.36236,2.8974,'1698:0 1701:0 1703:0 '), +(1703,'StonetalonW9',1,406,465,20,25,0,709.779,1654.47,-27.9084,0.454811,'1669:0 1698:0 1699:0 1700:0 1701:0 1702:0 '), +(1704,'STMExitSW',1,406,465,20,25,0,386.19,1791.83,40.6481,5.89762,'1700:0 1705:0 '), +(1705,'DesolaceExitN',1,405,405,28,40,0,254.286,1837.65,86.6541,5.89961,'1704:0 1706:0 '), +(1706,'DesolaceN1',1,405,405,28,40,0,136.31,1790.75,86.2292,4.91001,'1705:0 1707:0 1708:0 1709:0 '), +(1707,'DesolaceN2',1,405,405,28,40,0,174.958,1580.05,166.08,3.19588,'1706:0 1708:0 '), +(1708,'DesolaceN3',1,405,405,28,40,0,-43.4822,1503,100.016,0.657084,'1706:0 1707:0 1709:0 1711:0 1712:0 1713:0 '), +(1709,'DesolaceN4',1,405,599,28,40,0,-240.456,1624.18,94.7617,2.12576,'1706:0 1708:0 1710:0 1711:0 1712:0 '), +(1710,'DesolaceN5',1,405,599,28,40,0,-478.435,1651.62,103.311,6.16664,'1709:0 1711:0 1724:0 1727:0 '), +(1711,'DesolaceN6',1,405,405,28,40,0,-519.027,1436.95,89.0591,0.360579,'1708:0 1709:0 1710:0 1712:0 1723:0 1743:0 1744:0 1745:0 '), +(1712,'DesolaceN7',1,405,405,28,40,0,-262.519,1170.51,90.2776,1.81553,'1708:0 1709:0 1711:0 1713:0 1717:0 1720:0 1723:0 '), +(1713,'DesolaceN8',1,405,405,28,40,0,-68.2755,1158.91,90.8663,1.88621,'1708:0 1712:0 1714:0 1717:0 1720:0 '), +(1714,'DesolaceN9',1,405,608,28,40,2,24.7744,1218.69,134.023,3.19194,'1713:0 1715:0 '), +(1715,'DesolaceN10',1,405,608,28,40,2,166.996,1219.58,166.116,3.12321,'1714:0 1716:0 '), +(1716,'DesolaceN11',1,405,608,28,40,2,222.502,1276.25,189.878,5.01405,'1715:0 60:0 '), +(1717,'DesolaceN12',1,405,603,28,40,0,-37.1114,929.567,91.722,2.17957,'1712:0 1713:0 1718:0 1720:0 1721:0 '), +(1718,'DesolaceN13',1,405,603,28,40,0,7.08753,779.096,91.8455,4.34648,'1717:0 1719:0 '), +(1719,'DesolaceN14',1,405,603,28,40,0,-67.217,604.321,91.3718,1.59562,'1718:0 1720:0 1721:0 '), +(1720,'DesolaceN15',1,405,603,28,40,0,-319.285,898.298,89.3548,5.81712,'1712:0 1713:0 1717:0 1719:0 1721:0 1722:0 1723:0 '), +(1721,'DesolaceN16',1,405,603,28,40,0,-88.9887,771.679,132.892,1.27553,'1717:0 1719:0 1720:0 '), +(1722,'DesolaceN17',1,405,405,28,40,0,-524.771,816.278,91.0366,0.333055,'1720:0 1723:0 1744:0 '), +(1723,'DesolaceN18',1,405,405,28,40,0,-518.678,1099.09,93.063,6.1992,'1711:0 1712:0 1720:0 1722:0 1744:0 1745:0 '), +(1724,'DesolaceN19',1,405,599,28,40,0,-433.656,1712.56,127.171,4.07939,'1710:0 1725:0 1726:0 '), +(1725,'DesolaceN20',1,405,599,28,40,0,-346.92,1761.61,138.371,0.287887,'1724:0 '), +(1726,'DesolaceN21',1,405,599,28,40,0,-409.361,1865.43,127.463,4.65667,'1724:0 '), +(1727,'DesolaceN22',1,405,599,28,40,0,-557.524,1836.01,96.4213,5.15932,'1710:0 1728:0 1743:0 '), +(1728,'DesolaceN23',1,405,405,28,40,0,-596.61,2002.61,88.4432,6.14696,'1727:0 1729:0 1730:0 1731:0 1742:0 1743:0 '), +(1729,'DesolaceN24',1,405,405,28,40,0,-409.395,2019.53,98.1479,3.16245,'1728:0 1730:0 1731:0 '), +(1730,'DesolaceN25',1,405,598,28,40,0,-362.318,2211.47,90.4702,1.2127,'1728:0 1729:0 1731:0 1732:0 1733:0 '), +(1731,'DesolaceN26',1,405,2405,28,40,0,-574.499,2247.88,89.8498,5.11024,'1728:0 1729:0 1730:0 1732:0 1738:0 1741:0 1742:0 1772:0 '), +(1732,'DesolaceN27',1,405,2405,28,40,0,-436.581,2351.38,60.8605,4.01462,'1730:0 1731:0 1734:0 1735:0 '), +(1733,'DesolaceN28',1,405,598,28,40,0,-45.3846,2199.95,94.7517,3.10159,'1730:0 '), +(1734,'DesolaceN29',1,405,598,28,40,0,-223.597,2401.87,19.3117,3.25671,'1732:0 1735:0 '), +(1735,'DesolaceN30',1,405,598,28,40,0,-240.439,2492.2,1.43883,3.79864,'1732:0 1734:0 1736:0 1737:0 '), +(1736,'DesolaceN31',1,405,2405,28,40,0,-370.369,2605.96,1.54797,5.56185,'1735:0 1739:0 '), +(1737,'DesolaceN32',1,405,2406,31,40,0,263.113,2974.68,2.07535,3.87325,'1735:0 '), +(1738,'DesolaceN33',1,405,2405,28,40,0,-601.969,2431.57,74.1931,4.86205,'1731:0 1739:0 1741:0 '), +(1739,'DesolaceN34',1,405,2405,28,40,0,-502.176,2576.74,18.2199,4.19054,'1736:0 1738:0 1740:0 '), +(1740,'DesolaceN35',1,405,598,28,40,0,-719.849,2595.3,58.3371,6.23453,'1739:0 1741:0 '), +(1741,'DesolaceN36',1,405,405,31,40,0,-750.951,2392.96,91.9055,1.38273,'1731:0 1738:0 1740:0 1742:0 1770:0 1772:0 '), +(1742,'DesolaceN37',1,405,405,28,40,0,-759.272,2036.09,91.7138,1.7715,'1728:0 1731:0 1741:0 1770:0 1771:0 '), +(1743,'DesolaceN38',1,405,405,28,40,0,-785.037,1666.3,90.7953,3.33248,'1711:0 1727:0 1728:0 1745:0 1769:0 1770:0 '), +(1744,'DesolaceN39',1,405,609,28,40,0,-796.298,1079.33,90.2949,6.11672,'1711:0 1722:0 1723:0 1745:0 1746:0 1747:0 '), +(1745,'DesolaceN40',1,405,405,28,40,0,-754.549,1363.26,88.5616,5.47467,'1711:0 1723:0 1743:0 1744:0 1769:0 '), +(1746,'DesolaceN41',1,405,609,28,40,0,-897.587,897.096,95.0181,0.982181,'1744:0 1747:0 1748:0 '), +(1747,'DesolaceC1',1,405,405,28,40,0,-1125.97,1230.91,93.7933,5.43931,'1744:0 1746:0 1748:0 1749:0 1769:0 '), +(1748,'DesolaceC2',1,405,405,28,40,0,-1391.67,958.264,89.5921,0.552166,'1746:0 1747:0 1750:0 1751:0 '), +(1749,'DesolaceC3',1,405,405,28,40,0,-1427.18,1225.61,103.863,0.133931,'1747:0 129:0 1750:0 '), +(1750,'DesolaceC4',1,405,604,28,40,0,-1577.16,1114.97,90.6547,0.632647,'129:0 1748:0 1749:0 1751:0 1760:0 1762:0 '), +(1751,'DesolaceC5',1,405,604,28,40,0,-1631.55,978.868,90.3134,0.0954253,'1748:0 1750:0 1752:0 1759:0 1760:0 '), +(1752,'DesolaceC6',1,405,604,34,40,0,-1672.96,839.546,93.6943,1.409,'1751:0 1753:0 '), +(1753,'DesolaceC7',1,405,604,34,40,0,-1824.57,798.663,103.148,0.209302,'1752:0 1754:0 '), +(1754,'DesolaceC8',1,405,2198,34,40,0,-1847.21,666.041,107.727,1.26565,'1753:0 1755:0 1756:0 '), +(1755,'DesolaceC9',1,405,2198,34,40,0,-1837.82,584.775,136.457,1.64775,'1754:0 '), +(1756,'DesolaceC10',1,405,2198,34,40,0,-2020.93,674.999,118.726,6.26783,'1754:0 1757:0 '), +(1757,'DesolaceC11',1,405,2198,34,40,0,-2022.56,589.179,143.38,5.31357,'1756:0 1758:0 '), +(1758,'DesolaceC12',1,405,2198,34,40,0,-1933.59,514.493,164.939,2.49988,'1757:0 '), +(1759,'DesolaceC13',1,405,604,28,40,0,-1799.06,928.096,91.3039,0.218297,'1751:0 1760:0 1762:0 '), +(1760,'DesolaceC14',1,405,604,28,40,0,-1889.74,1110.04,92.5553,5.97134,'1750:0 1751:0 1759:0 1761:0 1762:0 '), +(1761,'DesolaceC15',1,405,405,31,40,0,-1897.9,1372.61,61.1381,4.74417,'1760:0 1762:0 1764:0 1777:0 1811:0 1813:0 '), +(1762,'DesolaceC16',1,405,604,28,40,0,-1749.34,1214.45,91.3952,0.204553,'1750:0 1759:0 1760:0 1761:0 1763:0 '), +(1763,'DesolaceC17',1,405,405,28,40,0,-1569.77,1277.99,87.9315,3.56017,'1762:0 1764:0 '), +(1764,'DesolaceC18',1,405,405,31,40,0,-1699.9,1433.42,61.6894,0.112268,'129:0 1761:0 1763:0 '), +(1765,'DesolaceC19',1,405,597,28,40,4,-1335.75,1704.43,89.9029,5.8932,'128:0 '), +(1766,'DesolaceC20',1,405,597,28,40,4,-1199.03,1771.37,99.5835,4.08873,'128:0 1767:0 '), +(1767,'DesolaceC21',1,405,597,28,40,4,-1157.92,1848.6,99.595,4.17119,'1766:0 1768:0 '), +(1768,'DesolaceC22',1,405,597,28,40,4,-1151.69,1938.33,88.8694,1.73253,'1767:0 '), +(1769,'DesolaceC23',1,405,405,31,40,0,-1193.57,1475.36,61.1112,4.30472,'128:0 129:0 1743:0 1745:0 1747:0 1770:0 1777:0 '), +(1770,'DesolaceC24',1,405,405,31,40,0,-1051.16,1759.04,62.2132,3.91398,'129:0 1741:0 1742:0 1743:0 1769:0 1771:0 1776:0 '), +(1771,'DesolaceC25',1,405,405,31,40,0,-1096.46,2045.41,58.5175,4.84859,'1742:0 1770:0 1772:0 1775:0 '), +(1772,'DesolaceC26',1,405,405,31,40,0,-1095.17,2383.16,92.5366,5.96779,'1731:0 1741:0 1771:0 1773:0 '), +(1773,'DesolaceC27',1,405,405,31,40,0,-1419.73,2331.31,91.8161,0.0556962,'1772:0 1774:0 1778:0 1779:0 1798:0 '), +(1774,'DesolaceC28',1,405,596,31,40,0,-1340.98,2101.55,63.5443,1.99366,'1773:0 1775:0 1776:0 1778:0 '), +(1775,'DesolaceC29',1,405,596,31,40,0,-1225.31,2021.97,59.0277,2.34708,'1771:0 1774:0 1776:0 '), +(1776,'DesolaceC30',1,405,596,31,40,0,-1362.19,1917.79,50.1441,0.929436,'128:0 1770:0 1774:0 1775:0 1777:0 '), +(1777,'DesolaceC31',1,405,405,31,40,0,-1575.89,1726.59,58.925,5.73608,'129:0 1761:0 1769:0 1776:0 1778:0 1809:0 1810:0 '), +(1778,'DesolaceC32',1,405,405,31,40,0,-1561.67,1991.32,61.6415,0.949069,'129:0 1773:0 1774:0 1777:0 1809:0 '), +(1779,'DesolaceC33',1,405,607,33,40,0,-1371.99,2554.03,108.02,4.4441,'1773:0 1780:0 1781:0 '), +(1780,'DesolaceC34',1,405,607,33,40,0,-1392.44,2659.84,112.065,5.41015,'1779:0 1781:0 '), +(1781,'DesolaceC35',1,405,607,33,40,0,-1251.49,2685.06,111.557,3.5193,'1779:0 1780:0 1782:0 1783:0 '), +(1782,'DesolaceC36',1,405,607,33,40,0,-1123.21,2688.52,111.831,2.59448,'1781:0 1783:0 '), +(1783,'DesolaceC37',1,405,607,33,40,0,-1161.95,2795.33,122.835,2.23711,'1781:0 1782:0 1784:0 1785:0 '), +(1784,'DesolaceC38',1,405,607,33,40,0,-1067.79,2918.91,179.121,3.97481,'1783:0 '), +(1785,'DesolaceC39',1,405,607,33,40,0,-1289.84,2920.03,113.966,5.30998,'1783:0 1786:0 '), +(1786,'DesolaceC40',1,405,607,33,40,0,-1360.57,2783.12,113.08,5.63201,'1785:0 127:0 1787:0 '), +(1787,'DesolaceC41',1,405,607,33,40,0,-1422.79,2793.13,111.697,0.937294,'127:0 1786:0 1788:0 '), +(1788,'DesolaceC42',1,405,607,33,40,0,-1470.51,2717.61,112.426,0.907843,'1787:0 1789:0 '), +(1789,'DesolaceC43',1,405,607,33,40,0,-1526.76,2740.76,111.941,5.60649,'1788:0 1790:0 '), +(1790,'DesolaceC44',1,405,607,33,40,0,-1487.94,2839.33,111.17,4.34789,'1789:0 1791:0 '), +(1791,'DesolaceC45',1,405,607,33,40,0,-1571.53,2902.93,112.301,5.73411,'1790:0 1794:0 '), +(1792,'DesolaceC46',1,405,2408,31,40,4,-1727.84,3103.88,35.0044,5.61552,'57:0 1793:0 '), +(1793,'DesolaceC47',1,405,598,31,40,4,-1841.75,3013.22,11.277,0.475075,'1792:0 1794:0 1795:0 '), +(1794,'DesolaceC48',1,405,598,31,40,0,-1852.69,2854.14,50.9586,0.237108,'1791:0 1795:0 1793:0 1796:0 1797:0 '), +(1795,'DesolaceC49',1,405,598,31,40,0,-1990.78,2803.76,56.2314,0.896844,'1794:0 1793:0 1796:0 1797:0 '), +(1796,'DesolaceS1',1,405,606,28,40,0,-2002.35,2610.23,62.383,1.22083,'1794:0 1795:0 1797:0 1799:0 1800:0 '), +(1797,'DesolaceS2',1,405,405,28,40,0,-1772.22,2471.83,73.3018,5.67404,'1794:0 1795:0 1796:0 1798:0 1800:0 '), +(1798,'DesolaceS3',1,405,405,31,40,0,-1592.19,2396.96,91.4972,2.81715,'1773:0 1797:0 1799:0 '), +(1799,'DesolaceS4',1,405,405,31,40,0,-1797.31,2149.68,62.4428,1.77847,'1796:0 1798:0 1800:0 1809:0 '), +(1800,'DesolaceS5',1,405,606,28,40,0,-1967.47,2474.94,61.5806,1.35043,'1796:0 1797:0 1799:0 1801:0 1803:0 '), +(1801,'DesolaceS6',1,405,606,28,40,0,-2098.84,2409.19,61.4027,3.19808,'1800:0 1802:0 1803:0 '), +(1802,'DesolaceExitS',1,405,405,28,40,0,-2402.61,2363.59,108.604,0.107536,'1801:0 1913:0 '), +(1803,'DesolaceS7',1,405,602,33,40,0,-2145.67,2180.6,67.6286,1.21495,'1800:0 1801:0 1804:0 1806:0 '), +(1804,'DesolaceS8',1,405,602,33,40,0,-2190.56,1992.71,64.0673,1.30332,'1803:0 1805:0 '), +(1805,'DesolaceS9',1,405,602,33,40,0,-2171.01,1903.5,66.9158,1.58018,'1804:0 1807:0 '), +(1806,'DesolaceS10',1,405,602,33,40,0,-1981.74,2045.99,59.3955,2.2831,'1803:0 1808:0 1809:0 '), +(1807,'DesolaceS11',1,405,602,33,40,0,-2096.65,1787.92,57.1531,1.95912,'1805:0 1808:0 1811:0 '), +(1808,'DesolaceS12',1,405,602,33,40,0,-1982.92,1838.14,63.409,3.36694,'1806:0 1807:0 1810:0 '), +(1809,'DesolaceS13',1,405,602,33,40,0,-1815.92,1988.94,59.0664,1.39951,'1777:0 1778:0 1799:0 1806:0 '), +(1810,'DesolaceS14',1,405,602,33,40,0,-1765.64,1678.32,60.7555,0.438962,'1777:0 1808:0 1811:0 '), +(1811,'DesolaceS15',1,405,405,31,40,0,-2062.67,1679.65,60.5781,5.56172,'1761:0 1807:0 1810:0 1812:0 '), +(1812,'DesolaceS16',1,405,2657,33,40,0,-2225.06,1577.55,60.1882,0.421288,'130:0 1811:0 '), +(1813,'DesolaceS17',1,405,2657,33,40,0,-2153.93,1293.66,63.9415,0.29169,'130:0 1761:0 '), +(1814,'MulgoreExitE',1,215,215,1,10,4,-2345.37,-1484.97,40.0713,4.66713,'174:0 1815:0 '), +(1815,'Mulgore1',1,215,215,1,10,4,-2440.53,-1200.03,-9.30534,1.55892,'1814:0 1816:0 1865:0 1869:0 '), +(1816,'Mulgore2',1,215,215,1,10,4,-2371.49,-904.809,-9.42438,4.38439,'1815:0 1817:0 1865:0 1866:0 1869:0 '), +(1817,'Mulgore3',1,215,215,1,10,4,-2311.97,-607.857,-9.42454,4.65338,'1816:0 26:0 1818:0 1819:0 1869:0 '), +(1818,'Mulgore4',1,215,223,1,10,4,-2105.91,-429.602,-6.83183,3.82086,'26:0 1817:0 1846:0 '), +(1819,'Mulgore5',1,215,215,1,10,4,-2475.36,-501.699,-9.42479,5.94732,'26:0 1817:0 1820:0 1869:0 '), +(1820,'Mulgore6',1,215,215,1,10,4,-2628.95,-190.942,-9.21765,5.24046,'1819:0 1821:0 1843:0 1844:0 1869:0 '), +(1821,'Mulgore7',1,215,215,1,10,4,-2844.69,184.459,62.1599,5.51927,'1820:0 1822:0 1843:0 1844:0 '), +(1822,'Mulgore8',1,215,215,1,10,4,-2993.74,194.655,71.8138,0.231569,'1821:0 1823:0 '), +(1823,'Mulgore9',1,215,220,1,10,4,-3072.38,82.3203,78.0188,1.03857,'1822:0 1824:0 '), +(1824,'Mulgore10',1,215,220,1,10,4,-3137.17,-93.9051,45.0213,1.34488,'1823:0 1825:0 1842:0 '), +(1825,'Mulgore11',1,215,220,1,10,4,-3063.95,-239.512,50.6424,1.85735,'1824:0 27:0 1842:0 '), +(1826,'Mulgore12',1,215,220,1,10,4,-2891.86,-697.729,45.2768,1.58442,'27:0 1827:0 1842:0 '), +(1827,'Mulgore13',1,215,220,1,10,4,-3261.33,-846.251,48.8421,0.235503,'1826:0 1828:0 1830:0 1841:0 1842:0 '), +(1828,'Mulgore14',1,215,220,1,10,4,-3366.64,-1016.68,109.367,6.12009,'1827:0 1829:0 '), +(1829,'Mulgore15',1,215,358,1,10,4,-3224,-1083.1,88.5366,2.85872,'1828:0 1830:0 '), +(1830,'Mulgore16',1,215,358,1,10,4,-3119.41,-1033.45,49.7491,3.46937,'1827:0 1829:0 1831:0 1832:0 '), +(1831,'Mulgore17',1,215,358,1,10,4,-3063.5,-1162.46,66.0398,0.149071,'1830:0 1832:0 1833:0 '), +(1832,'Mulgore18',1,215,358,1,10,4,-2991.05,-1001.3,57.8123,3.50664,'1830:0 1831:0 '), +(1833,'Mulgore19',1,215,358,1,10,4,-2989.95,-1155.46,58.1984,3.50469,'1831:0 1834:0 1837:0 '), +(1834,'Mulgore20',1,215,358,1,10,4,-2907.83,-1077.93,55.9333,3.94844,'1833:0 1835:0 1837:0 '), +(1835,'Mulgore21',1,215,358,1,10,4,-2844.44,-1016.64,56.9403,4.16051,'1834:0 1836:0 '), +(1836,'Mulgore22',1,215,358,1,10,4,-2854.01,-1100.12,90.8135,0.692983,'1835:0 '), +(1837,'Mulgore23',1,215,358,1,10,4,-2883.85,-1231.28,72.9943,1.82985,'1833:0 1834:0 1838:0 1840:0 '), +(1838,'Mulgore24',1,215,358,1,10,4,-3026.45,-1229.87,71.6597,6.01404,'1837:0 1839:0 '), +(1839,'Mulgore25',1,215,358,1,10,4,-2970.92,-1323.02,80.5074,1.92996,'1838:0 1840:0 '), +(1840,'Mulgore26',1,215,358,1,10,4,-2895.78,-1325.44,95.4793,2.24804,'1837:0 1839:0 '), +(1841,'Mulgore27',1,215,220,1,10,4,-3440.17,-866.836,69.6282,6.24768,'1827:0 1842:0 '), +(1842,'Mulgore28',1,215,220,1,10,4,-3367.33,-495.02,69.8435,4.71616,'1824:0 1825:0 1826:0 1827:0 1841:0 '), +(1843,'Mulgore29',1,215,818,1,10,4,-2404.86,217.609,48.1932,3.41043,'1820:0 1821:0 1844:0 1845:0 1847:0 1870:0 '), +(1844,'Mulgore30',1,215,215,1,10,4,-2261.05,-69.3042,-1.37537,2.79193,'1820:0 1821:0 1843:0 1845:0 1846:0 1847:0 '), +(1845,'Mulgore31',1,215,215,1,10,4,-1909.74,216.749,46.1874,3.10216,'1843:0 1844:0 1847:0 1848:0 1849:0 1871:0 '), +(1846,'Mulgore32',1,215,223,1,10,4,-2045.76,-343.178,-6.87874,4.09177,'1818:0 1844:0 1847:0 1862:0 1863:0 '), +(1847,'Mulgore33',1,215,215,1,10,4,-1920.69,-119.003,-11.7465,4.00341,'1843:0 1844:0 1845:0 1846:0 1848:0 1862:0 '), +(1848,'Mulgore34',1,215,215,1,10,4,-1668.96,-1.5923,-11.6961,3.52429,'1845:0 1847:0 1849:0 1850:0 1862:0 '), +(1849,'Mulgore35',1,215,215,1,10,5,-1427.28,82.3261,15.7467,3.85023,'1845:0 1848:0 1850:0 '), +(1850,'Mulgore36',1,215,215,1,10,4,-1347.57,320.413,8.78348,0.441607,'1848:0 1849:0 1851:0 1852:0 '), +(1851,'Mulgore37',1,1638,1638,1,10,4,-1148.06,245.946,40.6026,3.4811,'1850:0 1852:0 1853:0 1854:0 '), +(1852,'Mulgore38',1,215,215,1,10,4,-996.688,514.659,49.632,4.10942,'1850:0 1851:0 '), +(1853,'Mulgore39',1,215,215,1,10,4,-751.74,38.1612,-20.8616,2.04343,'1851:0 1854:0 1856:0 1855:0 '), +(1854,'Mulgore40',1,215,215,1,10,4,-927.854,10.2372,12.3669,2.39686,'1851:0 1853:0 1856:0 1855:0 '), +(1855,'Mulgore41',1,215,819,1,10,4,-561.426,-405.799,30.6454,1.96489,'1853:0 1854:0 1856:0 1857:0 '), +(1856,'Mulgore42',1,215,215,1,10,4,-901.7,-443.722,-39.0694,1.58907,'1853:0 1854:0 1855:0 1857:0 1861:0 '), +(1857,'Mulgore43',1,215,820,1,10,4,-924.517,-801.42,-5.66428,4.78761,'1855:0 1856:0 1858:0 1860:0 1861:0 '), +(1858,'Mulgore44',1,215,225,1,10,4,-956.811,-1082.75,42.1933,1.43395,'1857:0 1859:0 '), +(1859,'Mulgore45',1,215,225,1,10,4,-1099.12,-1152.59,50.0739,0.809557,'1858:0 1860:0 '), +(1860,'Mulgore46',1,215,820,1,10,4,-1365.16,-841.737,-8.11366,5.8636,'1857:0 1859:0 1861:0 1862:0 1863:0 '), +(1861,'Mulgore47',1,215,820,1,10,4,-1254.38,-429.801,-44.919,0.114484,'1856:0 1857:0 1860:0 1862:0 '), +(1862,'Mulgore48',1,215,820,1,10,4,-1600.99,-429.634,-38.9597,5.5357,'1846:0 1847:0 1848:0 1860:0 1861:0 1863:0 1864:0 '), +(1863,'Mulgore49',1,215,820,1,10,4,-1742.71,-749.243,-8.79196,6.02461,'1846:0 1860:0 1862:0 1864:0 1865:0 1866:0 '), +(1864,'Mulgore50',1,215,224,1,10,4,-1925.81,-713.913,3.65139,0.842938,'1862:0 1863:0 1865:0 1866:0 '), +(1865,'Mulgore51',1,215,215,1,10,4,-2111.14,-877.576,-5.68659,6.00889,'1815:0 1816:0 1863:0 1864:0 1866:0 '), +(1866,'Mulgore52',1,215,360,1,10,4,-1975.78,-1068.43,47.7644,0.687805,'1816:0 1863:0 1864:0 1865:0 1867:0 '), +(1867,'Mulgore53',1,215,360,1,10,4,-1884.66,-1112.51,92.8851,6.08152,'1866:0 1868:0 '), +(1868,'Mulgore54',1,215,360,1,10,4,-1584.13,-1092.53,103.917,3.87848,'1867:0 93:0 '), +(1869,'Mulgore55',1,215,215,1,10,4,-2751.82,-857.915,4.54324,5.9912,'1815:0 1816:0 1817:0 1819:0 1820:0 '), +(1870,'Mulgore56',1,215,818,1,10,4,-2369.44,444.618,66.6944,2.44118,'1843:0 '), +(1871,'Mulgore57',1,215,404,1,10,4,-1938.35,454.865,133.59,5.01926,'1845:0 '), +(1872,'DWMExitW',1,15,15,33,45,0,-3684.38,-2471.41,78.489,4.65716,'1596:0 99:0 '), +(1873,'DustwallowC1',1,15,15,33,45,0,-3476.61,-2710.46,33.3673,2.4724,'99:0 187:0 1903:0 1904:0 103:0 '), +(1874,'BlackhoofVillage',1,15,512,33,45,0,-2455.85,-3159.6,35.8624,3.43489,'102:0 1875:0 '), +(1875,'DustwallowN1',1,15,15,33,45,0,-2686.37,-3372.62,34.5997,0.845044,'101:0 102:0 1874:0 1876:0 1877:0 1902:0 '), +(1876,'DustwallowN2',1,15,15,33,45,0,-2562.07,-3486.87,34.2695,2.37303,'1875:0 1877:0 '), +(1877,'DustwallowN3',1,15,15,33,45,0,-2671.59,-3675.6,30.8794,2.00782,'101:0 110:0 1875:0 1876:0 1878:0 1879:0 1894:0 '), +(1878,'DustwallowN4',1,15,502,33,45,0,-2914.27,-3675.73,33.4338,1.38735,'101:0 110:0 1877:0 1879:0 1880:0 '), +(1879,'DustwallowN5',1,15,502,33,45,0,-2802.66,-3997.22,36.7838,2.19983,'110:0 1877:0 1878:0 1880:0 1892:0 '), +(1880,'DustwallowN6',1,15,502,33,45,0,-3155.95,-3974.68,29.251,1.17881,'110:0 1878:0 1879:0 1881:0 1887:0 '), +(1881,'DustwallowN7',1,15,15,33,45,0,-3305.97,-4095.97,23.4925,0.654559,'109:0 1880:0 '), +(1882,'DustwallowN8',1,15,15,33,45,0,-3491.34,-4245.46,7.816,1.17292,'109:0 54:0 1884:0 1912:0 '), +(1883,'TheramoreInn',1,15,513,33,45,3,-3627.66,-4472.85,17.4714,0.218669,'54:0 '), +(1884,'DustwallowCoastN1',1,15,518,33,45,0,-3458.13,-4377.69,0.755389,2.14564,'1882:0 1885:0 '), +(1885,'DustwallowCoastN2',1,15,518,33,45,0,-3329.33,-4289.33,1.48119,3.70268,'1884:0 1886:0 '), +(1886,'DustwallowCoastN3',1,15,518,33,45,0,-3142.27,-4215.25,0.891156,0.280307,'1885:0 1887:0 1897:0 '), +(1887,'DustwallowCoastN4',1,15,518,33,45,0,-2985.17,-4207.51,1.60007,3.33747,'1880:0 1886:0 1888:0 1897:0 '), +(1888,'DustwallowCoastN5',1,15,518,33,45,0,-2802.02,-4220.5,1.16157,3.12737,'1889:0 1887:0 1897:0 1898:0 '), +(1889,'DustwallowCoastN6',1,15,518,33,45,0,-2682.96,-4150.2,0.491195,3.24911,'1888:0 1890:0 1898:0 '), +(1890,'DustwallowCoastN7',1,15,518,33,45,0,-2644.79,-4076.96,0.24736,4.12678,'1889:0 1891:0 1892:0 1893:0 '), +(1891,'DustwallowCoastN8',1,15,518,33,45,0,-2634.51,-3970.05,1.40599,4.46647,'1890:0 1892:0 1893:0 1894:0 '), +(1892,'DustwallowCoastN9',1,15,518,33,45,0,-2678.39,-4014.18,4.97984,2.78377,'1879:0 1890:0 1891:0 '), +(1893,'DustwallowCoastN10',1,15,518,33,45,0,-2559.21,-4015.96,9.99785,3.70856,'1890:0 1891:0 1899:0 '), +(1894,'DustwallowCoastN11',1,15,518,33,45,0,-2607.14,-3826.64,6.27917,4.49985,'1877:0 1891:0 1895:0 '), +(1895,'DustwallowCoastN12',1,15,518,33,45,0,-2466.63,-3818.63,2.61691,3.22358,'1896:0 1894:0 1899:0 1900:0 '), +(1896,'DustwallowCoastN13',1,17,385,33,45,0,-2395.84,-3763.83,5.73158,5.36182,'1574:0 1895:0 '), +(1897,'DustwallowCoastN14',1,15,518,33,45,0,-3001.64,-4378.46,9.05538,1.1933,'1886:0 1887:0 1888:0 '), +(1898,'DustwallowCoastN15',1,15,518,33,45,0,-2662.99,-4255.03,3.90542,2.70126,'1888:0 1889:0 1901:0 '), +(1899,'DustwallowCoastN16',1,15,518,33,45,0,-2408.96,-4080.5,3.33986,2.28499,'1893:0 1895:0 1900:0 1901:0 '), +(1900,'DustwallowCoastN17',1,15,518,33,45,0,-2302.1,-4086.25,16.0641,1.37353,'1573:0 1574:0 1895:0 1899:0 1901:0 '), +(1901,'DustwallowCoastN18',1,15,518,33,45,0,-2291.73,-4303.19,4.72444,2.85793,'1898:0 1899:0 1900:0 '), +(1902,'DustwallowN9',1,15,15,33,45,0,-2942.4,-3252.27,31.3912,5.0692,'100:0 101:0 102:0 1875:0 1903:0 '), +(1903,'DustwallowC2',1,15,15,33,45,0,-3285.02,-3185.79,32.3767,5.19094,'1873:0 1902:0 1904:0 1905:0 '), +(1904,'DustwallowC3',1,15,2302,33,45,0,-3645.37,-3105.83,35.4956,0.749902,'99:0 1873:0 1903:0 1905:0 1907:0 103:0 '), +(1905,'DustwallowC4',1,15,4046,33,45,0,-3692.77,-3428.45,36.5831,1.67942,'1903:0 1904:0 1906:0 1907:0 '), +(1906,'DustwallowC5',1,15,501,33,45,0,-4023.01,-3761.02,42.2472,0.725162,'1905:0 1907:0 105:0 '), +(1907,'DustwallowC6',1,15,4049,33,45,0,-4017.64,-3385.66,38.3117,5.35312,'1904:0 1905:0 1906:0 103:0 105:0 '), +(1908,'DustwallowC7',1,15,510,33,45,0,-4230.28,-2794.11,27.7681,6.16323,'103:0 108:0 1909:0 '), +(1909,'DustwallowS1',1,15,509,33,45,0,-4392.92,-2968.72,72.0173,1.4685,'103:0 108:0 1908:0 233:0 105:0 '), +(1910,'DustwallowS2',1,15,511,33,45,0,-4652.75,-3983.64,63.5063,1.62756,'106:0 107:0 '), +(1911,'DustwallowS3',1,15,511,33,45,0,-4869.31,-3443.39,39.4441,0.620287,'106:0 107:0 233:0 '), +(1912,'DustwallowCoastS1',1,15,516,33,45,0,-3990.53,-4144.75,15.8765,3.0059,'104:0 1882:0 '), +(1913,'FeralasN1',1,357,1114,38,50,0,-2571.82,2244.63,96.3995,0.543668,'1802:0 1914:0 '), +(1914,'FeralasN2',1,357,1114,38,50,0,-2742,2335.99,62.5691,5.81565,'1913:0 1915:0 1920:0 1921:0 '), +(1915,'FeralasN3',1,357,1114,38,50,0,-2829.93,2719.49,74.7843,5.10291,'1914:0 1916:0 1920:0 '), +(1916,'FeralasN4',1,357,1114,38,50,0,-3032.68,2638.14,54.8507,0.249148,'1915:0 1917:0 1920:0 '), +(1917,'FeralasN5',1,357,1119,38,50,0,-3246.59,2815.94,85.1355,5.88045,'1916:0 1918:0 '), +(1918,'FeralasN6',1,357,1119,38,50,0,-3570.32,2569.75,80.1298,0.734128,'1917:0 1919:0 1923:0 '), +(1919,'FeralasN7',1,357,1119,38,50,0,-3349.08,2217.29,32.4203,2.18908,'1918:0 1920:0 1922:0 '), +(1920,'FeralasN8',1,357,1119,38,50,0,-2939.01,2249.46,45.7452,1.87688,'1914:0 1915:0 1916:0 1919:0 1921:0 '), +(1921,'FeralasN9',1,357,1111,50,60,0,-2867.51,1894.77,52.6495,3.09033,'1914:0 1920:0 '), +(1922,'FeralasN10',1,357,1119,38,50,0,-3464.98,2077.93,40.3195,0.882963,'1919:0 1923:0 1925:0 1926:0 '), +(1923,'FeralasN11',1,357,1119,38,50,0,-3508.95,2343.43,61.5171,1.82151,'1918:0 1922:0 1924:0 '), +(1924,'FeralasN12',1,357,1119,38,50,0,-3582.9,2225.74,39.1949,3.55134,'1923:0 1925:0 '), +(1925,'FeralasN13',1,357,1119,38,50,0,-3677.85,2192.02,79.5813,5.92325,'1922:0 1924:0 1929:0 '), +(1926,'FeralasN14',1,357,1119,38,50,0,-3902.37,1958.83,79.7325,5.2537,'1922:0 1927:0 1931:0 '), +(1927,'FeralasN15',1,357,1115,38,50,0,-3845.14,1829.16,124.075,1.98644,'1926:0 131:0 '), +(1928,'FeralasN16',1,357,1115,38,50,0,-3896.35,1647.85,117.592,4.8178,'131:0 '), +(1929,'FeralasN17',1,357,1119,38,50,0,-3899.54,2080.79,120.315,0.496151,'1925:0 1930:0 '), +(1930,'FeralasN18',1,357,1119,38,50,0,-4106.11,2126.23,91.7704,5.83686,'1929:0 1931:0 1932:0 '), +(1931,'FeralasN19',1,357,1119,38,50,0,-4171.24,2101.97,88.0788,0.490217,'1926:0 1930:0 1943:0 '), +(1932,'FeralasCoast1',1,357,1108,38,50,0,-4118.28,2330.97,2.19033,5.05932,'1930:0 1933:0 1935:0 '), +(1933,'FeralasCoast2',1,357,1108,38,50,0,-4052.79,2718.62,0.6784,4.75105,'1932:0 1934:0 1939:0 '), +(1934,'FeralasCoast3',1,357,1108,38,50,0,-3843.41,3064.54,7.61114,4.17574,'1933:0 '), +(1935,'FeralasCoast4',1,357,1108,38,50,0,-4379.47,2356.68,-0.331094,5.57371,'1932:0 1936:0 1937:0 1939:0 1944:0 '), +(1936,'FeralasCoast5',1,357,1108,38,50,0,-4743.36,2004.49,5.62754,0.704225,'1935:0 1937:0 1950:0 '), +(1937,'FeralasCoast6',1,357,1108,38,50,0,-4901.88,2189.77,1.21295,5.78972,'1935:0 1936:0 1938:0 '), +(1938,'FeralasCoast7',1,357,1108,38,50,0,-5436.32,2245.93,4.15669,6.02534,'1937:0 '), +(1939,'FeathermoonCoast',1,357,1116,38,50,2,-4396.44,3096.49,-0.543116,1.686,'58:0 1933:0 1935:0 '), +(1940,'SardorIsle1',1,357,1117,38,50,2,-4874.91,3238.84,9.84719,0.996823,'58:0 132:0 1941:0 '), +(1941,'SardorIsle2',1,357,1120,38,50,2,-4509.12,3541.55,19.5222,3.63969,'58:0 132:0 1940:0 '), +(1942,'ShalzarusLairInside',1,357,3117,38,50,0,-5628.12,3481.23,0.291859,1.03412,'140:0 '), +(1943,'FeralasC1',1,357,1108,38,50,0,-4354.27,2114.61,65.9115,3.94793,'1931:0 1944:0 '), +(1944,'FeralasC2',1,357,1108,38,50,0,-4459.61,2051.49,45.6382,0.749398,'1935:0 1943:0 1945:0 '), +(1945,'FeralasC3',1,357,1108,38,50,0,-4587.74,2020.12,49.7424,0.107333,'1944:0 1946:0 '), +(1946,'FeralasC4',1,357,1108,38,50,0,-4669.12,1941.38,71.209,4.65283,'1945:0 1947:0 '), +(1947,'FeralasC5',1,357,1136,38,50,0,-4689.38,1793.15,92.5165,3.59252,'1946:0 1948:0 1951:0 '), +(1948,'FeralasC6',1,357,1136,38,50,0,-4548.77,1825.47,91.6009,4.86489,'1947:0 1949:0 '), +(1949,'FeralasC7',1,357,2577,38,50,0,-4570.42,1333.99,110.276,1.57406,'1948:0 1952:0 1966:0 '), +(1950,'FeralasC8',1,357,1136,38,50,0,-4774.62,1706.39,79.0617,1.25399,'1936:0 1951:0 '), +(1951,'FeralasC9',1,357,1136,38,50,0,-4747.41,1601.96,83.6922,1.48372,'1947:0 1950:0 1952:0 1953:0 '), +(1952,'FeralasC10',1,357,1136,38,50,0,-4850.91,1315.71,81.0286,0.264391,'1949:0 1951:0 1966:0 '), +(1953,'FeralasC11',1,357,1105,38,50,0,-4984.77,1575.59,59.7078,0.164254,'1951:0 1954:0 1956:0 1964:0 '), +(1954,'FeralasC12',1,357,1105,38,50,0,-4932.88,1723.53,64.1127,4.66263,'1953:0 1955:0 '), +(1955,'FeralasC13',1,357,1105,38,50,0,-5149.79,1759.34,78.4796,0.706951,'1954:0 '), +(1956,'FeralasC14',1,357,1136,38,50,0,-5121.12,1597.3,62.6844,6.11245,'1953:0 1957:0 1964:0 '), +(1957,'FeralasC15',1,357,1136,38,50,0,-5312.41,1586.43,50.2301,2.5919,'1956:0 1958:0 1961:0 1964:0 '), +(1958,'FeralasC16',1,357,1106,38,50,0,-5747.35,1698.84,93.4873,6.16546,'1957:0 1959:0 '), +(1959,'FeralasC17',1,357,2522,38,50,0,-5765.25,1224.11,66.4458,1.50412,'1958:0 1960:0 '), +(1960,'FeralasC18',1,357,2522,38,50,0,-5550.63,1116.69,56.7549,0.496841,'1959:0 1961:0 '), +(1961,'FeralasC19',1,357,2522,38,50,0,-5461.46,1368.9,22.0332,4.46507,'1957:0 1960:0 1962:0 1964:0 '), +(1962,'FeralasC20',1,357,2522,38,50,0,-5595.89,1373.16,54.1263,3.31838,'1961:0 1963:0 '), +(1963,'FeralasC21',1,357,2522,38,50,0,-5687.79,1413.51,73.2559,3.3773,'1962:0 '), +(1964,'FeralasC22',1,357,2522,38,50,0,-5109.82,1250.83,51.7218,2.34252,'1953:0 1956:0 1957:0 1961:0 1965:0 '), +(1965,'FeralasC23',1,357,2522,38,50,0,-4947.18,1144.74,76.4718,2.43362,'1964:0 1966:0 '), +(1966,'FeralasC24',1,357,1136,38,50,0,-4819.41,1182.67,88.2722,1.88973,'1949:0 1952:0 1965:0 1967:0 '), +(1967,'FeralasC25',1,357,2521,38,50,0,-4850.75,1054.39,94.6178,1.13575,'1966:0 1968:0 '), +(1968,'FeralasC26',1,357,357,38,50,0,-4701.43,1002.11,106.384,2.81258,'1967:0 1969:0 1972:0 '), +(1969,'FeralasC27',1,357,357,38,50,0,-4686.71,913.041,90.3337,1.80138,'1968:0 1970:0 '), +(1970,'FeralasC28',1,357,357,38,50,0,-4641.48,856.356,83.6012,2.41398,'1969:0 1971:0 '), +(1971,'FeralasC29',1,357,357,38,50,0,-4679.42,716.437,75.2356,1.09647,'1970:0 1974:0 1981:0 '), +(1972,'FeralasC30',1,357,357,38,50,0,-4555.72,884.281,58.0988,3.21115,'1968:0 1973:0 '), +(1973,'FeralasC31',1,357,357,38,50,0,-4505.93,684.371,66.7637,1.78171,'1972:0 1974:0 1975:0 1976:0 '), +(1974,'FeralasE1',1,357,357,38,50,0,-4659.39,623.64,52.4147,4.96061,'1971:0 1973:0 1975:0 1978:0 2006:0 '), +(1975,'FeralasE2',1,357,357,38,50,0,-4635.27,542.675,37.1032,1.8524,'1973:0 1974:0 1976:0 1981:0 1993:0 1994:0 1995:0 '), +(1976,'FeralasE3',1,357,357,38,50,0,-4390.02,604.52,62.0106,2.9598,'1973:0 1975:0 1977:0 1995:0 2006:0 '), +(1977,'FeralasE4',1,357,1100,38,50,0,-4212.24,655.545,69.8097,3.6706,'1976:0 2005:0 2006:0 '), +(1978,'FeralasE5',1,357,2519,38,50,0,-4898.74,674.126,43.55,1.02185,'1974:0 1979:0 1981:0 1982:0 '), +(1979,'FeralasE6',1,357,2519,38,50,0,-4953.5,761.759,82.878,5.39652,'1978:0 1980:0 '), +(1980,'FeralasE7',1,357,2520,38,50,0,-4845.02,785.634,113.891,1.35172,'1979:0 178:0 '), +(1981,'FeralasE8',1,357,2519,38,50,0,-4880.52,558.22,12.8999,1.88775,'1971:0 1975:0 1978:0 1993:0 1994:0 '), +(1982,'FeralasE9',1,357,2519,38,50,0,-5149.35,692.713,54.9213,6.26831,'1978:0 1983:0 1994:0 '), +(1983,'FeralasE10',1,357,1101,38,50,0,-5319.6,478.232,53.9695,0.898155,'1982:0 1984:0 1994:0 '), +(1984,'FeralasE11',1,357,1101,38,50,0,-5163.07,220.677,54.4074,1.30064,'1983:0 1985:0 1986:0 1987:0 1988:0 1991:0 '), +(1985,'FeralasE12',1,357,1101,38,50,0,-5302,428.292,8.41442,3.73933,'1984:0 '), +(1986,'FeralasE13',1,357,1101,38,50,0,-5384.3,261.887,20.4496,0.905996,'1984:0 '), +(1987,'FeralasE14',1,357,1101,38,50,0,-5265.18,-4.42852,15.1479,1.56377,'1984:0 '), +(1988,'FeralasE15',1,357,1101,38,50,0,-5343.63,67.4607,28.7204,3.66077,'1984:0 1989:0 1990:0 '), +(1989,'FeralasE16',1,357,1101,38,50,0,-5450.99,119.201,27.5633,6.28204,'1988:0 '), +(1990,'FeralasE17',1,357,1101,38,50,0,-5340.47,-23.1596,18.5077,1.41062,'1988:0 '), +(1991,'FeralasE18',1,357,2519,38,50,0,-4991.17,205.185,56.0523,2.62992,'1984:0 136:0 1992:0 1994:0 '), +(1992,'FeralasE19',1,357,1137,38,50,0,-4866.29,339.413,26.1994,4.53453,'136:0 1991:0 1993:0 1994:0 1999:0 '), +(1993,'FeralasE20',1,357,357,38,50,0,-4722.79,464.496,31.986,3.94744,'1975:0 1981:0 1992:0 '), +(1994,'FeralasE21',1,357,2519,38,50,0,-5009.99,424.997,15.1355,0.648747,'1975:0 1981:0 1982:0 1983:0 1991:0 1992:0 '), +(1995,'FeralasE22',1,357,357,38,50,4,-4578.16,370.868,33.8808,1.77815,'1975:0 1976:0 53:0 2006:0 '), +(1996,'FeralasE23',1,357,1099,38,50,5,-4481.74,227.782,48.3927,0.264285,'53:0 '), +(1997,'FeralasE24',1,357,1099,38,50,4,-4376.3,121.922,32.6793,1.76165,'53:0 1998:0 '), +(1998,'FeralasE25',1,357,1137,38,50,4,-4328.21,-19.6833,60.5124,1.72631,'1997:0 2003:0 '), +(1999,'FeralasE26',1,357,357,38,50,0,-4659.2,184.738,43.8095,4.93936,'1992:0 2000:0 '), +(2000,'FeralasE27',1,357,357,38,50,0,-4603.74,66.3705,89.2353,5.28101,'1999:0 2001:0 '), +(2001,'FeralasE28',1,357,357,38,50,0,-4564.62,-31.7688,90.9571,1.80758,'2000:0 2002:0 '), +(2002,'FeralasE29',1,357,357,38,50,0,-4446.96,-44.7766,57.3336,5.9427,'2001:0 2003:0 2009:0 '), +(2003,'FeralasE30',1,357,1137,38,50,0,-4300.16,-85.5649,62.2238,2.29256,'1998:0 2002:0 2004:0 2007:0 2008:0 '), +(2004,'FeralasE31',1,357,2518,38,50,0,-4205.65,113.809,55.5058,4.08129,'2003:0 2006:0 137:0 '), +(2005,'FeralasE32',1,357,1100,38,50,0,-4133.72,537.891,69.2153,3.29981,'1977:0 2006:0 '), +(2006,'FeralasE33',1,357,357,38,50,0,-4279.19,459.305,53.0559,3.87904,'1974:0 1976:0 1977:0 1995:0 2004:0 2005:0 137:0 '), +(2007,'FeralasE34',1,357,1137,38,50,0,-4106.63,-81.3584,57.9545,3.10739,'2003:0 137:0 2008:0 '), +(2008,'FeralasE35',1,357,1137,38,50,0,-4234.2,-193.867,59.9266,1.9823,'2003:0 2007:0 2010:0 2017:0 '), +(2009,'FeralasE36',1,357,1137,38,50,0,-4457.62,-347.118,42.7385,1.56603,'2002:0 2010:0 2013:0 '), +(2010,'FeralasE37',1,357,1137,38,50,0,-4279.38,-302.439,53.8359,3.38225,'2008:0 2009:0 2011:0 '), +(2011,'FeralasE38',1,357,1137,38,50,0,-4304.01,-380.254,42.9873,1.37949,'2010:0 2012:0 '), +(2012,'FeralasE39',1,357,1137,38,50,0,-4323.94,-518.378,18.8016,1.66222,'2011:0 2013:0 2015:0 '), +(2013,'FeralasE40',1,357,1137,38,50,0,-4484.94,-528.383,12.6689,1.34413,'2009:0 2012:0 2014:0 '), +(2014,'FeralasE41',1,357,1137,38,50,0,-4483.85,-680.591,-13.4172,2.0451,'2013:0 40:0 2019:0 '), +(2015,'FeralasE42',1,357,1137,38,50,0,-4357.69,-582.597,3.62467,1.18115,'2012:0 2016:0 '), +(2016,'FeralasE43',1,357,1137,38,50,0,-4283.62,-657.145,-16.5908,4.73115,'2015:0 2019:0 '), +(2017,'FeralasE44',1,357,1137,38,50,0,-4097.49,-507.444,10.4313,4.54463,'2008:0 2018:0 '), +(2018,'FeralasE45',1,357,1137,38,50,0,-4189.57,-767.003,-39.6189,2.74803,'2017:0 2019:0 2023:0 '), +(2019,'FeralasE46',1,357,1137,38,50,0,-4273.71,-763.178,-44.585,1.5287,'40:0 2014:0 2016:0 2018:0 2020:0 '), +(2020,'ThousandNeedlesW1',1,400,400,23,35,0,-4400.82,-883.395,-57.6691,2.28464,'40:0 2019:0 2022:0 2024:0 '), +(2021,'ThousandNeedlesW2',1,400,400,23,35,0,-4635.23,-913.058,-56.816,0.876811,'40:0 2022:0 2030:0 '), +(2022,'ThousandNeedlesW3',1,400,400,23,35,0,-4526.31,-1003.24,-57.3019,0.841468,'2020:0 2021:0 2024:0 2025:0 '), +(2023,'ThousandNeedlesW4',1,400,400,23,35,0,-4265.7,-975.548,-52.6163,1.36572,'2018:0 2024:0 '), +(2024,'ThousandNeedlesW5',1,400,400,23,35,0,-4345.17,-1001.19,-55.4334,0.859138,'2020:0 2022:0 2023:0 2026:0 '), +(2025,'ThousandNeedlesW6',1,400,400,23,35,0,-4577.79,-1063.18,-51.9676,0.682426,'2022:0 2026:0 2027:0 '), +(2026,'ThousandNeedlesW7',1,400,480,23,35,0,-4478.86,-1145.77,-53.883,1.10654,'2024:0 2025:0 2028:0 '), +(2027,'ThousandNeedlesW8',1,400,400,23,35,0,-4653.89,-1114.06,-54.8856,5.97601,'2025:0 2028:0 2029:0 '), +(2028,'ThousandNeedlesW9',1,400,480,23,35,0,-4608.02,-1288.85,-51.0977,0.870926,'2026:0 2027:0 2033:0 2037:0 '), +(2029,'ThousandNeedlesW10',1,400,400,23,35,0,-4726.44,-1099.89,-54.2582,6.07616,'2027:0 2030:0 2033:0 '), +(2030,'ThousandNeedlesW11',1,400,400,23,35,0,-4790.74,-1025.56,-58.7304,0.476274,'2021:0 2029:0 2031:0 2058:0 '), +(2031,'ThousandNeedlesW12',1,400,400,23,35,0,-4923.35,-1082.53,-50.209,0.562663,'2030:0 2032:0 2035:0 '), +(2032,'ThousandNeedlesW13',1,400,400,23,35,0,-4838.39,-1191.4,-49.6151,2.08829,'2031:0 2033:0 2034:0 '), +(2033,'ThousandNeedlesW14',1,400,400,23,35,0,-4768.1,-1216.56,-52.8083,3.08574,'2028:0 2029:0 2032:0 2036:0 '), +(2034,'ThousandNeedlesW15',1,400,400,23,35,0,-4942.46,-1263.31,-48.5414,1.36768,'2032:0 2035:0 50:0 '), +(2035,'ThousandNeedlesW16',1,400,400,23,35,0,-5066.47,-1194.08,-55.537,0.71973,'2031:0 2034:0 2039:0 '), +(2036,'ThousandNeedlesW17',1,400,400,23,35,0,-4769.77,-1352.68,-49.836,1.52869,'50:0 2033:0 2037:0 2040:0 '), +(2037,'ThousandNeedlesW18',1,400,400,23,35,0,-4652.8,-1464.98,-50.872,2.47901,'2028:0 2036:0 2041:0 2043:0 '), +(2038,'ThousandNeedlesW19',1,400,400,23,35,0,-5074.73,-1410.96,-52.2312,0.222955,'50:0 2039:0 2048:0 '), +(2039,'ThousandNeedlesW20',1,400,400,23,35,0,-5194.18,-1406.79,-50.3763,0.764877,'2035:0 2038:0 2050:0 '), +(2040,'ThousandNeedlesW21',1,400,400,23,35,0,-4891.87,-1456.05,-51.236,1.07118,'50:0 2036:0 2042:0 2048:0 '), +(2041,'ThousandNeedlesW22',1,400,400,23,35,0,-4757,-1501.21,-28.7948,0.370198,'2037:0 2042:0 '), +(2042,'ThousandNeedlesW23',1,400,400,23,35,0,-4811.64,-1556.31,-50.0359,0.963171,'2040:0 2041:0 2043:0 2046:0 '), +(2043,'ThousandNeedlesW24',1,400,400,23,35,0,-4661.22,-1624.56,-25.4573,1.73482,'2037:0 2042:0 2044:0 '), +(2044,'ThousandNeedlesW25',1,400,400,23,35,0,-4624.13,-1730.14,-31.5621,1.85852,'2043:0 2045:0 '), +(2045,'ThousandNeedlesW26',1,400,485,23,35,0,-4691.02,-1824.75,-56.5207,1.01619,'2044:0 2047:0 2081:0 '), +(2046,'ThousandNeedlesW27',1,400,400,23,35,0,-4811.86,-1670.4,-51.0873,1.72892,'2042:0 2047:0 2052:0 '), +(2047,'ThousandNeedlesW28',1,400,2097,23,35,0,-4783.23,-1788.72,-49.3172,1.42261,'2045:0 2046:0 2051:0 '), +(2048,'ThousandNeedlesW29',1,400,400,23,35,0,-4982.42,-1560.83,-47.125,1.17787,'2038:0 2040:0 2049:0 2052:0 '), +(2049,'ThousandNeedlesW30',1,400,400,23,35,0,-5165.39,-1577.56,-54.4116,0.343386,'2048:0 2050:0 2054:0 '), +(2050,'ThousandNeedlesW31',1,400,483,23,35,0,-5295.65,-1509.76,-56.4121,0.573119,'2039:0 2049:0 2055:0 '), +(2051,'ThousandNeedlesW32',1,400,2097,23,35,0,-4855.93,-1831.87,-51.9183,1.28194,'2047:0 2052:0 2079:0 2080:0 '), +(2052,'ThousandNeedlesW33',1,400,400,23,35,0,-4969.52,-1717.69,-61.5435,1.6491,'2046:0 2048:0 2051:0 2053:0 '), +(2053,'ThousandNeedlesW34',1,400,400,23,35,0,-5061.47,-1763.65,-66.1325,0.36653,'2052:0 2054:0 2068:0 '), +(2054,'ThousandNeedlesW35',1,400,400,23,35,0,-5173.45,-1717.17,-61.8311,6.25112,'2049:0 2053:0 2055:0 2086:0 2087:0 '), +(2055,'ThousandNeedlesW36',1,400,483,23,35,0,-5372.83,-1603.26,-55.6282,0.975215,'2050:0 2054:0 2056:0 2087:0 2088:0 '), +(2056,'ThousandNeedlesW37',1,400,483,23,35,0,-5472.25,-1702.35,2.75952,1.04984,'2055:0 116:0 '), +(2057,'ThousandNeedlesW38',1,400,487,23,35,0,-5593.3,-1588.57,6.36415,0.366532,'116:0 '), +(2058,'ThousandNeedlesW39',1,400,482,23,35,0,-4950.58,-1058.58,-14.0026,1.23243,'2030:0 2059:0 '), +(2059,'ThousandNeedlesW40',1,400,482,23,35,0,-4980.25,-980.015,-4.94507,5.09659,'2058:0 2060:0 2062:0 2061:0 '), +(2060,'ThousandNeedlesW41',1,400,482,23,35,0,-4876.43,-922.254,-5.58887,3.87725,'2059:0 2062:0 2061:0 '), +(2061,'ThousandNeedlesW42',1,400,482,23,35,0,-5005.39,-842.193,-5.46968,5.01411,'2059:0 2060:0 2062:0 '), +(2062,'ThousandNeedlesW43',1,400,482,23,35,0,-5051.17,-927.317,-5.60775,4.14232,'2059:0 2060:0 2061:0 2063:0 2064:0 '), +(2063,'ThousandNeedlesW44',1,400,482,23,35,0,-5166.88,-896.379,-5.07889,5.92361,'2062:0 '), +(2064,'ThousandNeedlesW45',1,400,482,23,35,0,-5091.13,-1008.99,-5.23615,3.78733,'2062:0 2065:0 '), +(2065,'ThousandNeedlesW46',1,400,482,23,35,0,-5126.17,-1095.6,50.5447,1.11501,'2064:0 2066:0 '), +(2066,'ThousandNeedlesW47',1,400,482,23,35,0,-5191.66,-1142.08,49.8328,4.3371,'2065:0 2067:0 '), +(2067,'ThousandNeedlesW48',1,400,482,23,35,0,-5186.85,-1242.42,53.7882,2.36968,'2066:0 '), +(2068,'DarkcloudPinnacle1',1,400,2097,23,35,0,-5005.12,-1890.45,3.66479,1.83561,'2053:0 2069:0 '), +(2069,'DarkcloudPinnacle2',1,400,2097,23,35,0,-4901.08,-1867.77,34.9781,3.58507,'2068:0 2070:0 '), +(2070,'DarkcloudPinnacle3',1,400,2097,23,35,0,-4776.06,-1872.74,90.3879,3.04316,'2069:0 2071:0 '), +(2071,'DarkcloudPinnacle4',1,400,2097,23,35,0,-4876.74,-1978.26,91.8746,0.720343,'2070:0 2072:0 115:0 '), +(2072,'DarkcloudPinnacle5',1,400,2097,23,35,0,-4915.54,-2071.05,84.7897,4.93989,'2071:0 2073:0 2076:0 '), +(2073,'DarkcloudPinnacle6',1,400,2097,23,35,0,-4831.42,-2151.41,81.6488,1.60588,'2072:0 2074:0 2075:0 '), +(2074,'DarkcloudPinnacle7',1,400,2097,23,35,0,-4748.26,-2111.8,83.523,4.04415,'2073:0 '), +(2075,'DarkcloudPinnacle8',1,400,400,23,35,0,-4842.57,-2213.19,85.1857,5.94087,'2073:0 '), +(2076,'DarkcloudPinnacle9',1,400,2097,23,35,0,-5016.1,-2107.74,83.8605,6.28252,'2072:0 '), +(2077,'DarkcloudPinnacle10',1,400,2097,23,35,0,-5170.08,-2145.09,93.7072,3.77318,'115:0 '), +(2078,'DarkcloudPinnacle11',1,400,2097,23,35,0,-4921.47,-1843.98,80.8273,3.6318,'115:0 '), +(2079,'ThousandNeedlesC1',1,400,2097,23,35,0,-4970.93,-1895.26,-42.399,2.08851,'2051:0 2080:0 '), +(2080,'ThousandNeedlesC2',1,400,2097,23,35,0,-4870.6,-1904.88,-51.5857,2.74235,'2051:0 2079:0 2082:0 '), +(2081,'ThousandNeedlesC3',1,400,2097,23,35,0,-4709.18,-1916.95,-45.1441,1.27365,'2045:0 2082:0 '), +(2082,'ThousandNeedlesC4',1,400,2097,23,35,0,-4776.72,-1989.22,-60.8744,1.12639,'2080:0 2081:0 2083:0 '), +(2083,'ThousandNeedlesC5',1,400,2097,23,35,0,-4881.01,-2128.55,-44.1711,3.78849,'2082:0 2084:0 '), +(2084,'ThousandNeedlesC6',1,400,481,23,35,0,-5057.5,-2264.52,-53.5104,0.73918,'2083:0 2085:0 2094:0 119:0 '), +(2085,'ThousandNeedlesC7',1,400,2097,23,35,0,-5086.72,-2103.07,-39.7408,4.73175,'2084:0 2086:0 '), +(2086,'ThousandNeedlesC8',1,400,400,23,35,0,-5241.22,-1991.69,-60.4397,4.88884,'2054:0 2085:0 2090:0 2094:0 '), +(2087,'ThousandNeedlesC9',1,400,483,23,35,0,-5343.03,-1806.64,-52.2102,0.525945,'2054:0 2055:0 2089:0 '), +(2088,'ThousandNeedlesC10',1,400,483,23,35,0,-5473.39,-1743.1,-18.0835,0.979516,'2055:0 2089:0 '), +(2089,'ThousandNeedlesC11',1,400,483,23,35,0,-5533.01,-1878.93,-58.2063,1.19746,'2087:0 2088:0 2090:0 2092:0 '), +(2090,'ThousandNeedlesC12',1,400,400,23,35,0,-5432.97,-2043.66,-64.7738,6.26721,'2086:0 2089:0 2091:0 '), +(2091,'ThousandNeedlesC13',1,400,400,23,35,0,-5465.16,-2122.43,-60.3274,1.03645,'2090:0 2092:0 2095:0 '), +(2092,'ThousandNeedlesC14',1,400,400,23,35,0,-5599.58,-2073.33,-64.8276,0.983438,'2089:0 2091:0 2093:0 '), +(2093,'ThousandNeedlesC15',1,400,400,23,35,0,-5667.81,-2212.47,-58.1306,1.11303,'2092:0 2096:0 2106:0 '), +(2094,'ThousandNeedlesC16',1,400,400,23,35,0,-5287.5,-2195.62,-54.0753,0.741926,'2084:0 2086:0 2095:0 2097:0 119:0 '), +(2095,'ThousandNeedlesC17',1,400,400,23,35,0,-5426.32,-2213.81,-58.3051,6.2613,'2091:0 2094:0 2096:0 2097:0 '), +(2096,'ThousandNeedlesC18',1,400,400,23,35,0,-5522.8,-2260.72,-59.6581,0.457222,'2093:0 2095:0 2098:0 '), +(2097,'ThousandNeedlesC19',1,400,400,23,35,0,-5326.39,-2315.08,-42.353,1.99266,'2094:0 2095:0 2098:0 2110:0 '), +(2098,'ThousandNeedlesC20',1,400,484,23,35,4,-5535.61,-2362.59,-50.5979,1.14639,'2096:0 2097:0 2099:0 2107:0 '), +(2099,'ThousandNeedlesC21',1,400,484,23,35,4,-5649.75,-2375.72,2.56609,6.22399,'2098:0 2100:0 '), +(2100,'ThousandNeedlesC22',1,400,484,23,35,4,-5656.57,-2454.18,0.642997,4.88096,'2099:0 2101:0 '), +(2101,'ThousandNeedlesC23',1,400,484,23,35,4,-5604.75,-2490.4,30.4679,0.258887,'2100:0 2102:0 '), +(2102,'ThousandNeedlesC24',1,400,484,23,35,4,-5527.14,-2463.43,30.6774,2.88172,'2101:0 2103:0 '), +(2103,'ThousandNeedlesC25',1,400,484,23,35,4,-5474.04,-2382.59,57.2742,4.1305,'2102:0 2104:0 '), +(2104,'ThousandNeedlesC26',1,400,484,23,35,4,-5424.92,-2272.98,82.5255,4.55855,'2103:0 41:0 '), +(2105,'ThousandNeedlesC27',1,400,484,23,35,5,-5477.21,-2454.4,89.2838,1.45228,'41:0 '), +(2106,'ThousandNeedlesC28',1,400,400,23,35,0,-5735.44,-2408.6,-53.3532,0.767009,'2093:0 2107:0 2108:0 '), +(2107,'ThousandNeedlesC29',1,400,484,23,35,0,-5587.73,-2442.35,-50.4618,1.40318,'2098:0 2106:0 2109:0 '), +(2108,'ThousandNeedlesC30',1,400,400,23,35,0,-5685.83,-2597.38,-56.0596,2.12378,'2106:0 2109:0 2119:0 '), +(2109,'ThousandNeedlesC31',1,400,484,23,35,0,-5520.25,-2555.03,-56.5953,1.46797,'2107:0 2108:0 2111:0 2118:0 '), +(2110,'ThousandNeedlesC32',1,400,484,23,35,0,-5309.22,-2432.88,-43.4182,2.04524,'2097:0 2111:0 2112:0 '), +(2111,'ThousandNeedlesC33',1,400,400,23,35,4,-5357.57,-2551.14,-55.4342,1.46403,'2109:0 2110:0 2113:0 2115:0 '), +(2112,'ThousandNeedlesC34',1,400,400,23,35,0,-5163.1,-2464.2,-52.5964,1.02224,'119:0 2110:0 2113:0 '), +(2113,'ThousandNeedlesC35',1,400,400,23,35,0,-5168.45,-2584.79,-50.393,1.73498,'2111:0 2112:0 2114:0 '), +(2114,'ThousandNeedlesC36',1,400,400,23,35,0,-5206.18,-2701.85,-49.9805,1.80567,'2113:0 2115:0 2116:0 '), +(2115,'ThousandNeedlesC37',1,400,400,23,35,0,-5338.74,-2645.97,-45.5587,1.67019,'2111:0 2114:0 2117:0 '), +(2116,'ThousandNeedlesC38',1,400,2303,23,35,0,-5338.36,-2841.96,-55.7141,1.17145,'2114:0 2117:0 2120:0 2123:0 '), +(2117,'ThousandNeedlesC39',1,400,400,23,35,0,-5400.44,-2696.1,-41.6368,5.12985,'2115:0 2116:0 2118:0 '), +(2118,'ThousandNeedlesC40',1,400,400,23,35,0,-5508.15,-2681.27,-49.789,6.27849,'2109:0 2117:0 2119:0 '), +(2119,'ThousandNeedlesC41',1,400,400,23,35,0,-5557.86,-2761.38,-53.7029,2.24548,'2108:0 2118:0 2121:0 '), +(2120,'ThousandNeedlesC42',1,400,2303,23,35,0,-5427.21,-2905.53,-56.1528,0.737522,'2116:0 2121:0 2122:0 '), +(2121,'ThousandNeedlesC43',1,400,400,23,35,0,-5557.61,-2850.74,-55.2145,1.22644,'2119:0 2120:0 2124:0 '), +(2122,'ThousandNeedlesC44',1,400,2303,23,35,0,-5449.75,-3008,-48.4049,1.26768,'2120:0 2123:0 2125:0 '), +(2123,'ThousandNeedlesC45',1,400,2303,23,35,0,-5324.57,-3065.64,-49.0275,2.62249,'2116:0 2122:0 2127:0 '), +(2124,'ThousandNeedlesC46',1,400,400,23,35,0,-5623.73,-2943.53,-50.4623,1.05563,'2121:0 2125:0 2126:0 '), +(2125,'ThousandNeedlesC47',1,400,400,23,35,0,-5602.92,-3056.82,-51.9701,1.71732,'2122:0 2124:0 2126:0 2130:0 '), +(2126,'ThousandNeedlesC48',1,400,400,23,35,0,-5718.6,-3126.82,-37.7719,1.04973,'2124:0 2125:0 2131:0 '), +(2127,'ThousandNeedlesC49',1,400,400,23,35,0,-5328.41,-3207.34,-47.4244,5.04348,'2123:0 2128:0 2129:0 '), +(2128,'ThousandNeedlesC50',1,400,400,23,35,0,-5464.87,-3276.04,-34.1317,0.525478,'2127:0 2129:0 2130:0 '), +(2129,'ThousandNeedlesC51',1,400,400,23,35,0,-5455.25,-3387.53,-41.6009,1.22056,'2127:0 2128:0 2132:0 '), +(2130,'ThousandNeedlesC52',1,400,400,23,35,0,-5569.49,-3268.7,-44.3082,1.72125,'2125:0 2128:0 2131:0 2133:0 '), +(2131,'ThousandNeedlesC53',1,400,400,23,35,0,-5740.95,-3216.87,-41.218,1.17932,'2126:0 2130:0 121:0 '), +(2132,'ThousandNeedlesC54',1,400,439,23,35,0,-5553.99,-3514.19,-56.2254,0.918174,'2129:0 2133:0 2134:0 '), +(2133,'ThousandNeedlesC55',1,400,439,23,35,0,-5650.38,-3490.29,-57.2333,1.16557,'121:0 2130:0 2132:0 122:0 2134:0 '), +(2134,'ThousandNeedlesE1',1,400,2240,23,35,0,-5619.93,-3820.34,-58.7494,1.7723,'121:0 122:0 2132:0 2133:0 2135:0 2136:0 '), +(2135,'ThousandNeedlesE2',1,400,439,23,35,0,-5645.47,-4214.99,-58.7498,1.71339,'2134:0 2136:0 '), +(2136,'ThousandNeedlesE3',1,400,2240,23,35,0,-5936.34,-4266.6,-58.7493,0.65899,'122:0 2134:0 2135:0 117:0 2137:0 '), +(2137,'ThousandNeedlesE4',1,400,2240,23,35,0,-6405.27,-4240.86,-58.749,0.861231,'117:0 123:0 2136:0 '), +(2138,'ThousandNeedlesE5',1,400,439,23,35,0,-6173.94,-3567.25,-58.7497,5.13183,'117:0 118:0 121:0 122:0 123:0 '), +(2139,'ThousandNeedlesE6',1,400,439,23,35,0,-6638.63,-3654.02,-58.7493,0.631484,'118:0 2141:0 '), +(2140,'ThousandNeedlesE7',1,400,439,23,35,0,-6660.6,-3797.14,-58.5293,2.97002,'123:0 2141:0 '), +(2141,'ThousandNeedlesE8',1,400,439,23,35,0,-6820.59,-3759.56,21.2097,1.72713,'2139:0 2140:0 2142:0 '), +(2142,'TanarisExitN1',1,440,440,38,50,0,-6890.93,-3772.75,52.9557,0.201496,'2141:0 2143:0 '), +(2143,'TanarisExitN2',1,440,440,38,50,0,-6952.74,-3716.92,43.1345,3.42359,'2142:0 2144:0 2145:0 '), +(2144,'GadgetzanH1',1,440,976,38,50,5,-7047.86,-3764.87,11.4018,3.75207,'2143:0 52:0 2147:0 2148:0 2149:0 2166:0 '), +(2145,'GadgetzanA1',1,440,976,38,50,2,-7182.58,-3638.57,11.2417,5.95314,'2143:0 2146:0 2165:0 2166:0 '), +(2146,'GadgetzanA2',1,440,976,38,50,3,-7232.4,-3740.53,8.47838,5.52708,'2145:0 52:0 2147:0 2148:0 2165:0 2166:0 '), +(2147,'GadgetzanS',1,440,976,38,50,1,-7160.74,-3844,8.72832,1.31539,'52:0 2144:0 2146:0 '), +(2148,'Tanaris1',1,440,440,38,50,0,-7236.3,-4040.94,11.0963,1.40179,'2144:0 2146:0 2149:0 2150:0 2158:0 2161:0 2165:0 '), +(2149,'Tanaris2',1,440,1937,38,50,0,-7012.17,-4180.07,10.8027,2.40318,'2144:0 2148:0 2150:0 2151:0 2152:0 '), +(2150,'Tanaris3',1,440,440,38,50,0,-7183.44,-4321.24,9.48149,1.28398,'2148:0 2149:0 2151:0 2152:0 2157:0 2158:0 '), +(2151,'Tanaris4',1,440,1937,38,50,0,-6915.38,-4366.17,11.3936,2.18522,'2149:0 2150:0 2152:0 '), +(2152,'Tanaris5',1,440,440,38,50,0,-7083.47,-4512.85,8.48356,1.53726,'2149:0 2150:0 2151:0 2153:0 2157:0 2158:0 '), +(2153,'Tanaris6',1,440,977,38,50,0,-6965.03,-4691.43,8.43736,2.08705,'2152:0 2154:0 2155:0 2156:0 2157:0 '), +(2154,'Tanaris7',1,440,977,38,50,0,-6745.21,-4852.73,0.167925,2.76838,'2153:0 2155:0 '), +(2155,'Tanaris8',1,440,977,38,50,0,-6936.98,-4872.05,0.71439,1.89659,'2153:0 2154:0 2156:0 '), +(2156,'Tanaris9',1,440,988,38,50,0,-7135.69,-4833.99,0.699453,1.36644,'2153:0 2155:0 2157:0 '), +(2157,'Tanaris10',1,440,985,38,50,0,-7326.32,-4666.64,8.78219,0.194235,'2150:0 2152:0 2153:0 2156:0 2158:0 113:0 '), +(2158,'Tanaris11',1,440,985,38,50,0,-7455.84,-4410.59,11.5646,0.58301,'2148:0 2150:0 2152:0 2157:0 113:0 2160:0 2161:0 '), +(2159,'Tanaris12',1,440,988,40,50,0,-7777.85,-4943.31,6.30343,4.78293,'113:0 2160:0 2210:0 '), +(2160,'Tanaris13',1,440,985,38,50,0,-7765.82,-4633.67,11.3435,5.2954,'113:0 2158:0 2159:0 2161:0 2162:0 '), +(2161,'Tanaris14',1,440,1938,38,50,0,-7816.44,-4067.72,9.39531,5.57225,'2148:0 2158:0 2160:0 2162:0 2163:0 2164:0 2165:0 '), +(2162,'Tanaris15',1,440,440,40,50,0,-8124.61,-4381.44,10.6814,6.10827,'2160:0 2161:0 2163:0 2206:0 '), +(2163,'Tanaris16',1,440,440,40,50,0,-8211.49,-4018.52,10.2243,5.89032,'2161:0 2162:0 2164:0 2205:0 2209:0 '), +(2164,'Tanaris17',1,440,1938,38,50,0,-7957.98,-3774.76,42.6206,4.73579,'2161:0 2163:0 2165:0 2173:0 '), +(2165,'Tanaris18',1,440,440,38,50,0,-7532.17,-3671.41,9.08631,5.75681,'2145:0 2146:0 2148:0 2161:0 2164:0 2166:0 2172:0 2173:0 '), +(2166,'Tanaris19',1,440,440,38,50,0,-7149.95,-3429.64,10.9479,4.73187,'2144:0 2145:0 2146:0 2165:0 2167:0 2170:0 2172:0 '), +(2167,'Tanaris20',1,440,979,38,50,0,-6944.47,-3147.06,30.6034,1.0405,'2166:0 2168:0 2169:0 2170:0 '), +(2168,'Tanaris21',1,440,978,38,50,0,-6847.43,-2908.93,8.88901,4.55712,'2167:0 2169:0 2170:0 '), +(2169,'Tanaris22',1,440,979,38,50,0,-6994.4,-2790.52,8.87793,5.49763,'2167:0 2168:0 2170:0 2171:0 '), +(2170,'Tanaris23',1,440,979,38,50,0,-7181.29,-3008.99,31.1598,2.12434,'2166:0 2167:0 2168:0 2169:0 2171:0 2172:0 '), +(2171,'Tanaris24',1,440,982,40,50,0,-7405.37,-2656.15,12.3269,5.64056,'2169:0 2170:0 2172:0 2174:0 2175:0 '), +(2172,'Tanaris25',1,440,979,38,50,0,-7432.96,-3165.88,11.6881,0.195781,'2165:0 2166:0 2170:0 2171:0 2173:0 2174:0 '), +(2173,'Tanaris26',1,440,1939,38,50,0,-7856.78,-3289.98,68.7512,5.401,'2164:0 2165:0 2172:0 2174:0 2184:0 2187:0 '), +(2174,'Tanaris27',1,440,982,40,50,0,-7689.14,-2835.54,14.0257,3.79487,'2171:0 2172:0 2173:0 2175:0 2179:0 2182:0 2184:0 '), +(2175,'Tanaris28',1,440,982,44,55,0,-7757.78,-2516.52,8.85209,5.79567,'2171:0 2174:0 2176:0 2177:0 2178:0 2179:0 2183:0 '), +(2176,'Tanaris29',1,440,982,38,50,0,-7885.89,-2645.63,-56.7571,1.76264,'2175:0 '), +(2177,'Tanaris30',1,440,982,38,50,0,-7691.97,-2600,-56.5855,2.87594,'2175:0 '), +(2178,'Tanaris31',1,440,982,38,50,0,-7867.56,-2503.34,-45.915,6.01951,'2175:0 '), +(2179,'Tanaris32',1,440,982,44,55,0,-8024.98,-2496.1,4.7767,5.76032,'2174:0 2175:0 2180:0 2181:0 2182:0 '), +(2180,'Tanaris33',1,440,982,38,50,0,-7961.12,-2608.77,-51.3402,2.94075,'2181:0 2179:0 '), +(2181,'Tanaris34',1,440,982,38,50,0,-7948.03,-2388.48,-27.27,2.48522,'2180:0 2179:0 '), +(2182,'Tanaris35',1,440,982,44,55,0,-8192.63,-2678.87,9.10516,6.208,'2174:0 2179:0 2183:0 2185:0 '), +(2183,'Tanaris36',1,440,982,40,50,0,-8237.94,-2283.21,9.47101,5.21841,'2175:0 2182:0 2188:0 2189:0 2190:0 '), +(2184,'Tanaris37',1,440,1939,40,50,0,-8080.23,-2940.32,41.1249,5.97434,'2173:0 2174:0 2185:0 2186:0 2187:0 '), +(2185,'Tanaris38',1,440,983,40,50,0,-8463.57,-2772.2,16.342,6.04503,'2182:0 2184:0 2186:0 2187:0 2190:0 '), +(2186,'Tanaris39',1,440,983,40,50,0,-8572.13,-2989.04,10.5515,0.775002,'2184:0 2185:0 2187:0 2190:0 2194:0 2196:0 '), +(2187,'Tanaris40',1,440,983,40,50,0,-8349.72,-3161.78,10.5974,1.38762,'2173:0 2184:0 2185:0 2186:0 '), +(2188,'Tanaris41',1,440,440,44,55,0,-8568.73,-2090.66,8.81721,5.96256,'2183:0 2189:0 2220:0 '), +(2189,'Tanaris42',1,440,980,44,55,0,-8728.04,-2264.78,8.87691,0.109387,'2183:0 2188:0 2190:0 2191:0 2192:0 '), +(2190,'Tanaris43',1,440,440,40,50,0,-8780.75,-2536.53,12.5632,1.2757,'2183:0 2185:0 2186:0 2189:0 2192:0 2194:0 '), +(2191,'Tanaris44',1,440,980,44,55,0,-9113.86,-2138.28,14.5526,5.80156,'2189:0 2192:0 '), +(2192,'Tanaris45',1,440,440,44,55,0,-9078.5,-2368.44,15.1243,6.03129,'2189:0 2190:0 2191:0 2193:0 2194:0 '), +(2193,'Tanaris46',1,440,440,40,50,0,-9394.76,-2441.52,12.341,0.162396,'2192:0 114:0 2194:0 '), +(2194,'Tanaris47',1,440,992,40,50,0,-9166,-2942.59,27.3153,4.4055,'114:0 2186:0 2190:0 2192:0 2193:0 2195:0 2196:0 '), +(2195,'Tanaris48',1,440,992,40,50,0,-9259.98,-3213.55,17.6595,1.63894,'114:0 2194:0 2196:0 2197:0 2198:0 '), +(2196,'Tanaris49',1,440,984,40,50,0,-8891.65,-3316.57,17.9459,2.09054,'2186:0 2194:0 2195:0 2197:0 2198:0 '), +(2197,'Tanaris50',1,440,981,44,55,0,-8891.37,-3634.86,9.8928,2.97216,'2195:0 2196:0 2198:0 2199:0 2204:0 2205:0 '), +(2198,'Tanaris51',1,440,440,40,50,0,-9376.51,-3525.65,10.4557,1.04009,'2195:0 2196:0 2197:0 2199:0 '), +(2199,'Tanaris52',1,440,981,44,55,0,-9238.88,-3842.23,16.1398,6.21782,'2197:0 2198:0 2200:0 2201:0 2204:0 '), +(2200,'Tanaris53',1,440,981,44,55,0,-9122.46,-4119.91,14.5657,1.92562,'2199:0 2202:0 2203:0 2204:0 '), +(2201,'Tanaris54',1,440,981,38,50,0,-9448.09,-4056.48,-45.7265,1.70179,'2199:0 '), +(2202,'Tanaris55',1,440,981,38,50,0,-9058.94,-4138.03,-30.5429,1.76658,'2200:0 '), +(2203,'Tanaris56',1,440,981,38,50,0,-9239.16,-3990.68,-38.2526,5.54632,'2200:0 '), +(2204,'Tanaris57',1,440,981,44,55,0,-8846.97,-4120.67,11.0961,2.09647,'2197:0 2199:0 2200:0 2205:0 2209:0 '), +(2205,'Tanaris58',1,440,440,40,50,0,-8582.56,-3848.15,27.4666,3.85733,'2163:0 2197:0 2204:0 2209:0 '), +(2206,'Tanaris59',1,440,440,40,50,0,-8436.38,-4485.59,9.98615,0.059917,'2162:0 2207:0 2209:0 '), +(2207,'Tanaris60',1,440,1940,44,50,0,-8543.26,-4792.62,0.74885,2.08624,'2206:0 2208:0 2371:0 '), +(2208,'Tanaris61',1,440,1940,44,50,0,-8731.47,-4651.05,6.22094,6.04857,'2207:0 2209:0 '), +(2209,'Tanaris62',1,440,981,40,50,0,-8636.73,-4274.81,14.2733,1.53645,'2163:0 2204:0 2205:0 2206:0 2208:0 '), +(2210,'Tanaris63',1,440,1336,40,50,0,-7834.45,-5044.24,4.58704,0.700017,'2159:0 2211:0 '), +(2211,'Tanaris64',1,440,1336,40,50,0,-7869.58,-5185.77,0.738081,3.22506,'2210:0 2212:0 2213:0 2215:0 '), +(2212,'Tanaris65',1,440,1336,40,50,0,-8043.72,-5139.25,11.1577,5.95236,'2211:0 '), +(2213,'Tanaris66',1,440,1336,40,50,0,-7985.53,-5405.09,1.12058,1.11823,'2211:0 2214:0 2215:0 2216:0 '), +(2214,'Tanaris67',1,440,1336,40,50,0,-7957.94,-5481.79,0.207866,2.5025,'2213:0 '), +(2215,'Tanaris68',1,440,1336,40,50,0,-7979.86,-5272.84,0.574685,0.762833,'2211:0 2213:0 2216:0 2217:0 2218:0 2219:0 '), +(2216,'Tanaris69',1,440,1336,40,50,0,-8057.83,-5349.53,19.8843,5.57338,'2213:0 2215:0 '), +(2217,'Tanaris70',1,440,1336,40,50,0,-8101.51,-5296.31,2.11654,0.146278,'2215:0 '), +(2218,'Tanaris71',1,440,1336,40,50,0,-8088.86,-5238.43,1.94444,6.18206,'2215:0 '), +(2219,'Tanaris72',1,440,1336,40,50,0,-8005.83,-5204.31,4.73672,1.59535,'2215:0 '), +(2220,'TanarisExitSW',1,440,440,44,55,0,-8243.03,-2079.74,-95.586,0.052021,'2188:0 2221:0 '), +(2221,'Ungoro1',1,490,490,44,56,0,-8066.01,-2098.76,-168.295,6.18792,'2220:0 2222:0 '), +(2222,'Ungoro2',1,490,490,44,56,0,-7916.41,-2142.07,-238.077,0.49967,'2221:0 2223:0 '), +(2223,'Ungoro3',1,490,490,44,56,0,-7864.72,-2099.17,-266.094,3.40762,'2222:0 2224:0 2260:0 '), +(2224,'Ungoro4',1,490,1942,44,56,0,-7934.33,-2040.71,-267.978,5.68527,'2223:0 2225:0 '), +(2225,'Ungoro5',1,490,1942,44,56,0,-7884.5,-1843.96,-274.777,4.70158,'2224:0 2226:0 2260:0 2261:0 '), +(2226,'Ungoro6',1,490,540,46,56,0,-8088.18,-1491.28,-269.655,5.9268,'2225:0 2227:0 2228:0 2261:0 '), +(2227,'Ungoro7',1,490,490,46,56,0,-8249.65,-1584.9,-198.819,0.538974,'2226:0 2229:0 '), +(2228,'Ungoro8',1,490,540,46,56,0,-8064.8,-1172.37,-270.687,4.96077,'2226:0 2229:0 2232:0 '), +(2229,'Ungoro9',1,490,540,46,56,0,-7866.69,-1345.21,-274.102,2.63992,'2227:0 2228:0 2230:0 2231:0 2232:0 2261:0 2262:0 2265:0 '), +(2230,'Ungoro10',1,490,490,46,56,0,-7978.25,-1085.08,-328.506,4.68783,'2229:0 '), +(2231,'Ungoro11',1,490,540,46,56,0,-8110.2,-1212.13,-336.702,6.17813,'2229:0 '), +(2232,'Ungoro12',1,490,539,48,56,0,-7862.94,-1011.26,-266.988,4.49344,'2228:0 2229:0 2233:0 2234:0 2262:0 '), +(2233,'Ungoro13',1,490,539,48,56,0,-7893.05,-604.125,-260.044,4.74085,'2232:0 2234:0 2235:0 '), +(2234,'Ungoro14',1,490,539,48,56,0,-7779.84,-752.864,-268.32,4.3658,'2232:0 2233:0 2235:0 2262:0 2263:0 '), +(2235,'Ungoro15',1,490,543,48,56,0,-7516.02,-568.085,-272.224,3.45867,'2233:0 2234:0 2236:0 2240:0 2263:0 '), +(2236,'Ungoro16',1,490,543,48,56,0,-7357.96,-423.47,-270.864,3.80227,'2235:0 2237:0 2238:0 '), +(2237,'Ungoro17',1,490,543,48,56,0,-7453.5,-224.428,-202.131,5.11585,'2236:0 '), +(2238,'Ungoro18',1,490,543,48,56,0,-7065.02,-412.024,-271.337,3.13271,'2236:0 2239:0 2240:0 '), +(2239,'Ungoro19',1,490,490,48,56,0,-6942.78,-227.898,-200.477,4.11643,'2238:0 '), +(2240,'Ungoro20',1,490,543,48,56,0,-7057.61,-568.049,-272.117,3.13074,'2235:0 2238:0 2241:0 2273:0 2275:0 '), +(2241,'Ungoro21',1,490,490,46,56,0,-6819.88,-500.077,-273.734,4.04377,'2240:0 2242:0 2273:0 '), +(2242,'Ungoro22',1,490,490,46,56,0,-6621.16,-628.631,-271.06,2.65557,'2241:0 2243:0 2273:0 2289:0 '), +(2243,'Ungoro23',1,490,490,46,56,0,-6516.77,-714.125,-272.04,4.8802,'2242:0 2244:0 2271:0 '), +(2244,'Ungoro24',1,490,538,46,56,0,-6357.05,-1044.55,-271.024,2.01939,'2243:0 2245:0 2247:0 '), +(2245,'Ungoro25',1,490,541,46,56,0,-6247.33,-1096.9,-216.374,2.3198,'111:0 2244:0 2246:0 '), +(2246,'Ungoro26',1,490,541,46,56,0,-6263.44,-1146.22,-245.676,0.70581,'111:0 2245:0 2247:0 '), +(2247,'Ungoro27',1,490,538,46,56,0,-6307.92,-1177.22,-269.521,1.78377,'2244:0 2246:0 2248:0 '), +(2248,'Ungoro28',1,490,538,46,56,0,-6416.8,-1325.76,-272.118,0.908046,'2247:0 2249:0 2270:0 2271:0 '), +(2249,'Ungoro29',1,490,538,46,56,0,-6394.87,-1511.09,-269.907,1.76217,'2248:0 112:0 2251:0 2269:0 2270:0 2271:0 '), +(2250,'Ungoro30',1,490,542,46,56,0,-6359.08,-1992.29,-276.383,1.21435,'112:0 '), +(2251,'Ungoro31',1,490,538,46,56,0,-6575.48,-1808.43,-271.11,0.413242,'112:0 2249:0 2252:0 2253:0 2269:0 2270:0 '), +(2252,'Ungoro32',1,490,490,46,56,0,-6527.29,-2027.48,-271.963,1.5501,'2251:0 2253:0 '), +(2253,'Ungoro33',1,490,490,46,56,0,-6834.73,-2083.14,-267.953,0.248293,'2251:0 2252:0 2254:0 2267:0 2269:0 '), +(2254,'Ungoro34',1,490,1942,46,56,0,-7068.84,-2256.36,-271.191,0.560497,'2253:0 2255:0 2256:0 2267:0 '), +(2255,'Ungoro35',1,490,1943,46,56,0,-6944.44,-2436.79,-201.566,2.1097,'2254:0 '), +(2256,'Ungoro36',1,490,1942,46,56,0,-7266.48,-2293,-268.44,0.00677204,'2254:0 2257:0 '), +(2257,'Ungoro37',1,490,1942,44,56,0,-7476.48,-2263.22,-266.534,5.76177,'2256:0 2258:0 2259:0 '), +(2258,'Ungoro38',1,490,1942,46,56,0,-7419.79,-2422.73,-214.477,1.54419,'2257:0 '), +(2259,'Ungoro39',1,490,1942,44,56,0,-7613.45,-2129.98,-272.086,5.58506,'2257:0 2260:0 2266:0 '), +(2260,'Ungoro40',1,490,1942,44,56,0,-7787.58,-1962.82,-272.166,5.62628,'2223:0 2225:0 2259:0 2261:0 2266:0 '), +(2261,'Ungoro41',1,490,1942,46,56,0,-7685.24,-1654.69,-272.216,4.29698,'2225:0 2226:0 2229:0 2260:0 2265:0 2266:0 '), +(2262,'Ungoro42',1,490,490,48,56,0,-7724.44,-1062.81,-269.408,1.8701,'2229:0 2232:0 2234:0 2263:0 2264:0 '), +(2263,'Ungoro43',1,490,539,48,56,0,-7524.61,-811.974,-269.097,2.63781,'2234:0 2235:0 2262:0 2264:0 2276:0 '), +(2264,'Ungoro44',1,490,490,46,56,0,-7536.49,-1136.09,-271.668,2.21958,'2262:0 2263:0 2265:0 2276:0 '), +(2265,'Ungoro45',1,490,490,46,56,0,-7540.94,-1440.28,-272.37,1.75227,'2229:0 2261:0 2264:0 2268:0 '), +(2266,'Ungoro46',1,490,1942,44,56,0,-7481.25,-1864.35,-272.185,3.61563,'2259:0 2260:0 2261:0 2267:0 '), +(2267,'Ungoro47',1,490,1942,46,56,0,-7196.2,-1804.18,-272.191,4.61501,'2253:0 2254:0 2266:0 2268:0 '), +(2268,'Ungoro48',1,490,537,46,56,0,-7052.38,-1597.57,-270.962,3.20599,'2265:0 2267:0 2269:0 2270:0 2272:0 2279:0 2280:0 '), +(2269,'Ungoro49',1,490,538,46,56,0,-6864.28,-1691.91,-271.236,0.290198,'2249:0 2251:0 2253:0 2268:0 2270:0 '), +(2270,'Ungoro50',1,490,538,46,56,0,-6742.71,-1481.45,-270.728,0.152749,'2248:0 2249:0 2251:0 2268:0 2269:0 2271:0 2272:0 '), +(2271,'Ungoro51',1,490,538,46,56,0,-6687.22,-1202.84,-270.132,5.43062,'2243:0 2248:0 2249:0 2270:0 2272:0 2274:0 2275:0 '), +(2272,'Ungoro52',1,490,537,48,56,0,-6971.9,-1359.19,-272.023,5.74869,'2268:0 2270:0 2271:0 2280:0 '), +(2273,'Ungoro53',1,490,490,46,56,0,-6905.86,-744.591,-271.912,1.82168,'2240:0 2241:0 2242:0 2274:0 2275:0 '), +(2274,'Ungoro54',1,490,490,46,56,0,-6713.26,-889.582,-269.981,2.47553,'2271:0 2273:0 2275:0 '), +(2275,'Ungoro55',1,490,490,46,56,0,-7071.75,-971.188,-268.921,1.5625,'2240:0 2271:0 2273:0 2274:0 2276:0 '), +(2276,'Ungoro56',1,490,537,48,56,0,-7262.04,-1142.55,-256.214,3.05082,'2263:0 2264:0 2275:0 2277:0 '), +(2277,'Ungoro57',1,490,537,48,56,0,-7276.34,-1237.12,-242.301,0.11537,'2276:0 2278:0 2286:0 '), +(2278,'Ungoro58',1,490,537,48,56,0,-7196.58,-1221.48,-206.377,2.70522,'2277:0 '), +(2279,'Ungoro59',1,490,537,48,56,0,-7139.11,-1460.35,-240.441,5.44625,'2268:0 '), +(2280,'Ungoro60',1,490,537,48,56,0,-6995.92,-1426.86,-263.362,1.61744,'2268:0 2272:0 2281:0 '), +(2281,'Ungoro61',1,490,537,48,56,0,-7087.82,-1424.62,-235.604,0.254778,'2280:0 2282:0 '), +(2282,'Ungoro62',1,490,537,48,56,0,-7094.67,-1371.27,-215.077,4.51555,'2281:0 2283:0 '), +(2283,'Ungoro63',1,490,537,48,56,0,-7151.31,-1318.52,-184.389,0.761355,'2282:0 2284:0 2285:0 '), +(2284,'Ungoro64',1,490,537,48,56,0,-7093.52,-1298.11,-185.557,3.52203,'2283:0 '), +(2285,'Ungoro65',1,490,537,48,56,0,-7193.59,-1360.95,-184.286,6.25914,'2283:0 '), +(2286,'Ungoro66',1,490,537,48,56,0,-7288.45,-1306.18,-240.621,1.48982,'2277:0 2287:0 '), +(2287,'Ungoro67',1,490,537,48,56,0,-7220.03,-1431,-231.915,2.60313,'2286:0 2288:0 '), +(2288,'Ungoro68',1,490,537,48,56,0,-7328.61,-1466.63,-241.155,0.211588,'2287:0 '), +(2289,'Ungoro69',1,490,490,50,56,0,-6487.3,-527.418,-240.618,3.68697,'2242:0 2290:0 '), +(2290,'Ungoro70',1,490,490,50,56,0,-6221.83,-588.48,-118.307,2.76414,'2289:0 2291:0 '), +(2291,'Ungoro71',1,490,490,50,56,0,-6222.04,-471.141,-64.8223,4.41936,'2290:0 2292:0 '), +(2292,'Silithus1',1,1377,1377,50,60,0,-6290.28,-372.309,-0.180207,5.29035,'2291:0 184:0 '), +(2293,'Silithus2',1,1377,1377,50,60,0,-6723.42,16.99,1.33043,2.60703,'184:0 2294:0 2295:0 2296:0 '), +(2294,'Silithus3',1,1377,2738,50,60,0,-7058.45,311.497,7.73577,5.31076,'2293:0 2295:0 2304:0 2350:0 2351:0 2352:0 2353:0 '), +(2295,'Silithus4',1,1377,1377,50,60,0,-6775.62,325.939,7.96733,4.88272,'191:0 2293:0 2294:0 2296:0 2304:0 2350:0 '), +(2296,'Silithus5',1,1377,1377,53,60,0,-6480.77,427.825,2.85797,3.86957,'191:0 2293:0 2295:0 2299:0 2304:0 2311:0 '), +(2297,'Silithus6',1,1377,3446,53,60,0,-6249.14,4.14336,6.27956,4.62356,'191:0 2298:0 '), +(2298,'Silithus7',1,1377,3446,53,60,0,-6303.03,23.7784,-8.51367,4.98288,'2297:0 '), +(2299,'Silithus8',1,1377,1377,53,60,0,-6325.77,679.48,7.83164,4.56898,'191:0 2296:0 2300:0 2304:0 2311:0 '), +(2300,'Silithus9',1,1377,2742,53,60,0,-6512.68,1140.49,5.52295,5.13838,'2299:0 2301:0 2311:0 2317:0 2322:0 2323:0 '), +(2301,'Silithus10',1,1377,2742,53,60,0,-6376.67,1003.32,-29.6361,5.81971,'2300:0 2302:0 2303:0 '), +(2302,'Silithus11',1,1377,2742,53,60,0,-6233.34,982.199,-44.8168,2.95104,'2301:0 2303:0 '), +(2303,'Silithus12',1,1377,2742,53,60,0,-6264.39,1180.37,-17.4845,2.70757,'2301:0 2302:0 '), +(2304,'Silithus13',1,1377,3425,50,60,0,-6806.63,645.151,14.4502,4.36084,'2294:0 2295:0 2296:0 2299:0 2305:0 2350:0 2351:0 '), +(2305,'Silithus14',1,1377,3425,53,60,0,-6809.1,722.467,39.8123,2.17349,'2304:0 2306:0 188:0 '), +(2306,'Silithus15',1,1377,3425,53,60,0,-6835.28,767.676,43.3803,5.14818,'2305:0 188:0 2307:0 2308:0 2309:0 2310:0 '), +(2307,'Silithus16',1,1377,3425,53,60,5,-6754.01,823.51,57.2677,3.58719,'2306:0 '), +(2308,'Silithus17',1,1377,3425,53,60,1,-6739.83,766.866,128.438,3.03271,'2306:0 '), +(2309,'Silithus18',1,1377,3425,53,60,0,-6767.97,869.849,23.6798,5.43995,'2306:0 2310:0 2311:0 '), +(2310,'Silithus19',1,1377,3425,53,60,0,-6882.21,862.037,35.6652,6.05845,'2306:0 2309:0 2323:0 '), +(2311,'Silithus20',1,1377,2742,53,60,0,-6689.89,843.881,1.63418,4.68008,'2296:0 2299:0 2300:0 2309:0 2312:0 2313:0 '), +(2312,'Silithus21',1,1377,2742,53,60,0,-6610.39,1133.65,-47.8484,4.53085,'2311:0 '), +(2313,'Silithus22',1,1377,2742,53,60,0,-6560.42,848.396,-19.7968,5.5872,'2311:0 2314:0 2315:0 2316:0 '), +(2314,'Silithus23',1,1377,2742,53,60,0,-6485.94,926.037,-42.1164,4.07923,'2313:0 '), +(2315,'Silithus24',1,1377,2742,53,60,0,-6592.71,755.859,-52.6973,0.303426,'2313:0 '), +(2316,'Silithus25',1,1377,2742,53,60,0,-6626.24,951.29,-53.3026,5.44387,'2313:0 '), +(2317,'Silithus26',1,1377,2742,53,60,0,-6416.82,1332.7,1.61694,4.0223,'2300:0 2318:0 2322:0 '), +(2318,'Silithus27',1,1377,2740,53,60,0,-6260.69,1633.49,6.1983,4.14011,'2317:0 2319:0 133:0 '), +(2319,'Silithus28',1,1377,1377,53,60,0,-6412.61,1879.6,4.97151,5.48313,'2318:0 133:0 2320:0 134:0 '), +(2320,'Silithus29',1,1377,1377,53,60,0,-6709.78,1906.69,5.58306,6.01956,'2319:0 2321:0 134:0 '), +(2321,'Silithus30',1,1377,2743,53,60,0,-6952.45,1684.29,2.49135,0.649405,'2320:0 134:0 2322:0 2325:0 '), +(2322,'Silithus31',1,1377,1377,53,60,0,-6798.69,1359.48,2.84112,2.99187,'134:0 189:0 2300:0 2317:0 2321:0 2325:0 '), +(2323,'Silithus32',1,1377,1377,53,60,0,-6935.53,933.15,20.0826,5.50711,'177:0 189:0 2300:0 2310:0 2324:0 2351:0 '), +(2324,'Silithus33',1,1377,1377,53,60,0,-7170.32,969.526,1.6856,0.846154,'177:0 189:0 2323:0 2335:0 2343:0 '), +(2325,'Silithus34',1,1377,2743,55,60,0,-7102.06,1490.93,6.9342,0.374927,'189:0 2321:0 2322:0 2326:0 '), +(2326,'Silithus35',1,1377,2743,55,60,0,-7227.39,1476.62,-3.4765,1.83774,'2325:0 2327:0 2335:0 2336:0 '), +(2327,'Silithus36',1,1377,2743,55,60,0,-7304.89,1624.78,-30.4609,5.43684,'2326:0 2328:0 2329:0 2330:0 '), +(2328,'Silithus37',1,1377,2743,55,60,0,-7466.84,1605.7,-50.256,4.1959,'2327:0 2331:0 '), +(2329,'Silithus38',1,1377,2743,55,60,0,-7434.92,1813.75,-46.5685,5.83934,'2327:0 2332:0 '), +(2330,'Silithus39',1,1377,2743,55,60,0,-7246.4,1714.88,-63.0024,4.43346,'2327:0 2333:0 2334:0 '), +(2331,'Silithus40',1,1377,1377,53,60,0,-7396.76,1242.93,-84.3036,3.00401,'2328:0 '), +(2332,'Silithus41',1,1377,2743,53,60,0,-7611.96,1707.51,-36.9118,0.00770472,'2329:0 '), +(2333,'Silithus42',1,1377,2743,53,60,0,-7390.74,1717.46,-92.9352,5.54673,'2330:0 '), +(2334,'Silithus43',1,1377,2743,53,60,0,-7320.18,1835.39,-90.5829,0.977671,'2330:0 '), +(2335,'Silithus44',1,1377,1377,53,60,0,-7395.86,1200.83,3.1343,6.21237,'177:0 189:0 2324:0 2326:0 2336:0 2341:0 2342:0 2343:0 '), +(2336,'Silithus45',1,1377,2743,53,60,0,-7467.85,1375.97,4.32407,0.317961,'2326:0 2335:0 135:0 2337:0 2341:0 2342:0 '), +(2337,'Silithus46',1,1377,1377,53,60,0,-7741.83,1415.69,0.628408,1.87894,'135:0 2336:0 2338:0 2339:0 2340:0 '), +(2338,'Silithus47',1,1377,1377,53,60,0,-7929.26,1491.4,-6.62955,0.141247,'135:0 2337:0 2339:0 '), +(2339,'Silithus48',1,1377,1377,53,60,0,-7951.61,1381.21,2.92482,0.094128,'193:0 2337:0 2338:0 2340:0 '), +(2340,'Silithus49',1,1377,1377,53,60,0,-7884.95,1305.55,-9.32275,1.92018,'193:0 2337:0 2339:0 2341:0 '), +(2341,'Silithus50',1,1377,1377,53,60,0,-7724.06,1121.13,1.90701,2.14991,'193:0 2335:0 2336:0 2340:0 2342:0 2347:0 '), +(2342,'Silithus51',1,1377,1377,53,60,0,-7614.56,1040.35,4.34335,2.37375,'2335:0 2336:0 2341:0 2343:0 2347:0 2348:0 '), +(2343,'Silithus52',1,1377,1377,53,60,0,-7446.46,1011.04,1.56245,2.60898,'177:0 2324:0 2335:0 2342:0 2348:0 '), +(2344,'Silithus53',1,1377,3427,53,60,0,-8071.74,1080.25,7.69378,0.464863,'193:0 2345:0 '), +(2345,'Silithus54',1,1377,1377,55,60,0,-8049.18,988.938,42.0219,2.43622,'2344:0 '), +(2346,'Silithus55',1,1377,2744,55,60,0,-8023.11,662.371,-12.5444,1.51572,'193:0 2359:0 '), +(2347,'Silithus56',1,1377,2744,53,60,0,-7855.36,863.831,-4.99768,1.56248,'193:0 2341:0 2342:0 2348:0 '), +(2348,'Silithus57',1,1377,1377,53,60,0,-7512.58,878.283,-2.19269,1.08534,'177:0 2342:0 2343:0 2347:0 2349:0 2350:0 '), +(2349,'Silithus58',1,1377,1377,53,60,0,-7412.18,577.662,-7.35207,4.30862,'177:0 2348:0 2355:0 2357:0 192:0 2359:0 '), +(2350,'Silithus59',1,1377,1377,53,60,0,-7177.07,574.409,1.58818,2.04744,'177:0 2294:0 2295:0 2304:0 2348:0 2351:0 2352:0 2355:0 '), +(2351,'Silithus60',1,1377,3097,53,60,0,-6922.48,666.906,11.4539,0.176223,'2294:0 2304:0 2323:0 2350:0 '), +(2352,'Silithus61',1,1377,2738,50,60,0,-7078.09,417.604,8.20627,4.43703,'2294:0 2350:0 2353:0 '), +(2353,'Silithus62',1,1377,2738,50,60,0,-7146.41,350.222,26.2951,0.0509645,'2294:0 2352:0 2354:0 2356:0 '), +(2354,'Silithus63',1,1377,2738,50,60,0,-7259.78,328.568,47.9494,0.00187492,'2353:0 2356:0 2357:0 '), +(2355,'Silithus64',1,1377,2738,50,60,0,-7283.42,473.511,11.4843,5.52991,'2349:0 2350:0 2356:0 2357:0 '), +(2356,'Silithus65',1,1377,2738,50,60,0,-7183.12,437.593,64.3972,4.38322,'2353:0 2354:0 2355:0 '), +(2357,'Silithus66',1,1377,2738,50,60,0,-7342.29,338.615,9.9245,1.29662,'2349:0 2354:0 2355:0 192:0 '), +(2358,'Silithus67',1,1377,2744,53,60,0,-7815.08,208.412,5.58908,6.15627,'192:0 '), +(2359,'Silithus68',1,1377,2744,55,60,0,-7734.1,629.157,-47.1739,6.08166,'2349:0 2360:0 2361:0 2346:0 '), +(2360,'Silithus69',1,1377,2744,55,60,0,-7803.61,427.995,-36.2971,1.11402,'2359:0 2361:0 2362:0 '), +(2361,'Silithus70',1,1377,2744,55,60,0,-7937.51,488.881,-34.8964,5.632,'2359:0 2360:0 2365:0 2366:0 '), +(2362,'Silithus71',1,1377,2744,55,60,0,-7860.7,346.797,-48.3413,1.79141,'2360:0 2363:0 2364:0 '), +(2363,'Silithus72',1,1377,2744,53,60,0,-7708.29,262.213,-42.7965,5.31941,'2362:0 '), +(2364,'Silithus73',1,1377,2744,53,60,0,-7678.37,284.795,-37.2732,5.97131,'2362:0 '), +(2365,'Silithus74',1,1377,1377,53,60,0,-8294.16,376.133,-94.7165,0.44759,'2361:0 '), +(2366,'Silithus75',1,1377,2744,53,60,0,-8140.13,753.793,-85.1582,5.97285,'2361:0 2367:0 '), +(2367,'Silithus76',1,1377,1377,53,60,0,-8313.26,729.325,-74.3037,4.85955,'2366:0 '), +(2368,'AltarOfStorms2',0,46,255,48,56,1,-7605.65,-798.145,190.852,3.45765,'635:0 '), +(2369,'Darrowshire',0,139,2262,53,60,1,1416.41,-3653.3,82.0183,4.38389,'230:0 '), +(2370,'BLS4H',0,4,72,43,54,5,-11896.1,-3084.13,31.0889,0.713907,'855:0 '), +(2371,'TanarisSpawnAll',1,440,1940,44,50,1,-8319.94,-5071.72,4.11589,2.25351,'2207:0 '); diff --git a/sql/Bots/updates/world/2023_04_11_00_creature_template_npcbot_wander_nodes.sql b/sql/Bots/updates/world/2023_04_11_00_creature_template_npcbot_wander_nodes.sql new file mode 100644 index 000000000..907e4f27b --- /dev/null +++ b/sql/Bots/updates/world/2023_04_11_00_creature_template_npcbot_wander_nodes.sql @@ -0,0 +1,27 @@ +-- +DELETE FROM `creature_template_npcbot_wander_nodes` WHERE `id` IN (2372,2373,2374,2375,2376,2377,2378,2379,2380,2381,2382,2383,2384,2385,2386,2387,2388,2389,2390,2391,2392,2393,2394,2395); +INSERT INTO `creature_template_npcbot_wander_nodes`(`id`,`name`,`mapid`,`zoneid`,`areaid`,`minlevel`,`maxlevel`,`flags`,`x`,`y`,`z`,`o`,`links`) VALUES +(2372,'WarsongGulchSpawnA1',489,3277,4571,10,80,59,1523.81,1481.76,351.992,3.14159,'2373:0 2375:0 2378:0 2377:0 '), +(2373,'WarsongGulchAInside1',489,3277,3321,10,80,0,1488.03,1463.62,362.472,1.46358,'2372:0 2374:0 2379:0 '), +(2374,'WarsongGulchAInside2',489,3277,4571,10,80,32,1530.85,1468.31,362.665,3.14825,'2372:0 2373:0 '), +(2375,'WarsongGulchAInside3',489,3277,3321,10,80,0,1471.13,1454.77,342.795,6.27608,'2372:0 2376:0 2380:0 '), +(2376,'WarsongGulchAInside4',489,3277,3321,10,80,32,1523.32,1468.01,373.688,4.44809,'2374:0 2375:0 '), +(2377,'WarsongGulchAInside5',489,3277,3321,10,80,0,1439.11,1574.68,343.587,5.09406,'2381:0 2383:0 2372:0 '), +(2378,'WarsongGulchAInside6',489,3277,3321,10,80,0,1416.56,1540.82,342.735,5.58886,'2381:0 2383:0 2372:0 '), +(2379,'WarsongGulchAInside7',489,3277,3321,10,80,0,1360.6,1392.13,326.593,5.39054,'2381:0 2382:0 2373:0 '), +(2380,'WarsongGulchAInside8',489,3277,3321,10,80,0,1356.9,1461.83,324.512,0.0216269,'2381:0 2382:0 2383:0 2375:0 '), +(2381,'WarsongGulchMidC1',489,3277,3277,10,80,2,1231.42,1473.79,307.813,5.82692,'2391:0 2392:0 '), +(2382,'WarsongGulchMidE1',489,3277,3277,10,80,2,1225.27,1411.04,310.329,0.0703331,'2391:0 2392:0 '), +(2383,'WarsongGulchMidW1',489,3277,3277,10,80,2,1195.67,1530.47,306.785,6.16029,'2391:0 2392:0 '), +(2384,'WarsongGulchSpawnH1',489,3277,4572,10,80,61,929.191,1434.63,345.536,0.0577507,'2385:0 2387:0 2390:0 2389:0 '), +(2385,'WarsongGulchHInside1',489,3277,3320,10,80,0,965.729,1453.27,356.269,4.75445,'2384:0 2391:0 2386:0 '), +(2386,'WarsongGulchHInside2',489,3277,4572,10,80,32,925.907,1451.62,355.856,5.79629,'2384:0 2385:0 '), +(2387,'WarsongGulchHInside3',489,3277,3320,10,80,0,982.314,1463.02,335.964,1.72006,'2384:0 2392:0 2388:0 '), +(2388,'WarsongGulchHInside4',489,3277,3320,10,80,32,931.381,1448.91,367.604,1.1742,'2386:0 2387:0 '), +(2389,'WarsongGulchHInside5',489,3277,3320,10,80,0,1033.59,1363.21,342.852,2.24035,'2394:0 2395:0 2384:0 '), +(2390,'WarsongGulchHInside6',489,3277,3320,10,80,0,1060.56,1390.51,338.618,2.82356,'2394:0 2395:0 2384:0 '), +(2391,'WarsongGulchHInside7',489,3277,3320,10,80,0,1086.1,1540.21,316.792,2.79013,'2393:0 2394:0 2385:0 '), +(2392,'WarsongGulchHInside8',489,3277,3320,10,80,0,1119.21,1462.5,316.34,3.25941,'2393:0 2394:0 2395:0 2387:0 '), +(2393,'WarsongGulchMidW2',489,3277,3277,10,80,4,1255.33,1511.56,309.973,3.31243,'2379:0 2380:0 '), +(2394,'WarsongGulchMidC2',489,3277,3277,10,80,4,1217.19,1480.7,306.432,3.41258,'2379:0 2380:0 '), +(2395,'WarsongGulchMidE2',489,3277,3277,10,80,4,1263.56,1400.5,310.369,3.07681,'2379:0 2380:0 '); diff --git a/sql/Bots/updates/world/2023_04_16_00_creature_template_npcbot_wander_nodes.sql b/sql/Bots/updates/world/2023_04_16_00_creature_template_npcbot_wander_nodes.sql new file mode 100644 index 000000000..ecd225cab --- /dev/null +++ b/sql/Bots/updates/world/2023_04_16_00_creature_template_npcbot_wander_nodes.sql @@ -0,0 +1,27 @@ +-- +DELETE FROM `creature_template_npcbot_wander_nodes` WHERE `id` IN (2372,2373,2374,2375,2376,2377,2378,2379,2380,2381,2382,2383,2384,2385,2386,2387,2388,2389,2390,2391,2392,2393,2394,2395); +INSERT INTO `creature_template_npcbot_wander_nodes`(`id`,`name`,`mapid`,`zoneid`,`areaid`,`minlevel`,`maxlevel`,`flags`,`x`,`y`,`z`,`o`,`links`) VALUES +(2372,'WarsongGulchSpawnA1',489,3277,4571,10,80,251,1535.77,1481.37,352.109,0.188495,'2373:0 2375:0 2378:0 2377:0 '), +(2373,'WarsongGulchAInside1',489,3277,3321,10,80,0,1488.03,1463.62,362.472,1.46358,'2372:0 2374:0 2379:0 '), +(2374,'WarsongGulchAInside2',489,3277,4571,10,80,32,1530.85,1468.31,362.665,3.14825,'2372:0 2373:0 '), +(2375,'WarsongGulchAInside3',489,3277,3321,10,80,0,1471.13,1454.77,342.795,6.27608,'2372:0 2376:0 2380:0 '), +(2376,'WarsongGulchAInside4',489,3277,3321,10,80,32,1523.32,1468.01,373.688,4.44809,'2374:0 2375:0 '), +(2377,'WarsongGulchAInside5',489,3277,3321,10,80,0,1439.11,1574.68,343.587,5.09406,'2381:0 2383:0 2372:0 '), +(2378,'WarsongGulchAInside6',489,3277,3321,10,80,0,1416.56,1540.82,342.735,5.58886,'2381:0 2383:0 2372:0 '), +(2379,'WarsongGulchAInside7',489,3277,3321,10,80,0,1360.6,1392.13,326.593,5.39054,'2381:0 2382:0 2373:0 '), +(2380,'WarsongGulchAInside8',489,3277,3321,10,80,0,1356.9,1461.83,324.512,0.0216269,'2381:0 2382:0 2383:0 2375:0 '), +(2381,'WarsongGulchMidC1',489,3277,3277,10,80,0,1231.42,1473.79,307.813,5.82692,'2391:0 2392:0 '), +(2382,'WarsongGulchMidE1',489,3277,3277,10,80,0,1225.27,1411.04,310.329,0.0703331,'2391:0 2392:0 '), +(2383,'WarsongGulchMidW1',489,3277,3277,10,80,0,1195.67,1530.47,306.785,6.16029,'2391:0 2392:0 '), +(2384,'WarsongGulchSpawnH1',489,3277,4572,10,80,253,919.418,1434.62,345.695,3.13216,'2385:0 2387:0 2390:0 2389:0 '), +(2385,'WarsongGulchHInside1',489,3277,3320,10,80,0,965.729,1453.27,356.269,4.75445,'2384:0 2391:0 2386:0 '), +(2386,'WarsongGulchHInside2',489,3277,4572,10,80,32,925.907,1451.62,355.856,5.79629,'2384:0 2385:0 '), +(2387,'WarsongGulchHInside3',489,3277,3320,10,80,0,982.314,1463.02,335.964,1.72006,'2384:0 2392:0 2388:0 '), +(2388,'WarsongGulchHInside4',489,3277,3320,10,80,32,931.381,1448.91,367.604,1.1742,'2386:0 2387:0 '), +(2389,'WarsongGulchHInside5',489,3277,3320,10,80,0,1033.59,1363.21,342.852,2.24035,'2394:0 2395:0 2384:0 '), +(2390,'WarsongGulchHInside6',489,3277,3320,10,80,0,1060.56,1390.51,338.618,2.82356,'2394:0 2395:0 2384:0 '), +(2391,'WarsongGulchHInside7',489,3277,3320,10,80,0,1086.1,1540.21,316.792,2.79013,'2393:0 2394:0 2385:0 '), +(2392,'WarsongGulchHInside8',489,3277,3320,10,80,0,1119.21,1462.5,316.34,3.25941,'2393:0 2394:0 2395:0 2387:0 '), +(2393,'WarsongGulchMidW2',489,3277,3277,10,80,0,1255.33,1511.56,309.973,3.31243,'2379:0 2380:0 '), +(2394,'WarsongGulchMidC2',489,3277,3277,10,80,0,1217.19,1480.7,306.432,3.41258,'2379:0 2380:0 '), +(2395,'WarsongGulchMidE2',489,3277,3277,10,80,0,1263.56,1400.5,310.369,3.07681,'2379:0 2380:0 '); diff --git a/sql/Bots/updates/world/2023_04_18_00_creature_template_npcbot_appearance.sql b/sql/Bots/updates/world/2023_04_18_00_creature_template_npcbot_appearance.sql new file mode 100644 index 000000000..09018660d --- /dev/null +++ b/sql/Bots/updates/world/2023_04_18_00_creature_template_npcbot_appearance.sql @@ -0,0 +1,2 @@ +-- +DELETE FROM `creature_template_npcbot_appearance` WHERE (`entry`='70266'); diff --git a/sql/Bots/updates/world/2023_04_22_00_creature_template_npcbot_wander_nodes.sql b/sql/Bots/updates/world/2023_04_22_00_creature_template_npcbot_wander_nodes.sql new file mode 100644 index 000000000..a3aa43425 --- /dev/null +++ b/sql/Bots/updates/world/2023_04_22_00_creature_template_npcbot_wander_nodes.sql @@ -0,0 +1,25 @@ +-- +DELETE FROM `creature_template_npcbot_wander_nodes` WHERE `id` IN (2396,2397,2398,2399,2400,2401,2402,2403,2404,2405,2406,2407,2408,2409,2410,2411,2412,2413,2414,2415,2416,2417,2418,2419,2420,2421,2422,2423,2424,2425,2426,2427,2428,2429,2430,2431,2432,2433,2434,2435,2436); +INSERT INTO `creature_template_npcbot_wander_nodes`(`id`,`name`,`mapid`,`zoneid`,`areaid`,`minlevel`,`maxlevel`,`flags`,`x`,`y`,`z`,`o`,`links`) VALUES +(2396,'ArathiBasinSpawnA',529,3358,3417,20,80,3,1313.9,1310.74,-9.01043,3.83874,'2397:0 '), +(2397,'ArathiBasinStartA',529,3358,3358,20,80,0,1241.32,1228.52,-40.2271,4.12149,'2411:0 2398:0 '), +(2398,'ArathiBasinStables',529,3358,3424,20,80,128,1168.45,1204.15,-56.582,6.24992,'2397:0 2399:0 '), +(2399,'ArathiBasinStablesBridge',529,3358,3358,20,80,0,1088.6,1223.56,-53.4783,0.143443,'2400:0 2398:0 2403:0 '), +(2400,'ArathiBasinMillLow1',529,3358,3358,20,80,0,988.277,1179.75,-50.2717,5.04236,'2399:0 2401:0 2416:0 '), +(2401,'ArathiBasinMillLow2',529,3358,3358,20,80,0,890.451,1109.94,-56.335,4.04097,'2400:0 2402:0 '), +(2402,'ArathiBasinMillLow3',529,3358,3358,20,80,0,845.513,978.663,-60.2139,0.977914,'2401:0 2408:0 2415:0 '), +(2403,'ArathiBasinMillUp1',529,3358,3358,20,80,0,939.139,1227.33,-23.8137,3.6404,'2399:0 2404:0 '), +(2404,'ArathiBasinMill',529,3358,3422,20,80,136,852.619,1151.8,11.5342,0.752077,'2405:0 2403:0 '), +(2405,'ArathiBasinMillUp2',529,3358,3422,20,80,0,783.565,1047.78,-26.1072,1.57478,'2404:0 2408:0 '), +(2406,'ArathiBasinSpawnH',529,3358,3418,20,80,5,684.071,681.281,-12.9148,0.836507,'2407:0 '), +(2407,'ArathiBasinStartH',529,3358,3358,20,80,0,748.646,759.099,-41.5104,0.818832,'2409:0 2408:0 '), +(2408,'ArathiBasinFarm',529,3358,3420,20,80,128,809.5,872.547,-56.7509,1.20759,'2405:0 2407:0 2402:0 '), +(2409,'ArathiBasinFarmBridge',529,3358,3420,20,80,0,864.499,771.902,-51.7236,0.695112,'2407:0 2410:0 '), +(2410,'ArathiBasinMineForkSE',529,3358,3358,20,80,0,956.739,804.522,-61.6094,0.524283,'2409:0 2412:0 2414:0 '), +(2411,'ArathiBasinMineForkNW',529,3358,3358,20,80,0,1221.3,1088.37,-59.7666,4.47091,'2412:0 2397:0 2413:0 '), +(2412,'ArathiBasinMine',529,3358,3423,20,80,136,1151.72,846.381,-110.525,2.55257,'2411:0 2410:0 '), +(2413,'ArathiBasinMineUp1',529,3358,3358,20,80,0,1156.61,991.63,-63.6154,2.3307,'2411:0 2414:0 2416:0 '), +(2414,'ArathiBasinMineUp2',529,3358,3358,20,80,0,1040.93,882.871,-61.8051,2.33458,'2410:0 2415:0 2413:0 '), +(2415,'ArathiBasinBlacksmithForkS',529,3358,3421,20,80,0,922.673,999.161,-55.5623,5.49188,'2402:0 2414:0 2417:0 2416:0 '), +(2416,'ArathiBasinBlacksmithForkN',529,3358,3421,20,80,0,1050.68,999.89,-50.4064,5.62341,'2400:0 2413:0 2415:0 2417:0 '), +(2417,'ArathiBasinBlacksmith',529,3358,3421,20,80,136,979.258,1043.34,-44.3749,0.106003,'2415:0 2416:0 '); diff --git a/sql/Bots/updates/world/2023_04_28_00_creature_template.sql b/sql/Bots/updates/world/2023_04_28_00_creature_template.sql new file mode 100644 index 000000000..460bf90f4 --- /dev/null +++ b/sql/Bots/updates/world/2023_04_28_00_creature_template.sql @@ -0,0 +1,3 @@ +-- +UPDATE `creature_template` SET `flags_extra` = (`flags_extra` | 0x02000000) WHERE (`flags_extra` & 0x0A000000) = 0x08000000 AND `entry` > 70000; +UPDATE `creature_template` SET `flags_extra` = (`flags_extra` | 0x0A000000) WHERE (`flags_extra` & 0x0E000000) = 0x04000000 AND `entry` > 70000; diff --git a/sql/Bots/updates/world/2023_05_14_00_creature_template_npcbot_wander_nodes.sql b/sql/Bots/updates/world/2023_05_14_00_creature_template_npcbot_wander_nodes.sql new file mode 100644 index 000000000..d6ae832db --- /dev/null +++ b/sql/Bots/updates/world/2023_05_14_00_creature_template_npcbot_wander_nodes.sql @@ -0,0 +1,2 @@ +-- +UPDATE `creature_template_npcbot_wander_nodes` SET `minlevel`='23',`flags`='1' WHERE (`id`='753'); diff --git a/sql/Bots/updates/world/2023_05_16_00_npc_text.sql b/sql/Bots/updates/world/2023_05_16_00_npc_text.sql new file mode 100644 index 000000000..23a490109 --- /dev/null +++ b/sql/Bots/updates/world/2023_05_16_00_npc_text.sql @@ -0,0 +1,13 @@ +-- +SET @BOT_START = 70661; +SET @BOT_END = 70666; + +DELETE FROM `npc_text` WHERE `ID` BETWEEN @BOT_START AND @BOT_END; + +INSERT INTO `npc_text` (`ID`,`text0_0`,`VerifiedBuild`) VALUES +(@BOT_START+0, "Bot gear bank...", -1), +(@BOT_START+1, "Deposit items...", -1), +(@BOT_START+2, "Withdraw items...", -1), +(@BOT_START+3, "Bank is empty", -1), +(@BOT_START+4, "Previous page", -1), +(@BOT_START+5, "Next page", -1); diff --git a/sql/Bots/updates/world/2023_05_20_00_creature_template.sql b/sql/Bots/updates/world/2023_05_20_00_creature_template.sql new file mode 100644 index 000000000..116e98f54 --- /dev/null +++ b/sql/Bots/updates/world/2023_05_20_00_creature_template.sql @@ -0,0 +1,24 @@ +-- +SET @BOT_START = 70587; +SET @BOT_END = 70595; + +DELETE FROM `creature_template` WHERE `entry` BETWEEN @BOT_START AND @BOT_END; + +INSERT INTO `creature_template` +(`entry`,`difficulty_entry_1`,`difficulty_entry_2`,`difficulty_entry_3`,`KillCredit1`,`KillCredit2`, +`modelid1`,`modelid2`,`modelid3`,`modelid4`,`name`,`subname`,`IconName`,`gossip_menu_id`,`minlevel`,`maxlevel`,`exp`, +`faction`,`npcflag`,`speed_walk`,`speed_run`,`scale`,`rank`,`dmgschool`,`BaseAttackTime`,`RangeAttackTime`, +`BaseVariance`,`RangeVariance`,`unit_class`,`unit_flags`,`unit_flags2`,`dynamicflags`,`family`,`type`,`type_flags`,`lootid`, +`pickpocketloot`,`skinloot`,`PetSpellDataId`,`VehicleId`,`mingold`,`maxgold`,`AIName`,`MovementType`, +`HoverHeight`,`HealthModifier`,`ManaModifier`,`ArmorModifier`,`DamageModifier`,`ExperienceModifier`,`RacialLeader`,`movementId`, +`RegenHealth`,`mechanic_immune_mask`,`spell_school_immune_mask`,`flags_extra`,`ScriptName`,`VerifiedBuild`) +VALUES +('70587','0','0','0','0','0','27395','0','0','0','Tuten\'arak','Crypt Lord Bot','','0','83','83','2','35','1','1.1','1.1','0.5','2','0','1900','1900','1','1','1','0','32','0','0','6','4096','0','0','0','0','0','0','0','','0','1','1','1','1','1','1','0','0','0','0','0','235929712','crypt_lord_bot','-1'), +('70588','0','0','0','0','0','27395','0','0','0','Anubiros','Crypt Lord Bot','','0','83','83','2','35','1','1.1','1.1','0.5','2','0','1900','1900','1','1','1','0','32','0','0','6','4096','0','0','0','0','0','0','0','','0','1','1','1','1','1','1','0','0','0','0','0','235929712','crypt_lord_bot','-1'), +('70589','0','0','0','0','0','27395','0','0','0','Nephri\'thos','Crypt Lord Bot','','0','83','83','2','35','1','1.1','1.1','0.5','2','0','1900','1900','1','1','1','0','32','0','0','6','4096','0','0','0','0','0','0','0','','0','1','1','1','1','1','1','0','0','0','0','0','235929712','crypt_lord_bot','-1'), +('70590','0','0','0','0','0','27395','0','0','0','Arak-arahm','Crypt Lord Bot','','0','83','83','2','35','1','1.1','1.1','0.5','2','0','1900','1900','1','1','1','0','32','0','0','6','4096','0','0','0','0','0','0','0','','0','1','1','1','1','1','1','0','0','0','0','0','235929712','crypt_lord_bot','-1'), +('70591','0','0','0','0','0','27395','0','0','0','Horus\'aman','Crypt Lord Bot','','0','83','83','2','35','1','1.1','1.1','0.5','2','0','1900','1900','1','1','1','0','32','0','0','6','4096','0','0','0','0','0','0','0','','0','1','1','1','1','1','1','0','0','0','0','0','235929712','crypt_lord_bot','-1'), +('70592','0','0','0','0','0','11094','0','0','0','Carrion Beetle','','','0','83','83','2','35','0','1.05','1.05','0.5','0','0','1500','1500','1','1','1','0','0','0','0','6','4096','0','0','0','0','0','0','0','','0','1','1','1','1','1','1','0','0','0','0','0','168820832','crypt_lord_pet_bot','-1'), +('70593','0','0','0','0','0','11094','0','0','0','Carrion Beetle','','','0','83','83','2','35','0','1.05','1.05','0.7','0','0','1500','1500','1','1','1','0','0','0','0','6','4096','0','0','0','0','0','0','0','','0','1','1','1','1','1','1','0','0','0','0','0','168820832','crypt_lord_pet_bot','-1'), +('70594','0','0','0','0','0','11094','0','0','0','Carrion Beetle','','','0','83','83','2','35','0','1.05','1.05','0.9','0','0','1500','1500','1','1','1','0','0','0','0','6','4096','0','0','0','0','0','0','0','','0','1','1','1','1','1','1','0','0','0','0','0','168820832','crypt_lord_pet_bot','-1'), +('70595','0','0','0','0','0','19252','0','0','0','Locust','','','0','83','83','2','35','0','1.4','1.4','0.25','1','0','1200','1200','1','1','1','33554432','0','0','0','6','4096','0','0','0','0','0','0','0','','0','2','1','1','1','1','1','0','0','0','0','0','704782368','crypt_lord_pet_bot','-1'); diff --git a/sql/Bots/updates/world/2023_05_20_01_creature_equip_template.sql b/sql/Bots/updates/world/2023_05_20_01_creature_equip_template.sql new file mode 100644 index 000000000..f852a3cb2 --- /dev/null +++ b/sql/Bots/updates/world/2023_05_20_01_creature_equip_template.sql @@ -0,0 +1,13 @@ +-- +SET @BOT_START = 70587; +SET @BOT_END = 70591; + +DELETE FROM `creature_equip_template` WHERE `CreatureID` BETWEEN @BOT_START AND @BOT_END; +INSERT INTO `creature_equip_template` +(`CreatureID`,`ID`,`VerifiedBuild`) +VALUES +('70587','1','-1'), +('70588','1','-1'), +('70589','1','-1'), +('70590','1','-1'), +('70591','1','-1'); diff --git a/sql/Bots/updates/world/2023_05_20_02_creature_template_npcbot_extras.sql b/sql/Bots/updates/world/2023_05_20_02_creature_template_npcbot_extras.sql new file mode 100644 index 000000000..45a25bd76 --- /dev/null +++ b/sql/Bots/updates/world/2023_05_20_02_creature_template_npcbot_extras.sql @@ -0,0 +1,15 @@ +-- +SET @BOT_START = 70587; +SET @BOT_END = 70595; + +DELETE FROM `creature_template_npcbot_extras` WHERE `entry` BETWEEN @BOT_START AND @BOT_END; +INSERT INTO `creature_template_npcbot_extras` (`entry`,`class`,`race`) VALUES +('70587', '20', '15'), +('70588', '20', '15'), +('70589', '20', '15'), +('70590', '20', '15'), +('70591', '20', '15'), +('70592', '0', '15'), +('70593', '0', '15'), +('70594', '0', '15'), +('70595', '0', '15'); diff --git a/sql/Bots/updates/world/2023_05_20_03_npc_text.sql b/sql/Bots/updates/world/2023_05_20_03_npc_text.sql new file mode 100644 index 000000000..34f28dbce --- /dev/null +++ b/sql/Bots/updates/world/2023_05_20_03_npc_text.sql @@ -0,0 +1,27 @@ +-- +SET @LOCALIZED_STRINGS_START = 70012; +SET @LOCALIZED_STRINGS_END = 70013; + +DELETE FROM `npc_text` WHERE ID BETWEEN @LOCALIZED_STRINGS_START and @LOCALIZED_STRINGS_END; +INSERT INTO `npc_text` (`ID`,`text0_0`,`VerifiedBuild`) VALUES +(70012,'$B$BWhat will it be, tiny creature? Your flesh will do like any other...','-1'), +(70013,'I consume the living and the dead.','-1'); + +SET @LOCALIZED_STRINGS_START = 70109; +SET @LOCALIZED_STRINGS_END = 70109; + +DELETE FROM `npc_text` WHERE ID BETWEEN @LOCALIZED_STRINGS_START and @LOCALIZED_STRINGS_END; +INSERT INTO `npc_text` (`ID`,`text0_0`,`VerifiedBuild`) VALUES +(70109,'|cff9900ccCrypt Lord|r$b|cffdd6600-=Warcraft III tribute=-|r$B$B\"Ancient behemoth, once one of the kings of Azjol-Nerub, now an undead monster within ranks of Lich King\'s mightiest warriors\".$B$BVery high armor, increased resistances, partially immune to control effects, immune to poison-based effects, mail/plate armor, deals melee/spellshadow damage, spell power bonus: 200% strength. Main attribute: Strength.$B$BImpale. Crypt Lord slams the ground with his massive claws, shooting spikes out in a frontal cone, dealing damage and hurling enemy units into the air in their wake, stunning them. Unlocked at level 20.$B$BSpiked Carapace. Crypt Lord\'s chitinous armor increases damage resistance and returns 15% to 50% damage to enemy melee attackers.$B$BCarrion Beetles. Crypt Lord progenerates a Carrion Beetle from a fresh corpse of an enemy to attack his enemies. Beetles are permanent but do not regenerate health and only 6 can be controlled at a time. Higher levels allow Crypt Lord to summon more powerful beetles. Unlocked at level 10.$B$BLocust Swarm. Crypt Lord releases a swarm of 20-40 (depends on Crypt Lord\'s level) angry locusts that bite and tear at nearby enemy units, reducing their ability to move or attack. As they chew the enemy flesh, they convert it into a substance that restores hit points to the Crypt Lord when they return. Unlocked at level 40.$B$B',-1); + +SET @LOCALIZED_STRINGS_START = 70667; +SET @LOCALIZED_STRINGS_END = 70672; + +DELETE FROM `npc_text` WHERE ID BETWEEN @LOCALIZED_STRINGS_START and @LOCALIZED_STRINGS_END; +INSERT INTO `npc_text` (`ID`,`text0_0`,`VerifiedBuild`) VALUES +(70667,'Do you really want to spend all this money to make Crypt Lord move again?','-1'), +(70668,'I doubt your ability to do much harm in your current state, but I am willing to lead you and help you restore your powers.','-1'), +(70669,'Crypt Lords','-1'), +(70670,'Crypt Lord','-1'), +(70671,'Reflect','-1'), +(70672,'Locusts','-1'); diff --git a/sql/Bots/updates/world/2023_06_02_00_creature_template_npcbot_extras.sql b/sql/Bots/updates/world/2023_06_02_00_creature_template_npcbot_extras.sql new file mode 100644 index 000000000..778d13d7a --- /dev/null +++ b/sql/Bots/updates/world/2023_06_02_00_creature_template_npcbot_extras.sql @@ -0,0 +1,2 @@ +-- +UPDATE `creature_template_npcbot_wander_nodes` SET `flags`='1' WHERE (`id`='283'); diff --git a/sql/Bots/updates/world/2023_06_03_00_creature_template.sql b/sql/Bots/updates/world/2023_06_03_00_creature_template.sql new file mode 100644 index 000000000..c55f7313b --- /dev/null +++ b/sql/Bots/updates/world/2023_06_03_00_creature_template.sql @@ -0,0 +1,2 @@ +-- +UPDATE `creature_template` SET `flags_extra`=(`flags_extra`|0x80000000) WHERE `entry` > 70000 AND (`flags_extra`&0x8E000000) IN (0xE000000, 0xA000000); diff --git a/sql/Bots/updates/world/2023_06_09_00_creature_template_npcbot_wander_nodes.sql b/sql/Bots/updates/world/2023_06_09_00_creature_template_npcbot_wander_nodes.sql new file mode 100644 index 000000000..725842fa3 --- /dev/null +++ b/sql/Bots/updates/world/2023_06_09_00_creature_template_npcbot_wander_nodes.sql @@ -0,0 +1,2627 @@ +-- +SET @WP_START = 2418; +SET @WP_END = 5038; + +DELETE FROM `creature_template_npcbot_wander_nodes` WHERE `id` BETWEEN @WP_START AND @WP_END; +INSERT INTO `creature_template_npcbot_wander_nodes`(`id`,`name`,`mapid`,`zoneid`,`areaid`,`minlevel`,`maxlevel`,`flags`,`x`,`y`,`z`,`o`,`links`) VALUES +('2418','hellfire001','530','3483','3804','60','63','0','-88.1661','1757.73','61.4158','2.30551','2419:0 2422:0 '), +('2419','hellfire002','530','3483','3804','60','63','0','-130.83','1837.76','78.4899','5.22012','2418:0 2420:0 2422:0 '), +('2420','hellfire003','530','3483','3542','60','63','0','-228.144','1920.56','96.8968','1.90495','2419:0 2421:0 2422:0 '), +('2421','hellfire004','530','3483','3542','60','63','1','-207.846','2150.92','80.3185','1.58606','2420:0 2442:0 2453:0 '), +('2422','hellfire005','530','3483','3804','60','63','0','-320.252','1725.48','60.1535','0.383608','2418:0 2419:0 2420:0 2423:0 '), +('2423','hellfire006','530','3483','3804','60','63','0','-443.68','1809.38','64.2507','6.17593','2422:0 2424:0 '), +('2424','hellfire007','530','3483','3815','60','63','0','-614.482','1855.41','72.3221','2.91339','2423:0 2425:0 '), +('2425','hellfire008','530','3483','3483','60','63','0','-772.087','1924.89','51.3142','3.69486','2424:0 2426:0 2427:0 '), +('2426','hellfire009','530','3483','3582','60','63','0','-890.066','1889.54','71.6232','2.99193','2425:0 '), +('2427','hellfire010','530','3483','3483','60','63','0','-665.15','2133.08','41.0304','6.09818','2425:0 2428:0 '), +('2428','hellfire011','530','3483','3483','60','63','0','-830.651','2170.3','10.6442','6.00392','2427:0 2429:0 '), +('2429','hellfire012','530','3483','3582','60','63','0','-1044.48','2221.12','18.1881','2.68955','2428:0 2430:0 2432:0 '), +('2430','hellfire013','530','3483','3582','60','63','0','-1060.21','2075.54','65.7492','1.53579','2429:0 2431:0 '), +('2431','hellfire014','530','3483','3582','60','63','0','-1136.98','1968.35','74.7199','4.3593','2430:0 '), +('2432','hellfire015','530','3483','3483','60','63','0','-1116.71','2366.85','26.2','1.55936','2429:0 2433:0 '), +('2433','hellfire016','530','3483','3483','60','63','0','-1209.75','2517.59','48.9283','1.97249','2432:0 '), +('2434','hellfire017','530','3483','3483','60','63','0','-927.061','2680.73','24.4655','4.225','2435:0 2438:0 '), +('2435','hellfire018','530','3483','3483','60','63','0','-991.176','2930.18','4.1955','4.89571','2434:0 2458:0 2459:0 '), +('2436','hellfire019','530','3483','3483','60','63','0','-652.576','2459.81','56.8494','0.00658825','2437:0 2438:0 '), +('2437','hellfire020','530','3483','3483','60','63','0','-382.816','2594.23','50.224','3.72781','2436:0 '), +('2438','hellfire021','530','3483','3538','60','63','3','-721.67','2675.81','95.7509','0.770785','2434:0 2436:0 '), +('2439','hellfire022','530','3483','3483','60','63','0','-449.715','2925.64','21.2132','0.32312','2440:0 '), +('2440','hellfire023','530','3483','3483','60','63','0','-593.071','2998.53','15.8438','0.017592','2439:0 2441:0 '), +('2441','hellfire024','530','3483','3483','60','63','0','-749.912','3117.85','4.88152','5.75179','2440:0 2458:0 2499:0 '), +('2442','hellfire025','530','3483','3483','60','63','0','-80.7943','2320.17','65.6333','4.50299','2421:0 2443:0 2446:0 2453:0 '), +('2443','hellfire026','530','3483','3483','60','63','0','18.4377','2469.36','53.639','4.22811','2442:0 2444:0 2446:0 '), +('2444','hellfire027','530','3483','3536','60','63','4','33.0662','2661.07','75.7738','3.89274','2443:0 2445:0 '), +('2445','hellfire028','530','3483','3536','60','63','5','221.743','2789.93','121.175','3.98307','2444:0 2496:0 '), +('2446','hellfire029','530','3483','3793','60','63','0','158.425','2334.4','54.1841','5.04414','2442:0 2443:0 2447:0 '), +('2447','hellfire030','530','3483','3793','60','63','0','270.62','2240.47','56.9978','2.96989','2446:0 2448:0 '), +('2448','hellfire031','530','3483','3793','60','63','0','255.635','2040.2','31.817','2.78925','2447:0 2449:0 '), +('2449','hellfire032','530','3483','3483','60','63','0','446.912','2049.26','99.1622','3.3783','2448:0 2450:0 '), +('2450','hellfire033','530','3483','3483','60','63','0','442.066','2351.62','123.026','4.69385','2449:0 2451:0 '), +('2451','hellfire034','530','3483','3483','60','63','0','438.394','2661.09','177.718','4.90591','2450:0 2452:0 '), +('2452','hellfire035','530','3483','3549','60','63','0','549.636','2796.65','217.8','4.31374','2451:0 '), +('2453','hellfire036','530','3483','3542','60','63','0','-220.959','2363','37.4537','1.6049','2421:0 2442:0 2454:0 '), +('2454','hellfire037','530','3483','3542','60','63','0','-248.594','2648.76','-11.9763','4.88786','2453:0 2455:0 '), +('2455','hellfire038','530','3483','3542','60','63','0','-270.052','2895.86','-55.0992','4.86824','2454:0 2456:0 2457:0 '), +('2456','hellfire039','530','3483','3545','60','63','0','-371.638','3039.8','-63.8946','5.60259','2455:0 '), +('2457','hellfire040','530','3483','3545','60','63','0','-243.298','3044.75','-65.4235','4.48185','2455:0 '), +('2458','hellfire041','530','3483','3483','60','63','0','-930.007','3088.24','16.9479','0.133084','2435:0 2441:0 2459:0 2466:0 '), +('2459','hellfire042','530','3483','3483','60','63','0','-1233.86','3071.59','29.2379','5.81622','2458:0 2435:0 2460:0 2462:0 '), +('2460','hellfire043','530','3483','3796','60','63','0','-1370.5','2890.29','-26.0325','3.35557','2459:0 2461:0 '), +('2461','hellfire044','530','3483','3546','60','63','0','-1257.98','2677.11','1.84108','2.88824','2460:0 '), +('2462','hellfire045','530','3483','3483','60','63','0','-1343.14','3305.78','47.482','2.24265','2459:0 2463:0 '), +('2463','hellfire046','530','3483','3765','60','63','0','-1519.57','3487.42','30.3847','2.18217','2462:0 2464:0 '), +('2464','hellfire047','530','3483','3765','60','63','0','-1621.55','3645.56','31.4811','1.74235','2463:0 2465:0 '), +('2465','hellfire048','530','3483','3765','60','63','0','-1680.38','3854.3','36.4441','1.97326','2464:0 2784:0 '), +('2466','hellfire049','530','3483','3483','60','63','0','-1003.35','3339.19','79.143','4.83211','2458:0 2467:0 '), +('2467','hellfire050','530','3483','3483','60','63','0','-866.12','3482.05','93.6018','3.97209','2466:0 2468:0 '), +('2468','hellfire051','530','3483','3543','60','63','0','-842.626','3645.6','31.4407','5.28056','2467:0 2469:0 '), +('2469','hellfire052','530','3483','3543','60','63','0','-578.854','3725.85','28.9962','3.4176','2468:0 2470:0 '), +('2470','hellfire053','530','3483','3543','60','63','0','-629.058','3954.17','28.9952','4.88236','2469:0 2471:0 2472:0 2474:0 '), +('2471','hellfire054','530','3483','3483','60','63','0','-503.474','4084.93','51.2172','4.03729','2470:0 2474:0 2481:0 '), +('2472','hellfire055','530','3483','3543','60','63','0','-841.235','4095.51','32.9704','5.79579','2470:0 2473:0 2474:0 2477:0 '), +('2473','hellfire056','530','3483','3483','60','63','0','-950.497','4184.92','29.5337','3.20635','2472:0 2475:0 2477:0 '), +('2474','hellfire057','530','3483','3554','60','63','5','-659.228','4167.05','67.8382','3.41684','2470:0 2471:0 2472:0 2481:0 '), +('2475','hellfire058','530','3483','3556','60','63','0','-1174.94','4209.88','20.9412','0.188851','2473:0 2476:0 '), +('2476','hellfire059','530','3483','3798','60','63','0','-1287.84','4075.76','93.8567','5.93954','2475:0 '), +('2477','hellfire060','530','3483','3483','60','63','0','-1036.95','4000.75','83.0333','2.90005','2472:0 2473:0 2478:0 '), +('2478','hellfire061','530','3483','3483','60','63','0','-891.162','4017.27','84.496','3.16788','2477:0 2479:0 '), +('2479','hellfire062','530','3483','3543','60','63','0','-799.397','3955.75','91.0415','2.42568','2478:0 2480:0 '), +('2480','hellfire063','530','3483','3483','60','63','0','-766.496','3789.21','113.362','1.77223','2479:0 '), +('2481','hellfire064','530','3483','3483','60','63','0','-593.759','4335.47','52.0515','4.60437','2474:0 2471:0 2482:0 '), +('2482','hellfire065','530','3483','3483','60','63','0','-336.014','4367.15','59.658','3.93679','2481:0 2483:0 2509:0 '), +('2483','hellfire066','530','3483','3483','60','63','0','-202.4','4447.51','42.6762','3.81506','2482:0 2484:0 2506:0 2509:0 '), +('2484','hellfire067','530','3483','3483','60','63','0','-100.749','4241.25','83.9687','2.24269','2483:0 2485:0 2487:0 2489:0 2505:0 '), +('2485','hellfire068','530','3483','3552','60','63','2','63.0268','4335.15','96.8814','6.21365','2484:0 2486:0 2487:0 '), +('2486','hellfire069','530','3483','3552','60','63','3','215.545','4332.38','119.241','2.95661','2485:0 '), +('2487','hellfire070','530','3483','3483','60','63','0','39.4389','4153.06','71.524','1.85939','2484:0 2485:0 2488:0 2489:0 2505:0 '), +('2488','hellfire071','530','3483','3483','60','63','0','150.282','3945.51','79.661','2.2835','2487:0 2489:0 2515:0 '), +('2489','hellfire072','530','3483','3483','60','63','0','-96.2588','3960.84','99.7364','0.521852','2484:0 2488:0 2487:0 2490:0 2505:0 '), +('2490','hellfire073','530','3483','3669','60','63','0','-120.509','3779.67','72.1233','1.75885','2489:0 2491:0 '), +('2491','hellfire074','530','3483','3553','60','63','0','-61.6848','3514.21','73.5858','5.1667','2490:0 2492:0 2494:0 2498:0 '), +('2492','hellfire075','530','3483','3553','60','63','0','318.42','3476.5','61.5198','2.95581','2491:0 2493:0 '), +('2493','hellfire076','530','3483','3553','60','63','0','383.974','3310.89','74.522','1.91516','2492:0 '), +('2494','hellfire077','530','3483','3483','60','63','0','-44.2798','3288.99','27.2629','1.47534','2491:0 2495:0 2498:0 '), +('2495','hellfire078','530','3483','3483','60','63','0','-43.1353','3087.28','-1.55873','1.59942','2494:0 2496:0 '), +('2496','hellfire079','530','3483','3483','60','63','0','312.814','2983.21','19.5651','2.81914','2445:0 2495:0 2497:0 '), +('2497','hellfire080','530','3483','3799','60','63','0','459.167','3040.66','17.0606','3.687','2496:0 '), +('2498','hellfire081','530','3483','3670','60','63','0','-182.907','3456.14','39.4587','0.671851','2491:0 2494:0 '), +('2499','hellfire082','530','3483','3671','60','63','0','-567.836','3269.14','15.2208','4.86979','2441:0 2500:0 '), +('2500','hellfire083','530','3483','3671','60','63','0','-549.666','3476.52','45.226','4.64597','2499:0 2501:0 2502:0 '), +('2501','hellfire084','530','3483','3671','60','63','0','-445.867','3462.18','37.6671','2.82071','2500:0 '), +('2502','hellfire085','530','3483','3669','60','63','0','-452.212','3658.72','45.9024','0.816375','2500:0 2503:0 '), +('2503','hellfire086','530','3483','3483','60','63','0','-378.049','3871.34','62.3491','4.48419','2502:0 2504:0 '), +('2504','hellfire087','530','3483','3669','60','63','0','-294.007','3742.96','51.9568','2.11386','2503:0 2505:0 '), +('2505','hellfire088','530','3483','3483','60','63','0','-203.95','4100.77','98.2207','5.35677','2484:0 2487:0 2489:0 2504:0 '), +('2506','hellfire089','530','3483','3483','60','63','1','-249.051','4768.73','14.6773','1.56722','2483:0 2507:0 2508:0 2509:0 2512:0 2513:0 '), +('2507','hellfire090','530','3483','3797','60','63','0','13.8026','4691.45','41.6534','6.21284','2506:0 2513:0 '), +('2508','hellfire091','530','3483','3551','60','63','0','-556.09','4800.17','34.169','3.11602','2506:0 2509:0 2512:0 '), +('2509','hellfire092','530','3483','3483','60','63','0','-412.14','4578.53','39.0585','2.37774','2506:0 2482:0 2483:0 2508:0 2512:0 '), +('2510','hellfire093','530','3483','3800','60','63','0','-259.551','5042.36','65.21','1.36455','2511:0 2517:0 '), +('2511','hellfire094','530','3483','3795','60','63','0','-403.556','4975.22','40.4154','0.508468','2510:0 2512:0 '), +('2512','hellfire095','530','3483','3795','60','63','0','-417.252','4788.73','19.9639','2.94399','2509:0 2506:0 2508:0 2511:0 '), +('2513','hellfire096','530','3483','3797','60','63','0','-68.76','4846.8','50.753','4.86823','2506:0 2507:0 2514:0 '), +('2514','hellfire097','530','3483','3797','60','63','0','173.831','4854.22','77.6594','3.44272','2513:0 '), +('2515','hellfire098','530','3483','3483','60','63','0','335.776','4014.33','100.063','3.83622','2488:0 2516:0 '), +('2516','hellfire099','530','3483','3483','60','63','0','353.816','3871.24','144.979','1.47611','2515:0 '), +('2517','hellfire100','530','3483','3800','60','63','0','-234.311','5165.64','83.9852','4.49598','2510:0 2518:0 '), +('2518','zangar101','530','3521','3521','61','64','0','-271.021','5303.67','41.8812','4.77871','2517:0 2519:0 '), +('2519','zangar102','530','3521','3565','61','64','1','-220.592','5436.56','21.669','5.30379','2518:0 2520:0 2521:0 '), +('2520','zangar103','530','3521','3521','61','64','0','-84.4992','5395.31','22.3401','3.21027','2519:0 2659:0 '), +('2521','zangar104','530','3521','3521','61','64','0','-395.049','5385.52','18.5881','6.06676','2519:0 2522:0 '), +('2522','zangar105','530','3521','3521','61','64','0','-535.462','5435.61','20.9825','5.81938','2521:0 2523:0 '), +('2523','zangar106','530','3521','3521','61','64','0','-672.658','5367.99','22.9724','0.237551','2522:0 2524:0 '), +('2524','zangar107','530','3521','3641','61','64','0','-758.891','5282.57','17.6121','1.72588','2523:0 2525:0 '), +('2525','zangar108','530','3521','3641','61','64','0','-813.122','5198.88','19.1746','1.05202','2524:0 2526:0 '), +('2526','zangar109','530','3521','3641','61','64','0','-976.189','5260.97','19.0927','6.05108','2525:0 2527:0 2672:0 '), +('2527','zangar110','530','3521','3521','61','64','0','-934.208','5419.78','22.8095','4.46851','2526:0 2528:0 2672:0 '), +('2528','zangar111','530','3521','3521','61','64','0','-966.5','5606.91','22.0137','4.97508','2527:0 2529:0 '), +('2529','zangar112','530','3521','3521','61','64','0','-912.293','5682.09','21.6937','5.08504','2528:0 2530:0 '), +('2530','zangar113','530','3521','3521','61','64','0','-913.195','5896.44','20.1725','4.59652','2529:0 2531:0 2534:0 '), +('2531','zangar114','530','3521','3521','61','64','0','-909.682','6045.49','19.7314','4.59651','2530:0 2532:0 2534:0 2535:0 '), +('2532','zangar115','530','3521','3521','61','64','0','-1037.12','6091.11','19.5931','5.89242','2531:0 2533:0 '), +('2533','zangar116','530','3521','3521','61','64','0','-1163.58','6201.43','53.0219','5.61282','2532:0 2671:0 '), +('2534','zangar117','530','3521','3841','61','64','0','-796.58','5918.23','22.3409','2.31415','2530:0 2531:0 2535:0 '), +('2535','zangar118','530','3521','3521','61','64','0','-701.347','6013.97','22.3146','3.22521','2534:0 2531:0 2536:0 2537:0 '), +('2536','zangar119','530','3521','3841','61','64','0','-592.408','5933.75','20.813','5.51464','2535:0 2537:0 '), +('2537','zangar120','530','3521','3521','61','64','0','-504.59','6048.64','23.1686','4.20775','2536:0 2535:0 2538:0 '), +('2538','zangar121','530','3521','3841','61','64','0','-495.325','5854.59','20.3458','2.52376','2537:0 2539:0 '), +('2539','zangar122','530','3521','3521','61','64','0','-318.55','6013.33','23.3657','3.50315','2538:0 2540:0 2654:0 '), +('2540','zangar123','530','3521','3819','61','64','0','-352.699','6158.62','21.7844','4.91293','2539:0 2541:0 2654:0 '), +('2541','zangar124','530','3521','3819','61','64','0','-338.739','6298.09','21.04','4.63411','2540:0 2542:0 '), +('2542','zangar125','530','3521','3819','61','64','0','-409.277','6401.64','21.9404','0.801373','2541:0 2543:0 2546:0 '), +('2543','zangar126','530','3521','3819','61','64','0','-230.832','6354.36','21.7058','3.81024','2542:0 2544:0 '), +('2544','zangar127','530','3521','3659','61','64','0','-139.857','6456.59','20.0973','3.85658','2543:0 2545:0 2653:0 '), +('2545','zangar128','530','3521','3659','61','64','0','-269.671','6613.48','23.0533','4.34353','2544:0 2546:0 '), +('2546','zangar129','530','3521','3659','61','64','0','-383.175','6598.68','22.9642','4.52024','2542:0 2545:0 2547:0 '), +('2547','zangar130','530','3521','3659','61','64','0','-215.725','6703.55','20.6145','4.19588','2546:0 2548:0 2549:0 '), +('2548','zangar131','530','3521','3659','61','64','0','-63.9463','6759.2','20.4434','3.22986','2547:0 2549:0 2555:0 '), +('2549','zangar132','530','3521','3659','61','64','0','-261.475','6823.98','21.6241','5.85702','2547:0 2548:0 2550:0 '), +('2550','zangar133','530','3521','3642','61','64','0','-208.234','6950.07','20.1839','0.893301','2549:0 2551:0 '), +('2551','zangar134','530','3521','3642','61','64','0','-126.36','7041.34','18.9083','3.86997','2550:0 2552:0 '), +('2552','zangar135','530','3521','3642','61','64','0','-50.056','7132.44','19.2134','4.60817','2551:0 2553:0 '), +('2553','zangar136','530','3521','3521','61','64','0','64.7648','7212.88','22.0782','0.46519','2552:0 2554:0 2562:0 2563:0 2574:0 '), +('2554','zangar137','530','3521','3521','61','64','0','44.854','7014.88','23.4767','1.78543','2553:0 2555:0 '), +('2555','zangar138','530','3521','3521','61','64','0','43.5835','6871.99','22.3894','4.7567','2554:0 2548:0 2556:0 2557:0 '), +('2556','zangar139','530','3521','3521','61','64','0','157.048','6719.99','20.4444','2.3259','2555:0 2557:0 2641:0 '), +('2557','zangar140','530','3521','3720','61','64','0','223.784','6824.17','25.6438','3.74197','2555:0 2556:0 2558:0 2641:0 '), +('2558','zangar141','530','3521','3720','61','64','0','234.681','6926.14','23.7418','4.49203','2557:0 2559:0 '), +('2559','zangar142','530','3521','3720','61','64','0','212.404','7079.58','35.2277','4.69231','2558:0 2560:0 '), +('2560','zangar143','530','3521','3720','61','64','0','220.362','7208.94','25.1738','4.71196','2559:0 2561:0 '), +('2561','zangar144','530','3521','3720','61','64','0','336.506','7340.86','41.5461','4.13471','2560:0 2562:0 2633:0 '), +('2562','zangar145','530','3521','3720','61','64','0','157.889','7365.75','22.1067','6.02358','2553:0 2561:0 2563:0 '), +('2563','zangar146','530','3521','3521','61','64','0','63.1597','7443.51','22.1993','5.2932','2553:0 2562:0 2564:0 2574:0 '), +('2564','zangar147','530','3521','3521','61','64','0','115.528','7547.02','21.6598','4.53135','2563:0 2565:0 2569:0 '), +('2565','zangar148','530','3521','3521','61','64','0','118.141','7658.13','21.5634','4.72141','2564:0 2566:0 2569:0 2575:0 2633:0 '), +('2566','zangar149','530','3521','3521','61','64','4','246.937','7753.08','23.1428','3.93445','2565:0 2567:0 2569:0 2575:0 2633:0 '), +('2567','zangar150','530','3521','3645','61','64','5','264.306','7863.47','23.7956','4.64131','2566:0 2568:0 2569:0 2575:0 '), +('2568','zangar151','530','3521','3521','61','64','4','449.39','7844.62','22.0319','3.04694','2567:0 2615:0 '), +('2569','zangar152','530','3521','3521','61','64','0','81.5617','7740.29','21.6356','3.14119','2564:0 2565:0 2566:0 2567:0 2570:0 2575:0 '), +('2570','zangar153','530','3521','3521','61','64','0','-28.0868','7736.24','20.5893','6.25922','2569:0 2571:0 2575:0 2582:0 '), +('2571','zangar154','530','3521','3521','61','64','0','-170.551','7601.35','23.3202','0.757515','2570:0 2572:0 2581:0 '), +('2572','zangar155','530','3521','3521','61','64','0','-174.825','7443.51','21.9702','1.57433','2571:0 2573:0 2574:0 '), +('2573','zangar156','530','3521','3642','61','64','0','-120.981','7305.7','23.422','1.77461','2572:0 2574:0 '), +('2574','zangar157','530','3521','3521','61','64','0','-20.2426','7327.37','23.7348','2.19481','2573:0 2572:0 2563:0 2553:0 '), +('2575','zangar158','530','3521','3521','61','64','0','84.0333','7841.91','22.0373','4.55886','2565:0 2566:0 2567:0 2569:0 2570:0 2576:0 '), +('2576','zangar159','530','3521','3521','61','64','0','65.13','7971.37','21.6779','4.89266','2575:0 2577:0 '), +('2577','zangar160','530','3521','3521','61','64','0','43.5829','8117.3','22.3841','4.94372','2576:0 2578:0 2598:0 '), +('2578','zangar161','530','3521','3521','61','64','0','-49.1017','8195.83','23.1999','5.71343','2577:0 2579:0 2583:0 2587:0 '), +('2579','zangar162','530','3521','3646','61','64','0','-209.87','8126.35','19.9595','0.261968','2578:0 2580:0 2582:0 2583:0 '), +('2580','zangar163','530','3521','3646','61','64','0','-261.772','7885.5','18.5845','1.41258','2579:0 2581:0 2582:0 '), +('2581','zangar164','530','3521','3521','61','64','0','-259.719','7720.02','20.8878','1.65605','2571:0 2580:0 '), +('2582','zangar165','530','3521','3646','61','64','0','-98.2706','7905.01','21.2267','4.92567','2570:0 2579:0 2580:0 '), +('2583','zangar166','530','3521','3521','61','64','0','-180.804','8271.58','22.3452','2.13749','2578:0 2579:0 2584:0 2587:0 '), +('2584','zangar167','530','3521','3521','61','64','0','-280.916','8401.01','22.613','5.34193','2583:0 2585:0 '), +('2585','zangar168','530','3521','3521','61','64','0','-414.115','8416.71','22.9544','6.2098','2584:0 2586:0 2949:0 '), +('2586','zangar169','530','3521','3521','61','64','0','-208.705','8507.09','22.1101','4.05625','2585:0 2587:0 '), +('2587','zangar170','530','3521','3521','61','64','0','-76.6423','8339.54','20.5708','1.28772','2583:0 2578:0 2586:0 2588:0 '), +('2588','zangar171','530','3521','3521','61','64','0','2.14825','8447.62','24.0799','1.18326','2587:0 2589:0 2596:0 '), +('2589','zangar172','530','3521','3647','61','64','0','-67.5414','8574.38','21.4033','5.22493','2588:0 2590:0 2596:0 '), +('2590','zangar173','530','3521','3647','61','64','0','-131.783','8711.6','20.1378','5.16367','2589:0 2591:0 '), +('2591','zangar174','530','3521','3647','61','64','0','-58.2395','8845.13','19.6236','4.45683','2590:0 2592:0 '), +('2592','zangar175','530','3521','3647','61','64','0','-158.14','8998.32','22.6743','5.39853','2591:0 2593:0 '), +('2593','zangar176','530','3521','3521','61','64','0','49.2289','8919.95','22.8353','4.08298','2592:0 2594:0 2612:0 '), +('2594','zangar177','530','3521','3658','61','64','0','191.332','8791.02','22.5064','3.15621','2593:0 2595:0 '), +('2595','zangar178','530','3521','3658','61','64','0','100.838','8633.77','22.3518','1.01992','2594:0 2596:0 '), +('2596','zangar179','530','3521','3521','61','64','0','46.3545','8528.92','21.3211','3.67772','2588:0 2589:0 2595:0 2597:0 '), +('2597','zangar180','530','3521','3521','61','64','1','201.143','8506.92','24.4625','4.24664','2596:0 2598:0 '), +('2598','zangar181','530','3521','3656','61','64','0','176.313','8271.1','19.3723','2.275','2577:0 2597:0 2599:0 '), +('2599','zangar182','530','3521','3656','61','64','0','285.943','8219.3','20.2926','3.15229','2598:0 2600:0 '), +('2600','zangar183','530','3521','3656','61','64','0','386.339','8346.33','22.789','4.31625','2599:0 2601:0 '), +('2601','zangar184','530','3521','3656','61','64','0','494.267','8437.25','21.4802','4.07671','2600:0 2602:0 2609:0 2613:0 '), +('2602','zangar185','530','3521','3656','61','64','0','608.933','8508.26','22.3504','3.55914','2601:0 2603:0 2609:0 '), +('2603','zangar186','530','3521','3521','61','64','0','742.61','8532.55','22.4318','3.26227','2602:0 2604:0 2608:0 2609:0 '), +('2604','zangar187','530','3521','3521','61','64','0','895.923','8416.01','24.0337','2.57898','2603:0 2605:0 '), +('2605','zangar188','530','3521','3650','61','64','0','1075.84','8407.67','22.4904','3.12875','2604:0 2606:0 2607:0 2620:0 '), +('2606','zangar189','530','3521','3650','61','64','0','1227.41','8505.09','22.644','3.70367','2605:0 2607:0 2621:0 '), +('2607','zangar190','530','3521','3650','61','64','0','1037.11','8556.71','23.1526','5.52971','2605:0 2606:0 2608:0 '), +('2608','zangar191','530','3521','3521','61','64','0','838.679','8631.92','21.121','3.9','2607:0 2603:0 '), +('2609','zangar192','530','3521','3657','61','64','0','532.243','8648.7','20.2148','1.72209','2601:0 2602:0 2603:0 2610:0 '), +('2610','zangar193','530','3521','3521','61','64','0','500.585','8831.78','29.9762','3.31252','2609:0 2611:0 '), +('2611','zangar194','530','3521','3521','61','64','0','312.939','8988.81','29.3602','2.17448','2610:0 2612:0 '), +('2612','zangar195','530','3521','3521','61','64','0','153.844','9001.52','20.6373','4.20787','2593:0 2611:0 '), +('2613','zangar196','530','3521','3643','61','64','0','634.189','8249.73','20.9802','4.23537','2601:0 2614:0 '), +('2614','zangar197','530','3521','3643','61','64','0','610.771','8068.12','19.5665','1.60114','2613:0 2615:0 2617:0 '), +('2615','zangar198','530','3521','3667','61','64','0','579.736','7860.06','21.923','1.46997','2568:0 2614:0 2616:0 '), +('2616','zangar199','530','3521','3667','61','64','0','737.751','7895.31','21.9665','6.19807','2615:0 2617:0 '), +('2617','zangar200','530','3521','3521','61','64','0','787.048','8075.83','19.8301','1.19115','2614:0 2616:0 2618:0 '), +('2618','zangar201','530','3521','3667','61','64','0','940.179','8075.93','22.3322','3.07219','2617:0 2619:0 2625:0 '), +('2619','zangar202','530','3521','3667','61','64','0','1079.94','8078.99','22.0839','3.44134','2618:0 2620:0 2624:0 '), +('2620','zangar203','530','3521','3640','61','64','0','1142.01','8208.94','18.3815','1.68911','2605:0 2619:0 2624:0 '), +('2621','zangar204','530','3521','3650','61','64','0','1365.27','8597.28','20.4376','3.62905','2606:0 2622:0 '), +('2622','zangar205','530','3521','3650','61','64','0','1473.46','8617.75','-19.2604','3.20336','2621:0 2623:0 '), +('2623','zangar206','530','3521','3651','61','64','0','1617.83','8604.6','-25.7602','3.11932','2622:0 '), +('2624','zangar207','530','3521','3521','61','64','0','1204.46','7994.66','23.5559','2.79496','2619:0 2620:0 2625:0 '), +('2625','zangar208','530','3521','3667','61','64','0','985.581','7906.61','21.9749','1.94279','2618:0 2624:0 2626:0 '), +('2626','zangar209','530','3521','3667','61','64','0','924.71','7759.51','22.2632','4.47963','2625:0 2627:0 '), +('2627','zangar210','530','3521','3667','61','64','0','972.886','7540.98','20.6745','5.06474','2626:0 2628:0 '), +('2628','zangar211','530','3521','3766','61','64','3','1008.04','7381.67','36.2454','2.67557','2627:0 2629:0 3123:0 '), +('2629','zangar212','530','3521','3521','61','64','2','888.478','7369.16','20.9021','3.3785','2628:0 2630:0 2634:0 '), +('2630','zangar213','530','3521','3521','61','64','0','749.898','7401.62','22.0246','0.295815','2629:0 2631:0 2634:0 '), +('2631','zangar214','530','3521','3653','61','64','0','578.096','7540.84','19.4559','5.3043','2630:0 2632:0 '), +('2632','zangar215','530','3521','3653','61','64','0','453.399','7459.1','23.6644','2.9591','2631:0 2633:0 '), +('2633','zangar216','530','3521','3521','61','64','0','301.56','7502.53','18.7007','4.88489','2561:0 2632:0 2566:0 2565:0 '), +('2634','zangar217','530','3521','3653','61','64','0','723.766','7219.4','18.4476','1.52104','2629:0 2630:0 2635:0 '), +('2635','zangar218','530','3521','3653','61','64','0','828.704','7046.99','21.9746','5.47316','2634:0 2636:0 '), +('2636','zangar219','530','3521','3521','61','64','0','813.291','6859.8','21.1175','4.5346','2635:0 2637:0 '), +('2637','zangar220','530','3521','3521','61','64','0','786.813','6686.79','19.6235','4.77415','2636:0 2638:0 '), +('2638','zangar221','530','3521','3521','61','64','0','739.145','6548.18','21.4284','2.68185','2637:0 2639:0 2646:0 '), +('2639','zangar222','530','3521','3653','61','64','0','533.697','6616.1','23.4617','5.98523','2638:0 2640:0 2646:0 '), +('2640','zangar223','530','3521','3521','61','64','0','392.669','6497.59','21.0234','1.88546','2639:0 2641:0 2642:0 2646:0 '), +('2641','zangar224','530','3521','3521','61','64','0','309.995','6655.73','21.2137','5.01134','2556:0 2640:0 2557:0 '), +('2642','zangar225','530','3521','3521','61','64','0','377.938','6282.83','22.023','4.47492','2640:0 2643:0 '), +('2643','zangar226','530','3521','3521','61','64','0','413.817','6144.54','22.2423','1.89569','2642:0 2644:0 2649:0 '), +('2644','zangar227','530','3521','3521','61','64','0','588.059','6075.05','22.5742','2.63553','2643:0 2645:0 2647:0 2670:0 '), +('2645','zangar228','530','3521','3818','61','64','0','603.408','6285.69','21.8038','4.64065','2644:0 2646:0 2647:0 '), +('2646','zangar229','530','3521','3653','61','64','0','621.81','6442.18','20.5988','2.00643','2638:0 2639:0 2640:0 2645:0 '), +('2647','zangar230','530','3521','3521','61','64','0','691.526','6136.65','24.626','4.02491','2644:0 2645:0 2648:0 3018:0 '), +('2648','zangar231','530','3521','3521','61','64','0','718.677','6010.01','23.0266','4.85273','2647:0 2670:0 3018:0 '), +('2649','zangar232','530','3521','3644','61','64','3','284.699','5949','26.4296','1.71192','2643:0 2650:0 2657:0 '), +('2650','zangar233','530','3521','3521','61','64','2','171.4','6095.69','21.4396','5.75672','2649:0 2651:0 2654:0 2657:0 '), +('2651','zangar234','530','3521','3521','61','64','0','220.592','6179.94','22.7879','2.4926','2650:0 2652:0 '), +('2652','zangar235','530','3521','3521','61','64','0','109.112','6307.6','20.5752','2.47375','2651:0 2653:0 2654:0 '), +('2653','zangar236','530','3521','3659','61','64','0','-2.05183','6372.4','21.196','6.01275','2544:0 2652:0 '), +('2654','zangar237','530','3521','3521','61','64','0','-101.637','6148.49','20.9712','3.74609','2539:0 2540:0 2652:0 2650:0 2655:0 '), +('2655','zangar238','530','3521','3521','61','64','0','-95.8529','5919.89','21.912','1.45665','2654:0 2656:0 '), +('2656','zangar239','530','3521','3521','61','64','0','13.2803','5837.85','22.8222','2.53657','2655:0 2657:0 '), +('2657','zangar240','530','3521','3521','61','64','0','151.585','5801.02','22.4062','1.73938','2649:0 2650:0 2656:0 2658:0 '), +('2658','zangar241','530','3521','3521','61','64','0','12.867','5638.72','21.5024','0.850312','2657:0 2659:0 '), +('2659','zangar242','530','3521','3521','61','64','0','48.9274','5481.91','21.872','2.08181','2520:0 2658:0 2660:0 2662:0 '), +('2660','zangar243','530','3521','3521','61','64','4','71.9609','5332.54','21.9147','5.20377','2659:0 2661:0 2662:0 '), +('2661','zangar244','530','3521','3718','61','64','5','85.2911','5223.84','22.6669','1.80693','2660:0 '), +('2662','zangar245','530','3521','3648','61','64','0','158.923','5429.39','21.7352','4.09636','2660:0 2659:0 2663:0 '), +('2663','zangar246','530','3521','3648','61','64','0','315.219','5408.5','20.9134','6.16588','2662:0 2664:0 2665:0 '), +('2664','zangar247','530','3521','3648','61','64','0','377.232','5291.67','16.6948','0.491381','2663:0 2665:0 '), +('2665','zangar248','530','3521','3648','61','64','0','610.457','5425.64','-14.6579','4.20083','2663:0 2664:0 2666:0 2667:0 2668:0 '), +('2666','zangar249','530','3521','3648','61','64','0','820.719','5482.54','9.38793','3.43975','2665:0 2667:0 '), +('2667','zangar250','530','3521','3648','61','64','0','675.579','5207.73','-0.430108','1.71187','2665:0 2666:0 '), +('2668','zangar251','530','3521','3648','61','64','0','581.932','5617.72','21.5502','1.58306','2665:0 2669:0 '), +('2669','zangar252','530','3521','3521','61','64','0','549.492','5772.45','23.2954','1.1668','2668:0 2670:0 '), +('2670','zangar253','530','3521','3521','61','64','0','569.447','5936.2','20.4341','1.13146','2669:0 2648:0 2644:0 3018:0 '), +('2671','nagrand254','530','3521','3521','61','64','0','-1293.68','6268.96','47.863','5.30897','2533:0 2973:0 '), +('2672','zangar255','530','3521','3521','61','64','0','-1072.75','5374.44','23.0643','3.2316','2526:0 2527:0 2673:0 '), +('2673','tero256','530','3519','3519','61','64','0','-1193.26','5326.41','32.1568','3.76253','2672:0 2674:0 '), +('2674','tero257','530','3519','3519','61','64','0','-1352.86','5166.19','60.3214','0.730896','2673:0 2675:0 '), +('2675','tero258','530','3519','3519','61','64','0','-1481.09','5196.68','44.2804','2.26164','2674:0 2676:0 '), +('2676','tero259','530','3703','3703','61','64','0','-1562.26','5219.05','26.8104','5.79513','2675:0 2677:0 '), +('2677','tero260','530','3703','3703','61','64','0','-1646.24','5089.63','9.06231','3.96908','2676:0 2678:0 2679:0 '), +('2678','tero261','530','3519','3519','62','65','0','-1504.23','5007.75','-5.38562','2.6182','2677:0 '), +('2679','tero262','530','3703','3703','62','65','0','-1767.61','5061.37','7.70519','3.4303','2677:0 2680:0 '), +('2680','tero263','530','3703','3703','62','65','0','-1923.14','5054.44','19.4908','2.72737','2679:0 2681:0 2682:0 2787:0 '), +('2681','tero264','530','3519','3519','62','65','0','-1970.49','4974.82','28.2638','4.09397','2680:0 2770:0 2787:0 '), +('2682','tero265','530','3703','3703','62','65','0','-2043.23','5134.06','8.27497','5.68597','2680:0 2683:0 2787:0 '), +('2683','tero266','530','3519','3519','62','65','0','-2181.58','5096.17','-21.1362','0.23923','2682:0 2684:0 2685:0 2686:0 2787:0 '), +('2684','tero267','530','3519','3519','62','65','0','-2216.99','4909.63','1.39859','1.40554','2683:0 2787:0 '), +('2685','tero268','530','3519','3519','62','65','0','-2303.98','5268.43','-9.97572','5.28148','2683:0 2686:0 2687:0 2690:0 '), +('2686','tero269','530','3519','3682','62','65','0','-2344.61','5051.57','-2.04758','0.266708','2685:0 2683:0 2690:0 '), +('2687','tero270','530','3519','3677','62','65','0','-2431.44','5399.92','1.64547','5.51947','2685:0 2688:0 '), +('2688','tero271','530','3519','3519','62','65','0','-2521.64','5210.06','1.76945','1.04896','2687:0 2689:0 2691:0 '), +('2689','tero272','530','3519','3682','62','65','0','-2502.78','5113.56','7.96009','6.26007','2688:0 2690:0 2691:0 '), +('2690','tero273','530','3519','3519','62','65','0','-2359.81','5159.68','-3.26821','3.01403','2689:0 2685:0 2686:0 '), +('2691','tero274','530','3519','3519','62','65','0','-2640.66','5114.54','-2.29','3.12004','2688:0 2689:0 2692:0 '), +('2692','tero275','530','3519','3887','62','65','1','-2764.32','5083.04','-6.61069','4.7576','2691:0 2693:0 2694:0 '), +('2693','tero276','530','3519','3697','62','65','0','-2753.24','4900.43','-9.77721','1.5139','2692:0 2694:0 2696:0 '), +('2694','tero277','530','3519','3697','62','65','0','-2924.05','5077.7','-21.4831','6.27734','2693:0 2692:0 2695:0 2696:0 '), +('2695','tero278','530','3519','3697','62','65','0','-2988.38','5242.11','-18.2224','5.09376','2694:0 2737:0 2739:0 '), +('2696','tero279','530','3519','3697','62','65','0','-2927.67','4884.87','-20.3937','1.65765','2693:0 2694:0 2697:0 '), +('2697','tero280','530','3519','3891','62','65','0','-2833.33','4763.19','-3.82034','2.17758','2696:0 2698:0 '), +('2698','tero281','530','3519','3891','62','65','0','-2735.66','4761.53','-7.97762','3.14205','2697:0 2699:0 '), +('2699','tero282','530','3519','3697','62','65','0','-2927.04','4629.03','-20.1141','1.04898','2698:0 2700:0 2701:0 2703:0 '), +('2700','tero283','530','3519','3697','62','65','0','-2792.58','4548.65','-5.17792','2.36688','2699:0 '), +('2701','tero284','530','3519','3697','62','65','0','-3098.66','4626.88','-20.2418','0.186611','2699:0 2702:0 2707:0 '), +('2702','tero285','530','3519','3697','62','65','0','-3111.3','4451.66','-21.8624','1.72364','2701:0 2703:0 '), +('2703','tero286','530','3519','3697','62','65','0','-2978.71','4453.52','-19.1914','1.37334','2702:0 2699:0 2704:0 2762:0 '), +('2704','tero287','530','3519','3697','62','65','0','-3124.74','4312.73','-13.4977','1.36941','2703:0 2705:0 2744:0 '), +('2705','tero288','530','3519','3697','62','65','0','-3343.1','4318.47','-21.2101','0.0703571','2704:0 2706:0 2708:0 2744:0 '), +('2706','tero289','530','3519','3697','62','65','0','-3251.81','4540.47','-21.009','4.41204','2705:0 2707:0 2709:0 '), +('2707','tero290','530','3519','3688','62','65','0','-3251.45','4665.87','-22.0178','5.15817','2706:0 2701:0 '), +('2708','tero291','530','3519','3892','62','65','0','-3461.56','4373.44','-11.6911','5.77627','2705:0 2709:0 2710:0 '), +('2709','tero292','530','3519','3697','62','65','0','-3412.64','4543.05','-19.8323','4.37513','2708:0 2706:0 2710:0 '), +('2710','tero293','530','3519','3697','62','65','0','-3615.55','4423.5','-15.264','6.23338','2708:0 2709:0 2711:0 2712:0 '), +('2711','tero294','530','3519','3885','62','65','0','-3737.19','4335.92','3.0303','0.715952','2710:0 2712:0 '), +('2712','tero295','530','3519','3697','62','65','0','-3717.12','4553.09','-15.395','5.41343','2710:0 2711:0 2713:0 '), +('2713','tero296','530','3519','3886','62','65','0','-3751.58','4692.08','-17.1029','5.18959','2712:0 2714:0 2717:0 '), +('2714','tero297','530','3519','3688','62','65','0','-3628.23','4736.46','-22.1214','3.39967','2713:0 2715:0 '), +('2715','tero298','530','3519','3688','62','65','0','-3683.64','4880.37','-22.2453','4.85659','2714:0 2716:0 '), +('2716','tero299','530','3519','3697','62','65','0','-3787.46','5013.52','-20.1657','5.31998','2715:0 2717:0 2718:0 '), +('2717','tero300','530','3519','3697','62','65','0','-3875.72','4882.7','-35.8103','0.180317','2716:0 2713:0 '), +('2718','tero301','530','3519','3697','62','65','0','-3756.44','5163.25','-22.4721','4.73093','2716:0 2720:0 2719:0 '), +('2719','tero302','530','3519','3697','62','65','1','-3750.45','5404.46','-3.33481','1.38198','2718:0 2720:0 2721:0 '), +('2720','tero303','530','3519','3894','62','65','0','-3634.78','5300.46','-21.1377','3.63217','2718:0 2721:0 2741:0 2719:0 '), +('2721','tero304','530','3519','3697','62','65','0','-3670.35','5445.73','-4.16488','5.0239','2720:0 2722:0 2741:0 2719:0 '), +('2722','tero305','530','3519','3697','62','65','0','-3645.73','5559.5','-2.14497','4.56524','2721:0 2723:0 '), +('2723','tero306','530','3519','3686','62','65','0','-3642.69','5710.06','-2.77685','4.68385','2722:0 2724:0 2725:0 '), +('2724','tero307','530','3519','3519','62','65','0','-3501.7','5745.82','0.234953','3.52538','2723:0 2730:0 '), +('2725','tero308','530','3519','3519','62','65','0','-3733.98','5830.74','-2.74856','5.14723','2723:0 2726:0 '), +('2726','tero309','530','3519','3519','62','65','0','-3787.12','5986.43','-1.72887','4.96659','2725:0 2727:0 '), +('2727','tero310','530','3519','3519','62','65','0','-3628.51','6056.29','-3.33948','3.46806','2726:0 2728:0 '), +('2728','tero311','530','3519','3519','62','65','0','-3508.49','6174.6','4.08676','3.93145','2727:0 2729:0 '), +('2729','tero312','530','3519','3519','62','65','0','-3378.56','5998.98','-6.51004','2.27505','2728:0 2730:0 2731:0 2742:0 '), +('2730','tero313','530','3519','3519','62','65','0','-3476.42','5902.44','-30.7079','0.739594','2729:0 2724:0 2731:0 '), +('2731','tero314','530','3519','3519','62','65','0','-3285.32','5879.96','-19.0985','2.70702','2730:0 2729:0 2732:0 2742:0 '), +('2732','tero315','530','3519','3519','62','65','0','-3364.14','5789.3','-1.76223','1.10479','2731:0 2733:0 2742:0 '), +('2733','tero316','530','3519','3697','62','65','0','-3305.33','5740.49','0.884062','4.92025','2732:0 2734:0 2742:0 '), +('2734','tero317','530','3519','3697','62','65','0','-3286.23','5602.34','-6.97428','4.90454','2733:0 2735:0 '), +('2735','tero318','530','3519','3697','62','65','0','-3117.93','5582.45','-7.6733','6.23185','2734:0 2736:0 2739:0 '), +('2736','tero319','530','3519','3697','62','65','0','-2973.64','5517.8','-7.12772','5.62318','2735:0 2737:0 2739:0 '), +('2737','tero320','530','3519','3697','62','65','0','-2940.15','5358.09','-13.875','4.96972','2736:0 2695:0 2738:0 2739:0 '), +('2738','tero321','530','3519','3697','62','65','0','-2795.19','5341.13','-2.81699','5.23676','2737:0 '), +('2739','tero322','530','3519','3697','62','65','0','-3141.55','5381.37','-22.1389','0.524372','2695:0 2735:0 2736:0 2737:0 2740:0 '), +('2740','tero323','530','3519','3697','62','65','0','-3327.94','5329.47','-18.7903','0.115962','2739:0 2741:0 '), +('2741','tero324','530','3519','3697','62','65','0','-3499.7','5387.05','-17.1808','2.91198','2740:0 2720:0 2721:0 '), +('2742','tero325','530','3519','3519','62','65','0','-3184.45','5888','2.6866','3.28897','2729:0 2731:0 2732:0 2733:0 2743:0 2823:0 '), +('2743','tero326','530','3519','3519','62','65','0','-3032.12','5978.81','4.34466','3.72093','2742:0 2823:0 '), +('2744','tero327','530','3519','3697','62','65','0','-3134.88','4193.43','-7.28336','1.29796','2704:0 2705:0 2745:0 '), +('2745','tero328','530','3519','3697','62','65','0','-3028.19','4178.94','-0.0653765','2.95906','2744:0 2746:0 '), +('2746','tero329','530','3519','3519','62','65','0','-2977.23','4157.96','8.11812','3.04232','2745:0 2747:0 '), +('2747','tero330','530','3519','3684','62','65','3','-2960.39','4073.83','2.27582','5.22886','2746:0 2748:0 '), +('2748','tero331','530','3519','3684','62','65','3','-2931.59','3983.95','-0.908721','4.85187','2747:0 2749:0 '), +('2749','tero332','530','3519','3519','62','65','2','-2826.95','3982.66','1.7483','3.08078','2748:0 2750:0 '), +('2750','tero333','530','3519','3519','62','65','0','-2775.95','3939.94','2.93918','0.30046','2749:0 2751:0 '), +('2751','tero334','530','3519','3519','62','65','0','-2653.89','3959.73','4.49025','3.42242','2750:0 2752:0 2791:0 '), +('2752','tero335','530','3519','3519','62','65','0','-2556.49','4106.29','1.74147','4.38846','2751:0 2753:0 2790:0 '), +('2753','tero336','530','3519','3519','62','65','0','-2371.13','4150.23','2.76251','3.40672','2752:0 2754:0 2763:0 '), +('2754','tero337','530','3519','3519','62','65','0','-2284.4','4192.35','1.1876','3.57951','2753:0 2755:0 2763:0 '), +('2755','tero338','530','3519','3675','62','65','0','-2258.83','4242.98','1.82659','4.04289','2754:0 2756:0 2763:0 '), +('2756','tero339','530','3519','3519','62','65','0','-2454.44','4246.04','1.04828','6.28128','2755:0 2757:0 '), +('2757','tero340','530','3519','3519','62','65','4','-2553.08','4300.19','21.2595','5.70952','2756:0 2758:0 '), +('2758','tero341','530','3519','3683','62','65','5','-2594.46','4387.9','29.7655','5.39144','2757:0 2759:0 '), +('2759','tero342','530','3519','3683','62','65','5','-2656.03','4422.89','36.1553','5.66241','2758:0 2760:0 '), +('2760','tero343','530','3519','3683','62','65','4','-2719.28','4376.75','22.1052','0.28636','2759:0 2761:0 '), +('2761','tero344','530','3519','3697','62','65','0','-2834.82','4335.44','5.95973','3.39654','2760:0 2762:0 '), +('2762','tero345','530','3519','3697','62','65','0','-2909.54','4349.69','0.308415','2.08492','2761:0 2703:0 '), +('2763','tero346','530','3519','3675','62','65','0','-2170.52','4243.01','5.56803','3.39654','2753:0 2754:0 2755:0 2764:0 2822:0 '), +('2764','tero347','530','3519','3675','62','65','0','-2148.84','4189.65','7.46099','4.90842','2763:0 2765:0 2766:0 2822:0 '), +('2765','tero348','530','3519','3519','62','65','0','-2169.46','4024.43','0.27975','4.58248','2764:0 2788:0 2793:0 '), +('2766','tero349','530','3519','3675','62','65','0','-2043.89','4276.14','4.85305','3.85599','2764:0 2767:0 2780:0 2822:0 '), +('2767','tero350','530','3519','3519','62','65','0','-2003.56','4443.33','7.66134','1.22491','2766:0 2768:0 2780:0 2785:0 2822:0 '), +('2768','tero351','530','3519','3674','62','65','0','-1957.79','4600.09','4.55853','1.42126','2767:0 2769:0 2777:0 2780:0 2785:0 '), +('2769','tero352','530','3519','3674','62','65','0','-1958.96','4765.62','-1.86355','1.51943','2768:0 2770:0 2785:0 2787:0 '), +('2770','tero353','530','3519','3519','62','65','0','-1920.31','4893.8','2.58999','4.29189','2769:0 2681:0 2771:0 2787:0 '), +('2771','tero354','530','3519','3674','62','65','0','-1767.42','4808.53','10.9381','2.60326','2770:0 2772:0 '), +('2772','tero355','530','3519','3519','62','65','0','-1610.9','4687.94','-0.462587','3.37452','2771:0 2773:0 '), +('2773','tero356','530','3519','3519','62','65','0','-1438.54','4646.53','23.7191','3.09413','2772:0 2774:0 '), +('2774','tero357','530','3519','3689','62','65','0','-1505.3','4470.55','41.7643','1.30342','2773:0 2775:0 2779:0 '), +('2775','tero358','530','3519','3674','62','65','0','-1652.18','4568.13','2.67411','5.85088','2774:0 2776:0 '), +('2776','tero359','530','3519','3674','62','65','0','-1796.78','4588.16','10.9079','6.12577','2775:0 2777:0 2778:0 2780:0 '), +('2777','tero360','530','3519','3674','62','65','0','-1895.32','4551.89','11.162','0.506242','2776:0 2768:0 2778:0 2780:0 2785:0 '), +('2778','tero361','530','3519','3689','62','65','0','-1732.56','4448.64','2.34425','5.26889','2777:0 2776:0 2779:0 2780:0 '), +('2779','tero362','530','3519','3689','62','65','0','-1608.22','4353.23','36.5945','1.16519','2778:0 2774:0 '), +('2780','tero363','530','3519','3519','62','65','0','-1921.27','4373.25','1.92765','2.16501','2766:0 2767:0 2768:0 2776:0 2777:0 2778:0 2781:0 2822:0 '), +('2781','tero364','530','3519','3519','62','65','0','-1840.29','4219.11','25.1803','2.004','2780:0 2782:0 2822:0 '), +('2782','tero365','530','3519','3519','62','65','0','-1752.97','4184.94','57.0737','5.13381','2781:0 2783:0 2822:0 '), +('2783','tero366','530','3519','3858','62','65','0','-1716.88','4074.81','63.5817','4.67042','2782:0 2784:0 '), +('2784','tero367','530','3519','3858','62','65','0','-1691.82','3935.7','48.2267','4.84636','2783:0 2465:0 '), +('2785','tero368','530','3519','3519','62','65','0','-2102.76','4635.13','-7.65692','2.95747','2767:0 2768:0 2769:0 2777:0 2786:0 '), +('2786','tero369','530','3519','3519','62','65','0','-2257.36','4704.28','-0.0780256','1.2021','2785:0 '), +('2787','tero370','530','3519','3519','62','65','0','-2070.88','4924.17','13.5597','3.10277','2680:0 2681:0 2682:0 2683:0 2684:0 2769:0 2770:0 '), +('2788','tero371','530','3519','3519','62','65','0','-2246.8','3880.41','2.21742','1.23746','2765:0 2789:0 2793:0 '), +('2789','tero372','530','3519','3519','62','65','0','-2340.28','3972.87','-13.2572','5.73387','2788:0 2790:0 2793:0 '), +('2790','tero373','530','3519','3519','62','65','0','-2488.61','4019.54','0.242718','5.94199','2789:0 2752:0 2792:0 '), +('2791','tero374','530','3519','3860','62','65','0','-2554.91','3912.09','9.61175','2.80825','2751:0 2792:0 '), +('2792','tero375','530','3519','3860','62','65','0','-2485.03','3909.12','5.70774','1.75582','2791:0 2790:0 '), +('2793','tero376','530','3519','3519','62','65','0','-2259.61','3793.94','0.912792','1.33958','2765:0 2788:0 2789:0 2794:0 2817:0 '), +('2794','tero377','530','3519','3519','62','65','0','-2386.92','3640.31','1.09836','4.00208','2793:0 2795:0 2804:0 '), +('2795','tero378','530','3519','3519','62','65','0','-2479.53','3552.42','1.40969','5.89488','2794:0 2796:0 2805:0 '), +('2796','tero379','530','3519','3519','62','65','0','-2345.97','3468.35','-9.89249','2.70223','2795:0 2797:0 2805:0 '), +('2797','tero380','530','3519','3519','62','65','0','-2312.38','3409.64','-11.2993','4.48901','2796:0 2798:0 '), +('2798','tero381','530','3519','3681','62','65','0','-2354.51','3272.69','-1.60544','1.28851','2797:0 2799:0 '), +('2799','tero382','530','3519','3681','62','65','0','-2370.38','3203.41','-1.58182','4.34763','2798:0 2800:0 2808:0 '), +('2800','tero383','530','3519','3681','62','65','0','-2266.46','3160.45','-5.07597','2.81611','2799:0 2801:0 '), +('2801','tero384','530','3519','3519','62','65','0','-2151','3259.96','-30.9173','0.993986','2800:0 2802:0 '), +('2802','tero385','530','3519','3519','62','65','0','-2122.05','3416.4','-46.8967','4.54007','2801:0 2803:0 2817:0 '), +('2803','tero386','530','3519','3519','62','65','0','-2185.17','3533.98','-39.5174','5.06236','2802:0 2804:0 2817:0 '), +('2804','tero387','530','3519','3519','62','65','0','-2281.3','3632.39','-12.7757','5.10555','2803:0 2794:0 2817:0 '), +('2805','tero388','530','3519','3519','62','65','0','-2591.73','3492.79','0.37378','0.569874','2795:0 2796:0 2806:0 '), +('2806','tero389','530','3519','3519','62','65','0','-2706.59','3361.89','-0.445352','4.0649','2805:0 2807:0 2809:0 2810:0 '), +('2807','tero390','530','3519','3519','62','65','0','-2599.6','3260.29','1.92252','2.39202','2806:0 2808:0 2809:0 '), +('2808','tero391','530','3519','3519','62','65','0','-2474.91','3212.52','3.71098','0.644506','2807:0 2799:0 '), +('2809','tero392','530','3519','3519','62','65','0','-2789.26','3261.1','5.82088','0.813367','2806:0 2807:0 3580:0 '), +('2810','tero393','530','3519','3685','62','65','0','-2834.79','3413.38','-40.4077','5.88312','2806:0 2811:0 2816:0 '), +('2811','tero394','530','3519','3519','62','65','0','-2792.39','3494.53','-29.9885','3.12636','2810:0 2812:0 '), +('2812','tero395','530','3519','3685','62','65','0','-2893.22','3498.45','-28.8532','2.48233','2811:0 2813:0 '), +('2813','tero396','530','3519','3685','62','65','0','-2966.27','3553.99','-6.76611','5.48021','2812:0 2814:0 '), +('2814','tero397','530','3519','3685','62','65','0','-2986.16','3462.38','0.040144','1.25475','2813:0 2815:0 '), +('2815','tero398','530','3519','3685','62','65','0','-2966.04','3366.93','-1.18287','0.0020256','2814:0 2816:0 '), +('2816','tero399','530','3519','3519','62','65','0','-2877.47','3356.32','-19.7096','2.96298','2815:0 2810:0 '), +('2817','tero400','530','3519','3519','62','65','0','-2096.95','3654.03','-54.8181','4.15287','2793:0 2802:0 2803:0 2804:0 2818:0 '), +('2818','tero401','530','3519','3519','62','65','0','-2064.06','3807.53','1.04682','4.11754','2817:0 2819:0 '), +('2819','tero402','530','3519','3676','62','65','0','-1908.63','3920.4','-2.85436','0.610731','2818:0 2820:0 2821:0 '), +('2820','tero403','530','3519','3519','62','65','0','-1975.78','4077.99','-0.0257859','4.60841','2819:0 2821:0 2822:0 '), +('2821','tero404','530','3519','3676','62','65','0','-2060.38','3925.05','-0.749629','0.524347','2820:0 2819:0 '), +('2822','tero405','530','3519','3519','62','65','0','-1932.67','4229.67','0.0246133','4.45526','2763:0 2764:0 2766:0 2767:0 2780:0 2781:0 2782:0 2820:0 '), +('2823','nag406','530','3518','3788','62','65','0','-2979.16','5983.95','6.75645','5.87684','2742:0 2743:0 2824:0 '), +('2824','nag407','530','3518','3788','64','67','0','-2903.9','5958.93','14.5613','0.547909','2823:0 2825:0 '), +('2825','nag408','530','3518','3788','64','67','0','-2695.32','6074.17','39.2839','0.398684','2824:0 2826:0 '), +('2826','nag409','530','3518','3518','64','67','0','-2642.01','6196.52','35.4889','4.38459','2825:0 2827:0 2830:0 '), +('2827','nag410','530','3518','3610','64','67','0','-2584.26','6153','22.5432','1.45591','2826:0 2828:0 2878:0 '), +('2828','nag411','530','3518','3518','64','67','0','-2469.6','6354.46','29.3999','3.93692','2827:0 2829:0 2848:0 2868:0 '), +('2829','nag412','530','3518','3518','64','67','0','-2598.81','6353.52','38.4982','4.67519','2828:0 2830:0 2848:0 '), +('2830','nag413','530','3518','3518','64','67','0','-2647.16','6298.81','39.1645','4.36889','2829:0 2826:0 2831:0 '), +('2831','nag414','530','3518','3518','64','67','0','-2698.48','6403.1','37.6375','5.24131','2830:0 2832:0 '), +('2832','nag416','530','3518','3637','64','67','0','-2811.55','6437.45','63.1489','2.87099','2833:0 2834:0 2838:0 2831:0 '), +('2833','nag417','530','3518','3637','64','67','0','-2883.12','6557.15','51.9561','5.05205','2832:0 2836:0 2839:0 '), +('2834','nag418','530','3518','3637','64','67','0','-2866.92','6391.89','80.9253','0.732363','2832:0 2835:0 '), +('2835','nag419','530','3518','3637','64','67','0','-2947.68','6377.55','94.6931','0.234009','2834:0 2836:0 2837:0 '), +('2836','nag420','530','3518','3637','64','67','0','-2933.99','6526.24','74.3112','5.85317','2833:0 2835:0 '), +('2837','nag421','530','3518','3637','64','67','0','-2858.26','6300.73','73.5168','2.16181','2835:0 2838:0 '), +('2838','nag422','530','3518','3637','64','67','0','-2800.78','6320.06','63.9422','1.52171','2837:0 2832:0 '), +('2839','nag423','530','3518','3518','64','67','0','-2813.58','6624.23','27.952','0.826629','2833:0 2840:0 2843:0 '), +('2840','nag424','530','3518','3518','64','67','0','-2876.27','6701.94','18.9403','5.46206','2839:0 2841:0 '), +('2841','nag425','530','3518','3518','64','67','0','-2695.5','6579.52','23.3102','2.71395','2840:0 2842:0 '), +('2842','nag426','530','3518','3518','64','67','0','-2618.02','6729.9','-1.1338','4.0884','2841:0 2843:0 '), +('2843','nag427','530','3518','3518','64','67','0','-2762.9','6813.59','-4.67732','4.5989','2839:0 2842:0 2844:0 2979:0 '), +('2844','nag428','530','3518','3518','64','67','0','-2754.65','6993.51','-7.49834','4.92564','2843:0 2845:0 2979:0 2980:0 '), +('2845','nag429','530','3518','3518','64','67','0','-2554.45','7061.91','-8.95898','0.31535','2844:0 2846:0 2849:0 '), +('2846','nag430','530','3518','3518','64','67','0','-2401.18','6918.47','-1.9365','5.18482','2845:0 2847:0 2849:0 2860:0 2871:0 '), +('2847','nag431','530','3518','3518','64','67','0','-2443.4','6718.82','0.5495','4.75286','2846:0 2848:0 2870:0 '), +('2848','nag432','530','3518','3518','64','67','0','-2465.96','6485.46','18.388','4.48583','2847:0 2828:0 2829:0 '), +('2849','nag433','530','3518','3518','64','67','0','-2495.17','7067.29','-6.61379','5.16519','2845:0 2846:0 2850:0 2860:0 '), +('2850','nag434','530','3518','3518','64','67','2','-2545.1','7142.88','17.2089','5.26259','2849:0 2851:0 '), +('2851','nag435','530','3518','3626','64','67','3','-2587.61','7240.05','13.2805','5.09373','2850:0 2852:0 2853:0 '), +('2852','nag436','530','3518','3626','64','67','2','-2552.46','7299.13','13.4929','4.96416','2851:0 2855:0 '), +('2853','nag437','530','3518','3626','64','67','2','-2670.89','7210.11','23.8723','0.310654','2851:0 2854:0 '), +('2854','nag438','530','3518','3626','64','67','3','-2655.58','7280.08','30.7964','4.57931','2853:0 '), +('2855','nag439','530','3518','3626','64','67','2','-2487.77','7333.59','-15.1601','0.389211','2852:0 2856:0 '), +('2856','nag440','530','3518','3518','64','67','2','-2287.49','7419.75','-15.9035','3.46169','2855:0 2857:0 2913:0 '), +('2857','nag441','530','3518','3518','64','67','0','-2167.73','7320.37','-34.5989','5.71198','2856:0 2912:0 2858:0 '), +('2858','nag443','530','3518','3705','64','67','0','-2271.92','7267.29','-57.0529','4.8127','2859:0 2857:0 '), +('2859','nag444','530','3518','3705','64','67','0','-2261.19','7161.5','-56.5907','4.81663','2860:0 2861:0 2858:0 '), +('2860','nag445','530','3518','3518','64','67','0','-2347.48','7041.26','-12.5344','2.817','2859:0 2846:0 2849:0 '), +('2861','nag447','530','3518','3705','64','67','0','-2116.24','7209.42','-112.779','5.97431','2859:0 2862:0 '), +('2862','nag448','530','3518','3705','64','67','0','-1904.26','7175.34','-92.9691','3.12725','2863:0 2861:0 '), +('2863','nag449','530','3518','3705','64','67','0','-1985.26','6900.65','-85.609','5.34993','2862:0 2864:0 '), +('2864','nag450','530','3518','3518','64','67','0','-1848.57','6696.54','-53.151','5.40883','2863:0 2865:0 2872:0 '), +('2865','nag451','530','3518','3518','64','67','0','-1730.63','6547.09','19.5667','5.00434','2864:0 2866:0 2872:0 2885:0 2889:0 '), +('2866','nag452','530','3518','3518','64','67','0','-1929.46','6510.46','16.1863','2.96231','2865:0 2867:0 2885:0 2974:0 '), +('2867','nag453','530','3518','3638','64','67','0','-2134.61','6517.76','14.0285','6.16673','2866:0 2868:0 2869:0 2885:0 '), +('2868','nag454','530','3518','3518','64','67','0','-2312.35','6407.62','22.8307','0.6603','2867:0 2828:0 2869:0 '), +('2869','nag455','530','3518','3518','64','67','0','-2275.72','6586.73','3.55526','5.16535','2868:0 2867:0 2870:0 2877:0 '), +('2870','nag456','530','3518','3518','64','67','0','-2337.25','6706.87','-0.736603','5.08052','2869:0 2847:0 '), +('2871','nag457','530','3518','3518','64','67','0','-2230.6','6874.1','-6.45156','5.78973','2846:0 '), +('2872','nag458','530','3518','3518','64','67','0','-1596.41','6719.35','6.94102','2.95446','2865:0 2864:0 2873:0 2889:0 '), +('2873','nag459','530','3518','3518','64','67','0','-1749.49','6887.02','-31.8672','3.0919','2872:0 2874:0 2902:0 '), +('2874','nag460','530','3518','3705','64','67','0','-2033.22','6893.42','-39.0869','0.112087','2873:0 2875:0 '), +('2875','nag461','530','3518','3638','64','67','0','-2099.87','6812.26','-29.9742','0.936751','2874:0 2876:0 '), +('2876','nag462','530','3518','3638','64','67','0','-2097.69','6747.79','-3.22856','3.89457','2875:0 2877:0 '), +('2877','nag463','530','3518','3638','64','67','0','-2181.79','6690.97','-0.800369','0.623385','2876:0 2869:0 '), +('2878','nag464','530','3518','3610','64','67','0','-2569.73','6270.11','18.4891','5.91894','2827:0 2879:0 '), +('2879','nag465','530','3518','3610','64','67','0','-2480.31','6262.64','30.7263','5.11194','2878:0 2880:0 '), +('2880','nag466','530','3518','3610','64','67','0','-2447.6','6170.75','50.9756','3.42333','2879:0 2881:0 '), +('2881','nag467','530','3518','3610','64','67','0','-2505.35','6175.86','59.938','5.6892','2880:0 2882:0 '), +('2882','nag468','530','3518','3610','64','67','0','-2439.68','6122.93','84.0967','2.43372','2881:0 2883:0 '), +('2883','nag469','530','3518','3610','64','67','0','-2332.91','6161.97','53.5534','0.177271','2882:0 2884:0 '), +('2884','nag470','530','3518','3610','64','67','0','-2259.12','6100.84','76.839','2.48242','2883:0 '), +('2885','nag471','530','3518','3518','64','67','0','-1938.01','6408.13','38.4565','3.95504','2865:0 2866:0 2867:0 2886:0 2974:0 '), +('2886','nag472','530','3518','3518','64','67','0','-2080.87','6349.27','43.0054','5.9188','2885:0 2887:0 2974:0 '), +('2887','nag473','530','3518','3634','64','67','0','-1853.87','6319.73','46.4678','2.96543','2886:0 2888:0 2974:0 '), +('2888','nag474','530','3518','3518','64','67','0','-1699.25','6393.14','40.5718','0.499271','2887:0 2889:0 2973:0 '), +('2889','nag475','530','3518','3518','64','67','0','-1534.13','6557.83','19.5951','2.01038','2872:0 2888:0 2865:0 2890:0 2973:0 '), +('2890','nag476','530','3518','3518','64','67','0','-1482.81','6727.6','24.7441','1.27603','2889:0 2891:0 '), +('2891','nag477','530','3518','3518','64','67','0','-1390.93','6854.77','28.5326','0.624149','2890:0 2902:0 2899:0 '), +('2892','nag480','530','3518','3613','64','67','4','-1409.34','7112.02','33.814','1.09183','2893:0 2899:0 '), +('2893','nag481','530','3518','3613','64','67','5','-1329.62','7212.68','33.1588','0.853479','2892:0 2894:0 2901:0 '), +('2894','nag482','530','3518','3613','64','67','4','-1270.74','7314.59','33.6816','4.21027','2893:0 2895:0 '), +('2895','nag483','530','3518','3613','64','67','4','-1220.36','7373.07','33.23','4.20635','2894:0 2896:0 2897:0 '), +('2896','nag484','530','3518','3518','64','67','0','-1212.08','7477.88','22.0868','1.54857','2895:0 2908:0 2972:0 '), +('2897','nag485','530','3518','3613','64','67','4','-1182.14','7317.1','34.1095','4.44589','2895:0 2898:0 '), +('2898','nag486','530','3518','3613','64','67','4','-1195.71','7212.96','50.7515','1.47708','2897:0 2900:0 '), +('2899','nag487','530','3518','3613','64','67','4','-1266.65','6971.73','37.8915','2.73527','2891:0 2892:0 2900:0 '), +('2900','nag488','530','3518','3613','64','67','5','-1222.4','7167.59','57.2656','4.44744','2898:0 2901:0 2899:0 '), +('2901','nag489','530','3518','3613','64','67','4','-1276.87','7200.65','48.3758','5.06006','2893:0 2900:0 '), +('2902','nag491','530','3518','3518','64','67','0','-1550.75','6994.79','2.3684','5.6216','2891:0 2873:0 2903:0 '), +('2903','nag492','530','3518','3518','64','67','0','-1663.57','7019.01','0.239528','1.55716','2902:0 2904:0 2910:0 '), +('2904','nag493','530','3518','3518','64','67','0','-1624.79','7247.01','1.99533','4.52597','2903:0 2905:0 2910:0 '), +('2905','nag494','530','3518','3518','64','67','0','-1561.04','7386.09','1.02012','0.650024','2904:0 2906:0 2909:0 '), +('2906','nag495','530','3518','3518','64','67','0','-1369.87','7525.98','8.40119','6.06534','2905:0 2907:0 3014:0 '), +('2907','nag496','530','3518','3628','64','67','0','-1342.35','7749.72','-4.85434','4.51025','2906:0 2908:0 3005:0 '), +('2908','nag497','530','3518','3518','64','67','0','-1244.21','7675','9.34848','4.90297','2907:0 2896:0 2965:0 '), +('2909','nag498','530','3518','3518','64','67','0','-1822.25','7460.83','-6.36199','6.02215','2905:0 2910:0 3017:0 '), +('2910','nag499','530','3518','3518','64','67','0','-1788.32','7255.55','-7.10964','1.73702','2909:0 2904:0 2903:0 2911:0 '), +('2911','nag500','530','3518','3518','64','67','0','-1876.28','7247.82','-15.0226','2.58134','2910:0 2912:0 '), +('2912','nag501','530','3518','3518','64','67','0','-2010.2','7318.56','-33.8028','5.83525','2911:0 2857:0 '), +('2913','nag502','530','3518','3518','64','67','0','-2284.88','7613.96','-8.38883','4.91632','2856:0 2914:0 2915:0 2928:0 '), +('2914','nag503','530','3518','3518','64','67','0','-2110.77','7507.01','-30.9661','2.75255','2913:0 '), +('2915','nag504','530','3518','3518','64','67','0','-2280.53','7760.34','-25.2768','3.08634','2913:0 2916:0 2928:0 2999:0 '), +('2916','nag505','530','3518','3631','64','67','0','-2202.48','7982.31','-19.9501','4.31156','2915:0 2917:0 2930:0 2999:0 '), +('2917','nag506','530','3518','3518','64','67','0','-2164.5','8163.38','-23.2432','1.37024','2916:0 2918:0 2930:0 2995:0 '), +('2918','nag507','530','3518','3518','64','67','0','-2186.27','8355.19','-20.7293','1.74723','2917:0 2919:0 2931:0 2995:0 '), +('2919','nag508','530','3518','3518','64','67','0','-2261.53','8552.19','-18.4527','1.93573','2918:0 2920:0 2931:0 2932:0 2933:0 '), +('2920','nag509','530','3518','3631','64','67','0','-2454.9','8618.3','-25.5244','2.80359','2919:0 2921:0 2932:0 '), +('2921','nag510','530','3518','3518','64','67','0','-2638.28','8663.06','-21.4046','2.94104','2920:0 2922:0 2932:0 '), +('2922','nag511','530','3518','3631','64','67','0','-2824.97','8623.37','-27.21','4.0995','2921:0 2923:0 '), +('2923','nag512','530','3518','3518','64','67','0','-2867.65','8450.08','-30.1391','4.44507','2922:0 2924:0 '), +('2924','nag513','530','3518','3631','64','67','0','-2899.04','8250.79','-34.7269','4.57858','2923:0 2925:0 '), +('2925','nag514','530','3518','3518','64','67','0','-2888.4','8048.68','-26.7216','5.47001','2924:0 2926:0 '), +('2926','nag515','530','3518','3518','64','67','0','-2777.53','7867.47','-33.0086','5.33648','2925:0 2927:0 2984:0 '), +('2927','nag516','530','3518','3518','64','67','0','-2646.42','7719.28','-30.5333','5.53675','2926:0 2928:0 2929:0 2984:0 '), +('2928','nag517','530','3518','3518','64','67','0','-2469.37','7698.03','-17.5014','0.0114679','2915:0 2927:0 2913:0 2929:0 '), +('2929','nag518','530','3518','3631','64','67','0','-2548.6','7872.59','-53.4948','1.39927','2928:0 2927:0 2930:0 '), +('2930','nag519','530','3518','3631','64','67','0','-2377.16','8119.54','-42.3084','5.94673','2929:0 2916:0 2917:0 2931:0 '), +('2931','nag520','530','3518','3631','64','67','0','-2383.8','8337.19','-39.2254','0.68063','2930:0 2918:0 2919:0 2932:0 '), +('2932','nag521','530','3518','3631','64','67','0','-2489.29','8499.84','-36.8544','3.07375','2931:0 2920:0 2919:0 2921:0 '), +('2933','nag522','530','3518','3518','64','67','0','-2206.87','8693.03','-3.10935','4.27148','2919:0 2934:0 2985:0 '), +('2934','nag523','530','3518','3518','64','67','1','-2042.46','8705.35','18.0019','0.174061','2933:0 2935:0 '), +('2935','nag524','530','3518','3518','64','67','0','-1841.7','8722.11','25.2892','5.96245','2934:0 2936:0 '), +('2936','nag525','530','3518','3518','64','67','0','-1640.31','8750.19','32.1406','6.19414','2935:0 2937:0 2959:0 2987:0 '), +('2937','nag526','530','3518','3518','64','67','0','-1515.41','8730.96','27.2727','5.36163','2936:0 2938:0 2959:0 2940:0 '), +('2938','nag527','530','3518','3518','64','67','0','-1371.92','8548.95','11.8041','3.728','2937:0 2939:0 2944:0 2950:0 '), +('2939','nag528','530','3518','3622','64','67','0','-1460.34','8490.57','4.09139','0.714427','2938:0 2943:0 3011:0 '), +('2940','nag530','530','3518','3622','64','67','0','-1570.36','8600.16','4.98091','3.67147','2937:0 2941:0 2943:0 '), +('2941','nag531','530','3518','3622','64','67','0','-1642.07','8541.56','-12.6482','5.37185','2942:0 2940:0 '), +('2942','nag532','530','3518','3622','64','67','0','-1595.85','8476.21','-11.7076','0.541643','2941:0 2943:0 '), +('2943','nag533','530','3518','3622','64','67','0','-1526.1','8519.38','1.01318','3.57721','2942:0 2939:0 2940:0 '), +('2944','nag534','530','3518','3518','64','67','0','-1207.6','8431.13','20.2568','5.9334','2938:0 2945:0 2950:0 '), +('2945','nag535','530','3518','3518','64','67','0','-1095.6','8405.41','20.4451','5.79204','2944:0 2946:0 2950:0 '), +('2946','nag536','530','3518','3518','64','67','0','-953.793','8333.32','22.8678','0.0193606','2945:0 2947:0 2962:0 '), +('2947','nag537','530','3518','3518','64','67','0','-745.188','8393.11','33.9484','0.313885','2946:0 2948:0 '), +('2948','nag538','530','3518','3763','64','67','0','-588.034','8447.14','64.625','6.12976','2947:0 2949:0 '), +('2949','nag539','530','3518','3763','64','67','0','-494.831','8433.32','38.1295','6.07479','2948:0 2585:0 '), +('2950','nag540','530','3518','3518','64','67','0','-1168.9','8591.89','36.759','1.21709','2938:0 2944:0 2945:0 2951:0 2961:0 '), +('2951','nag541','530','3518','3617','64','67','0','-1067.77','8749.39','84.4276','1.60586','2950:0 2952:0 '), +('2952','nag542','530','3518','3617','64','67','0','-1080.66','8823.02','100.874','1.41736','2951:0 2953:0 '), +('2953','nag543','530','3518','3617','64','67','0','-1090.98','8945.42','103.447','0.588767','2952:0 2954:0 '), +('2954','nag544','530','3518','3617','64','67','0','-963.69','8887.28','146.6','3.44055','2953:0 2955:0 '), +('2955','nag545','530','3518','3617','64','67','0','-868.709','8940.17','156.257','5.74571','2954:0 2956:0 '), +('2956','nag546','530','3518','3617','64','67','0','-752.326','8861.16','182.978','4.2134','2955:0 2957:0 2958:0 '), +('2957','nag547','530','3518','3617','64','67','0','-651.777','8791','201.252','0.180777','2956:0 '), +('2958','nag553','530','3518','3617','64','67','0','-902.893','8685.38','170.054','1.29602','2956:0 '), +('2959','nag554','530','3518','3625','64','67','0','-1431.81','8885.86','36.9729','1.07333','2936:0 2937:0 2960:0 2994:0 '), +('2960','nag555','530','3518','3625','64','67','0','-1305.49','8958.42','58.5828','5.24728','2959:0 2961:0 '), +('2961','nag556','530','3518','3518','64','67','0','-1225.12','8803.57','39.6271','4.88207','2960:0 2950:0 '), +('2962','nag557','530','3518','3518','64','67','0','-968.504','8169.97','14.3663','5.25514','2946:0 2963:0 '), +('2963','nag558','530','3518','3518','64','67','0','-917.128','8072.21','21.4411','5.19624','2962:0 2964:0 2966:0 '), +('2964','nag559','530','3518','3518','64','67','0','-1020.93','7945.51','22.9619','4.026','2963:0 2965:0 2966:0 '), +('2965','nag560','530','3518','3518','64','67','0','-1146.56','7828.51','14.0417','4.1548','2964:0 2908:0 '), +('2966','nag561','530','3518','3616','64','67','0','-852.563','7887.28','40.6401','4.6339','2963:0 2964:0 2967:0 '), +('2967','nag562','530','3518','3616','64','67','0','-830.358','7797.91','38.3027','4.69279','2966:0 2968:0 '), +('2968','nag563','530','3518','3616','64','67','0','-829.954','7695.84','37.7371','5.2465','2967:0 2969:0 '), +('2969','nag564','530','3518','3616','64','67','0','-783.358','7540.05','60.9113','5.00303','2968:0 2970:0 '), +('2970','nag565','530','3518','3518','64','67','0','-763.166','7451.73','58.8323','4.56714','2969:0 2971:0 '), +('2971','nag566','530','3518','3518','64','67','0','-933.691','7394.83','34.4282','3.14949','2970:0 2972:0 '), +('2972','nag567','530','3518','3518','64','67','0','-1090.44','7395.57','33.0234','2.48583','2971:0 2896:0 '), +('2973','nag568','530','3518','3518','64','67','1','-1409.72','6385.4','38.2999','5.37255','2671:0 2888:0 2889:0 '), +('2974','nag569','530','3518','3634','64','67','0','-2075.76','6274.62','62.3128','0.289453','2866:0 2885:0 2886:0 2887:0 2975:0 '), +('2975','nag571','530','3518','3518','64','67','0','-2094.97','6179.79','87.9183','1.00219','2974:0 2976:0 '), +('2976','nag572','530','3518','3761','64','67','0','-2030.06','6071.21','119.672','4.93705','2977:0 2975:0 '), +('2977','nag573','530','3518','3761','64','67','0','-2001.22','5972.22','151.597','6.26043','2976:0 2978:0 '), +('2978','nag574','530','3518','3761','64','67','0','-1946.52','5928.39','150.948','1.91915','2977:0 '), +('2979','nag575','530','3518','3518','64','67','0','-2897.91','6988.92','-35.6052','0.23407','2843:0 2844:0 2980:0 '), +('2980','nag576','530','3518','3518','64','67','0','-2847.89','7102.5','-8.05767','2.77876','2979:0 2844:0 2981:0 '), +('2981','nag577','530','3518','3518','64','67','0','-2912.74','7175.13','-2.27885','1.70277','2980:0 2982:0 '), +('2982','nag578','530','3518','3518','64','67','0','-2935.1','7301.43','3.27707','1.53784','2981:0 2983:0 '), +('2983','nag579','530','3518','3518','64','67','0','-2902.48','7467.17','2.79586','1.43181','2982:0 2984:0 '), +('2984','nag580','530','3518','3518','64','67','0','-2802.19','7645.35','-6.45531','0.870248','2983:0 2926:0 2927:0 '), +('2985','nag581','530','3518','3633','64','67','0','-2196.09','8827.81','16.1506','4.59461','2933:0 2986:0 '), +('2986','nag582','530','3518','3633','64','67','0','-2071.65','8894.1','28.0157','0.506613','2985:0 2987:0 '), +('2987','nag583','530','3518','3518','64','67','0','-1884.01','8889.41','36.2099','5.7138','2936:0 2986:0 2988:0 '), +('2988','nag584','530','3518','3518','64','67','0','-1916.54','9091.13','52.9111','1.74753','2987:0 2989:0 '), +('2989','nag585','530','3518','3624','64','67','0','-1849.22','9237.75','70.9999','5.57243','2988:0 2990:0 '), +('2990','nag586','530','3518','3624','64','67','0','-1755.41','9160.76','79.5049','5.59599','2989:0 2991:0 '), +('2991','nag587','530','3518','3518','64','67','0','-1651.3','9137.62','86.3008','5.99261','2990:0 2992:0 '), +('2992','nag588','530','3518','3518','64','67','0','-1545.69','9099.13','82.8122','5.48996','2991:0 2993:0 '), +('2993','nag589','530','3518','3625','64','67','0','-1448.69','9002.9','57.299','5.25434','2992:0 2994:0 '), +('2994','nag590','530','3518','3625','64','67','0','-1419.87','8948.03','58.9144','6.12613','2993:0 2959:0 '), +('2995','nag591','530','3518','3518','64','67','0','-2052.37','8225.22','-6.54776','2.94922','2917:0 2918:0 2996:0 '), +('2996','nag592','530','3518','3518','64','67','0','-1986.35','8210.53','0.797534','4.84989','2995:0 2997:0 3000:0 '), +('2997','nag593','530','3518','3518','64','67','0','-2024.61','8154.98','1.25254','4.1077','2996:0 2998:0 '), +('2998','nag594','530','3518','3518','64','67','0','-2103.83','8063.36','2.18912','4.10771','2997:0 2999:0 '), +('2999','nag595','530','3518','3518','64','67','0','-2156.48','7911.49','-11.2008','4.37866','2998:0 2916:0 2915:0 '), +('3000','nag596','530','3518','3628','64','67','0','-1840.26','7999.74','-24.7446','2.3484','2996:0 3001:0 '), +('3001','nag597','530','3518','3628','64','67','0','-1746.01','7996.18','-27.0075','3.06865','3000:0 3002:0 '), +('3002','nag598','530','3518','3628','64','67','0','-1653.08','7993.42','-26.6535','6.25344','3001:0 3006:0 '), +('3003','nag600','530','3518','3628','64','67','0','-1550.22','7929.2','-21.6393','6.03352','3004:0 3006:0 '), +('3004','nag601','530','3518','3628','64','67','0','-1483.84','7905.42','-19.2864','5.65261','3003:0 3005:0 '), +('3005','nag602','530','3518','3628','64','67','0','-1404.2','7842.35','-18.1886','5.40914','3004:0 2907:0 '), +('3006','nag603','530','3518','3628','64','67','0','-1554.58','7990.68','-21.1856','3.99937','3002:0 3003:0 3007:0 3008:0 '), +('3007','nag604','530','3518','3628','64','67','0','-1601.1','7865.59','-22.0744','4.70386','3012:0 3006:0 '), +('3008','nag605','530','3518','3628','64','67','0','-1478.18','8079.45','-21.4921','0.88839','3009:0 3006:0 '), +('3009','nag606','530','3518','3518','64','67','0','-1517.43','8229.34','-15.3084','1.82694','3008:0 3010:0 '), +('3010','nag607','530','3518','3622','64','67','0','-1508.91','8347.15','-12.1475','1.98089','3009:0 3011:0 '), +('3011','nag608','530','3518','3622','64','67','0','-1502.91','8432.5','-0.939562','0.853047','3010:0 2939:0 '), +('3012','nag609','530','3518','3628','64','67','0','-1604.12','7762.81','-21.7436','1.48608','3007:0 3013:0 3015:0 '), +('3013','nag610','530','3518','3518','64','67','0','-1587.52','7621.15','-10.2326','4.91826','3012:0 3014:0 3015:0 '), +('3014','nag611','530','3518','3518','64','67','0','-1475.29','7596.72','-5.21911','5.69972','3013:0 2906:0 3015:0 '), +('3015','nag612','530','3518','3628','64','67','0','-1647.6','7686.9','-14.3996','0.653543','3013:0 3014:0 3012:0 3016:0 '), +('3016','nag613','530','3518','3518','64','67','0','-1703.41','7669.14','-14.3192','3.85013','3015:0 3017:0 '), +('3017','nag614','530','3518','3518','64','67','0','-1781.28','7605.53','-8.7957','3.82657','3016:0 2909:0 '), +('3018','zang615','530','3521','3521','64','67','0','794.211','5932.33','57.6977','1.47191','2647:0 2648:0 2670:0 3019:0 '), +('3019','zang616','530','3521','3521','64','67','0','820.771','6049.46','79.8283','4.84129','3018:0 3020:0 '), +('3020','bedg617','530','3522','3767','65','70','0','882.952','5919.73','115.632','0.505885','3019:0 3021:0 '), +('3021','bedg618','530','3522','3767','65','70','0','942.091','5960.21','121.279','0.600133','3020:0 3022:0 '), +('3022','bedg619','530','3522','3522','65','70','0','1067.62','6060.89','132.716','0.256129','3021:0 3023:0 '), +('3023','bedg620','530','3522','3522','65','70','0','1132.44','6081.19','153.605','5.97461','3022:0 3024:0 '), +('3024','bedg621','530','3522','3522','65','70','0','1202.41','6057.05','164.269','5.95105','3023:0 3025:0 '), +('3025','bedg622','530','3522','3522','65','70','0','1278.35','5993.43','168.127','5.58584','3024:0 3026:0 '), +('3026','bedg623','530','3522','3522','65','70','0','1406.14','6036.86','152.292','0.476827','3025:0 3027:0 '), +('3027','bedg624','530','3522','3522','65','70','0','1466.01','6069.6','133.93','6.17882','3026:0 3028:0 '), +('3028','bedg625','530','3522','3522','65','70','0','1591.09','6047.48','128.651','6.21023','3027:0 3029:0 '), +('3029','bedg626','530','3522','3768','65','70','0','1713.44','6043.35','143.923','6.2495','3028:0 3030:0 3049:0 '), +('3030','bedg627','530','3522','3768','65','70','0','1762.72','5869.71','155.631','4.98895','3029:0 3031:0 3049:0 '), +('3031','bedg628','530','3522','3833','65','70','0','1802.41','5792.15','188.129','6.12384','3030:0 3032:0 '), +('3032','bedg629','530','3522','3833','65','70','0','1865.39','5786.05','215.307','4.78866','3031:0 3033:0 '), +('3033','bedg631','530','3522','3833','65','70','0','1882.59','5668.18','256.167','1.82379','3034:0 3037:0 3032:0 '), +('3034','bedg632','530','3522','3833','65','70','0','2046.5','5630.87','263.828','6.03743','3033:0 3035:0 3036:0 3037:0 3040:0 '), +('3035','bedg633','530','3522','3922','65','70','0','2186.78','5679.45','267.903','1.53317','3034:0 3236:0 '), +('3036','bedg634','530','3522','3833','65','70','0','2078.91','5730.76','265.935','3.74408','3034:0 3037:0 '), +('3037','bedg635','530','3522','3833','65','70','0','1986.76','5663.91','265.451','5.29919','3036:0 3034:0 3033:0 3038:0 '), +('3038','bedg636','530','3522','3918','65','70','0','1882.53','5595.86','256.211','0.429695','3037:0 3039:0 '), +('3039','bedg637','530','3522','3918','65','70','3','1913.85','5539.89','265.641','4.76903','3038:0 3040:0 '), +('3040','bedg638','530','3522','3918','65','70','3','1980.47','5572.14','258.131','2.97243','3039:0 3034:0 3041:0 '), +('3041','bedg639','530','3522','3833','65','70','0','1818.29','5603','261.471','3.82655','3040:0 3042:0 3048:0 '), +('3042','bedg640','530','3522','3918','65','70','0','1800.72','5550.99','267.48','2.61311','3041:0 3043:0 3047:0 '), +('3043','bedg641','530','3522','3919','65','70','0','1708.89','5566.11','265.641','3.36316','3042:0 3047:0 3235:0 '), +('3044','bedg643','530','3522','3919','65','70','0','1564.28','5423.88','265.088','1.83317','3045:0 3235:0 '), +('3045','bedg644','530','3522','3919','65','70','0','1534.04','5541.59','258.021','1.3761','3044:0 3046:0 '), +('3046','bedg645','530','3522','3919','65','70','0','1599.86','5605.47','266.783','0.413989','3045:0 3047:0 '), +('3047','bedg646','530','3522','3919','65','70','0','1692.34','5649.63','265.597','5.29918','3046:0 3043:0 3042:0 3048:0 '), +('3048','bedg647','530','3522','3919','65','70','0','1775.77','5678.9','267.502','5.25597','3041:0 3047:0 '), +('3049','bedg648','530','3522','3768','65','70','0','1827.31','6004.38','138.57','2.3814','3029:0 3030:0 3050:0 '), +('3050','bedg649','530','3522','3768','65','70','0','1934.89','5992.25','140.661','0.472881','3049:0 3051:0 3087:0 '), +('3051','bedg650','530','3522','3768','65','70','0','2035.37','6038.3','145.555','3.92079','3050:0 3052:0 3087:0 '), +('3052','bedg651','530','3522','3768','65','70','0','2098.1','5939.59','137.134','5.31486','3051:0 3053:0 3087:0 '), +('3053','bedg652','530','3522','3831','65','70','0','2140.78','5883.29','138.074','5.36983','3052:0 3054:0 '), +('3054','bedg653','530','3522','3831','65','70','0','2169.87','5811.41','120.231','1.97692','3053:0 3055:0 '), +('3055','bedg654','530','3522','3831','65','70','0','2156.44','5731.77','120.753','4.55303','3054:0 3056:0 '), +('3056','bedg655','530','3522','3831','65','70','0','2209.5','5671.79','127.813','5.09887','3055:0 3057:0 '), +('3057','bedg656','530','3522','3831','65','70','0','2242.52','5586.84','144.63','4.98106','3056:0 3058:0 '), +('3058','bedg657','530','3522','3831','65','70','0','2242.64','5516.54','161.969','4.74938','3057:0 3059:0 '), +('3059','bedg658','530','3522','3831','65','70','0','2266.32','5460.89','147.107','3.73228','3058:0 3060:0 3061:0 '), +('3060','bedg659','530','3522','3831','65','70','0','2243.22','5409.52','144.241','3.16286','3061:0 3059:0 '), +('3061','bedg660','530','3522','3831','65','70','0','2184.26','5427.04','144.314','3.26496','3060:0 3062:0 3059:0 '), +('3062','bedg661','530','3522','3831','65','70','0','2056.91','5452.67','144.871','3.87757','3061:0 3063:0 '), +('3063','bedg662','530','3522','3831','65','70','0','1986.16','5372.79','148.912','3.58305','3062:0 3064:0 '), +('3064','bedg663','530','3522','3831','65','70','0','1869.19','5342.55','144.553','3.39455','3063:0 3065:0 '), +('3065','bedg664','530','3522','3831','65','70','0','1844.86','5256.68','138.071','1.36038','3064:0 3066:0 '), +('3066','bedg666','530','3522','3831','65','70','0','1882.44','5202','150.044','6.2809','3065:0 3067:0 '), +('3067','bedg668','530','3522','3831','65','70','0','1942.47','5157.81','163.104','1.47426','3066:0 3068:0 '), +('3068','bedg670','530','3522','3831','65','70','0','1859.25','5091.18','146.37','4.69048','3067:0 3069:0 '), +('3069','bedg672','530','3522','3831','65','70','0','1892.19','4998.98','146.563','5.3777','3070:0 3068:0 '), +('3070','bedg673','530','3522','3827','65','70','0','1882.75','4918.05','144.661','4.59623','3069:0 3071:0 3079:0 '), +('3071','bedg674','530','3522','3827','65','70','0','1970.05','4876.31','143.046','5.83716','3070:0 3072:0 3078:0 '), +('3072','bedg675','530','3522','3844','65','70','0','2066.28','4886.71','148.962','0.107673','3071:0 3073:0 '), +('3073','bedg676','530','3522','3844','65','70','0','2113.18','4914.06','149.085','6.14739','3072:0 3074:0 '), +('3074','bedg677','530','3522','3827','65','70','0','2197.61','4955.01','153.824','2.65786','3073:0 3075:0 3232:0 '), +('3075','bedg678','530','3522','3844','65','70','5','2122.42','4767.11','145.442','4.39595','3074:0 3076:0 3077:0 '), +('3076','bedg679','530','3522','3844','65','70','5','2051.79','4712.94','149.377','4.14775','3075:0 '), +('3077','bedg680','530','3522','3844','65','70','4','2045.43','4756.7','142.832','3.24454','3075:0 3078:0 '), +('3078','bedg681','530','3522','3827','65','70','0','1924.25','4753.97','143.374','3.15815','3077:0 3071:0 3079:0 '), +('3079','bedg682','530','3522','3827','65','70','0','1818.76','4741.32','143.599','3.17386','3078:0 3080:0 3081:0 3070:0 '), +('3080','bedg683','530','3522','3783','65','70','0','1763.5','4636.75','148.572','2.17246','3081:0 3079:0 '), +('3081','bedg684','530','3522','3827','65','70','0','1721.08','4751.46','141.246','2.47877','3080:0 3082:0 3079:0 '), +('3082','bedg685','530','3522','3827','65','70','0','1671.81','4805.51','144.067','1.15537','3081:0 3083:0 '), +('3083','bedg686','530','3522','3827','65','70','0','1716.64','4889.56','168.716','1.08076','3082:0 3084:0 '), +('3084','bedg690','530','3522','3779','65','70','0','1764.57','5020.59','169.786','2.98533','3083:0 3085:0 '), +('3085','bedg691','530','3522','3779','65','70','0','1689.52','5029.67','171.17','6.17405','3086:0 3084:0 '), +('3086','bedg692','530','3522','3779','65','70','0','1644.1','5086.59','174.824','5.08627','3085:0 '), +('3087','bedg693','530','3522','3768','65','70','0','2051.36','6085.99','147.268','1.58417','3050:0 3051:0 3052:0 3088:0 3094:0 '), +('3088','bedg694','530','3522','3768','65','70','0','2163.29','6091.67','144.685','6.16931','3087:0 3089:0 3093:0 '), +('3089','bedg695','530','3522','3769','65','70','5','2276.18','6049.52','143.141','5.80411','3088:0 3090:0 '), +('3090','bedg696','530','3522','3769','65','70','4','2338.36','6031.21','142.438','2.8196','3089:0 3091:0 '), +('3091','bedg697','530','3522','3769','65','70','5','2384.95','6054.49','138.162','1.60222','3090:0 3092:0 '), +('3092','bedg698','530','3522','3768','65','70','0','2372.13','6149.95','128.725','3.20443','3091:0 3093:0 3160:0 '), +('3093','bedg699','530','3522','3768','65','70','0','2265.65','6155.81','138.179','3.41256','3092:0 3088:0 '), +('3094','bedg700','530','3522','3768','65','70','0','2028.28','6216.03','134.538','4.67706','3087:0 3095:0 '), +('3095','bedg701','530','3522','3771','65','70','0','2041.4','6592.18','135.807','3.54607','3094:0 3096:0 3102:0 '), +('3096','bedg702','530','3522','3772','65','70','3','2066.72','6735.02','154.854','4.59066','3095:0 3097:0 3101:0 '), +('3097','bedg703','530','3522','3772','65','70','2','1972.28','6779.92','162.128','1.46478','3096:0 3098:0 '), +('3098','bedg704','530','3522','3772','65','70','2','1959.55','6842.15','158.876','3.29476','3097:0 3099:0 3105:0 '), +('3099','bedg705','530','3522','3772','65','70','3','2065.72','6849.09','172.562','0.0589211','3098:0 3100:0 '), +('3100','bedg706','530','3522','3772','65','70','2','2135.82','6829.36','173.431','4.65351','3099:0 3101:0 '), +('3101','bedg707','530','3522','3772','65','70','2','2140.28','6753.83','165.006','3.40472','3100:0 3096:0 '), +('3102','bedg708','530','3522','3771','65','70','0','1952.24','6615.86','143.542','5.85751','3095:0 3103:0 3104:0 '), +('3103','bedg709','530','3522','3771','65','70','0','1869.68','6608.28','143.783','2.00906','3102:0 3104:0 '), +('3104','bedg710','530','3522','3771','65','70','0','1867.3','6714.18','142.554','2.81017','3102:0 3103:0 3105:0 '), +('3105','bedg711','530','3522','3771','65','70','0','1794.79','6806.46','137.334','0.147653','3104:0 3098:0 3106:0 3125:0 '), +('3106','bedg712','530','3522','3782','65','70','0','1698.35','6823.85','136.031','2.50777','3105:0 3107:0 3112:0 3113:0 '), +('3107','bedg713','530','3522','3782','65','70','0','1660.38','6861.71','142.107','2.01886','3106:0 3108:0 '), +('3108','bedg714','530','3522','3782','65','70','0','1628.86','6909.46','152.556','2.54312','3107:0 3109:0 '), +('3109','bedg715','530','3522','3782','65','70','0','1565.97','6934.47','157.679','0.579622','3108:0 3110:0 '), +('3110','bedg716','530','3522','3782','65','70','0','1642.57','6995.64','157.952','6.01458','3109:0 3111:0 '), +('3111','bedg717','530','3522','3782','65','70','0','1706.57','6967.44','152.552','5.15456','3110:0 3112:0 '), +('3112','bedg718','530','3522','3782','65','70','0','1749.11','6892.32','140.635','4.05192','3111:0 3106:0 '), +('3113','bedg719','530','3522','3782','65','70','0','1636.12','6812.12','131.714','0.187751','3106:0 3114:0 3124:0 '), +('3114','bedg720','530','3522','3782','65','70','0','1569.95','6826.69','128.23','3.23117','3113:0 3115:0 '), +('3115','bedg721','530','3522','3522','65','70','0','1487.42','6820.82','107.218','2.83454','3114:0 3116:0 '), +('3116','bedg722','530','3522','3522','65','70','0','1418.63','6847.18','110.541','2.77564','3115:0 3117:0 '), +('3117','bedg723','530','3522','3522','65','70','0','1357.84','6881.95','95.233','5.88189','3116:0 3118:0 '), +('3118','bedg724','530','3522','3522','65','70','0','1305.01','6962.51','93.0961','3.03875','3117:0 3119:0 '), +('3119','bedg725','530','3522','3522','65','70','0','1224.71','6978.54','90.3881','2.59893','3118:0 3120:0 '), +('3120','bedg726','530','3522','3522','65','70','0','1145.67','7048.89','113.579','2.41436','3119:0 3121:0 '), +('3121','bedg727','530','3522','3522','65','70','2','1107.87','7095.02','122.35','3.18012','3120:0 3122:0 '), +('3122','bedg728','530','3522','3770','65','70','2','1053.11','7098.72','116.217','0.376251','3121:0 3123:0 '), +('3123','bedg729','530','3522','3770','65','70','2','1020.4','7173.65','86.2439','1.66823','3122:0 2628:0 '), +('3124','bedg730','530','3522','3824','65','70','0','1647.1','6732.39','116.704','4.75248','3113:0 3125:0 '), +('3125','bedg731','530','3522','3771','65','70','0','1740.86','6736.89','134.249','0.774434','3124:0 3105:0 3126:0 '), +('3126','bedg732','530','3522','3824','65','70','0','1628.32','6690.04','108.29','6.13869','3125:0 3127:0 '), +('3127','bedg733','530','3522','3824','65','70','0','1723.61','6678.08','87.8644','5.8481','3126:0 3128:0 '), +('3128','bedg734','530','3522','3824','65','70','0','1772.91','6642.19','74.5483','2.51408','3127:0 3129:0 '), +('3129','bedg735','530','3522','3824','65','70','0','1619.66','6621.4','33.3376','3.64899','3128:0 3130:0 '), +('3130','bedg736','530','3522','3824','65','70','0','1761.7','6532.98','4.13407','5.72636','3129:0 3131:0 3263:0 '), +('3131','bedg737','530','3522','3824','65','70','0','1936.85','6455.82','1.98027','5.87558','3130:0 3132:0 3261:0 '), +('3132','bedg738','530','3522','3824','65','70','0','2158.79','6394.21','-10.3376','6.01302','3131:0 3133:0 '), +('3133','bedg739','530','3522','3931','65','70','0','2352.32','6410.86','-10.3378','0.246553','3132:0 3134:0 3136:0 '), +('3134','bedg740','530','3522','3773','65','70','0','2401.13','6465.38','3.57212','0.140524','3133:0 3135:0 '), +('3135','bedg741','530','3522','3773','65','70','0','2489.83','6479','-6.94094','0.898433','3134:0 3136:0 3137:0 '), +('3136','bedg742','530','3522','3931','65','70','0','2486.13','6407.7','-10.338','3.05435','3135:0 3133:0 3143:0 '), +('3137','bedg743','530','3522','3773','65','70','0','2546.35','6560.09','1.844','4.13898','3135:0 3138:0 3139:0 3140:0 3142:0 '), +('3138','bedg744','530','3522','3773','65','70','0','2667.31','6519.19','0.94932','5.95717','3137:0 '), +('3139','bedg745','530','3522','3773','65','70','0','2613.6','6682.32','23.1547','1.02881','3137:0 3142:0 '), +('3140','bedg746','530','3522','3773','65','70','0','2376.02','6637.4','10.9828','6.02394','3137:0 3141:0 '), +('3141','bedg747','530','3522','3773','65','70','0','2482.22','6684.55','0.72676','5.48987','3140:0 3142:0 '), +('3142','bedg748','530','3522','3773','65','70','0','2570.07','6610.1','7.6561','4.0683','3141:0 3137:0 3139:0 '), +('3143','bedg749','530','3522','3931','65','70','0','2625.59','6357.13','-10.3379','0.0313585','3136:0 3144:0 3150:0 '), +('3144','bedg750','530','3522','3862','65','70','0','2747.24','6390.91','2.251','5.27782','3143:0 3145:0 '), +('3145','bedg751','530','3522','3862','65','70','0','2871.51','6233.27','11.692','5.37992','3144:0 3146:0 3150:0 '), +('3146','bedg752','530','3522','3862','65','70','0','2980.33','6146.77','4.99801','5.25426','3145:0 3147:0 '), +('3147','bedg753','530','3522','3826','65','70','0','3036.46','6073.22','0.772358','5.36224','3146:0 3148:0 '), +('3148','bedg754','530','3522','3826','65','70','0','3098.88','6022.32','1.18446','4.5042','3147:0 3149:0 '), +('3149','bedg755','530','3522','3826','65','70','0','3081.27','5967.39','-10.3385','2.55249','3148:0 3150:0 3154:0 '), +('3150','bedg756','530','3522','3825','65','70','0','2759.83','6108.29','-10.3396','6.08285','3145:0 3149:0 3143:0 3151:0 3157:0 '), +('3151','bedg757','530','3522','3825','65','70','1','2685.12','5895.26','-16.1123','4.71862','3150:0 3152:0 '), +('3152','bedg758','530','3522','3825','65','70','0','2687.78','5785.12','-16.8636','4.73433','3151:0 3153:0 '), +('3153','bedg759','530','3522','3904','65','70','0','2695.75','5583.37','-10.6374','4.75001','3152:0 '), +('3154','bedg760','530','3522','3826','65','70','0','3126.83','5854.78','-10.3341','5.52444','3149:0 3155:0 3156:0 '), +('3155','bedg761','530','3522','3826','65','70','0','3287.4','5738.99','-10.3325','5.79933','3154:0 3156:0 3161:0 '), +('3156','bedg762','530','3522','3826','65','70','0','3281.88','5854.49','-1.4584','3.21929','3155:0 3154:0 3161:0 '), +('3157','bedg763','530','3522','3825','65','70','0','2582.1','6136.86','24.0804','4.49162','3150:0 3158:0 '), +('3158','bedg765','530','3522','3825','65','70','0','2521.32','6015.69','84.9349','2.22967','3157:0 3159:0 '), +('3159','bedg767','530','3522','3768','65','70','0','2494.15','6124.82','111.3','1.62492','3160:0 3158:0 '), +('3160','bedg768','530','3522','3768','65','70','0','2427.5','6152.5','122.621','2.74804','3159:0 3092:0 '), +('3161','bedg769','530','3522','3826','65','70','0','3383.33','5729.17','-10.3389','3.41014','3155:0 3156:0 3162:0 3163:0 3172:0 '), +('3162','bedg770','530','3522','3777','65','70','0','3452.63','5809.98','1.12407','0.865436','3161:0 3164:0 '), +('3163','bedg771','530','3522','3826','65','70','0','3520','5691.54','-10.3386','6.05377','3161:0 3164:0 '), +('3164','bedg772','530','3522','3777','65','70','0','3523.18','5795.46','1.6669','1.53041','3163:0 3162:0 3165:0 '), +('3165','bedg773','530','3522','3826','65','70','0','3589.57','5684.17','-10.2714','6.25066','3164:0 3166:0 '), +('3166','bedg774','530','3522','3774','65','70','0','3660.48','5491.29','-20.7025','5.0647','3165:0 3171:0 '), +('3167','bedg775','530','3522','3774','65','70','0','3641.58','5308.33','-20.5148','5.3867','3168:0 3171:0 '), +('3168','bedg776','530','3522','3774','65','70','0','3587.11','5221.49','14.2925','1.80371','3169:0 3167:0 '), +('3169','bedg777','530','3522','3774','65','70','0','3438.2','5241.29','-6.61032','0.307527','3168:0 '), +('3170','bedg779','530','3522','3774','65','70','0','3593.15','5382.62','-9.51038','0.0970404','3171:0 '), +('3171','bedg780','530','3522','3774','65','70','0','3651.06','5374.99','-20.2357','1.49502','3170:0 3166:0 3167:0 '), +('3172','bedg781','530','3522','3826','65','70','0','3233.27','5636.21','38.3515','5.69295','3161:0 3173:0 '), +('3173','bedg782','530','3522','3826','65','70','0','3303.69','5597.03','55.3332','6.0071','3172:0 3174:0 '), +('3174','bedg783','530','3522','3826','65','70','0','3374.58','5579.62','85.0975','4.19284','3173:0 3175:0 '), +('3175','bedg784','530','3522','3828','65','70','0','3306.73','5468.57','141.818','6.03852','3174:0 3176:0 3179:0 '), +('3176','bedg785','530','3522','3828','65','70','0','3389.48','5461.86','145.745','4.75047','3175:0 3177:0 '), +('3177','bedg786','530','3522','3829','65','70','0','3363.88','5342.13','147.792','3.91795','3176:0 3178:0 '), +('3178','bedg787','530','3522','3829','65','70','0','3286','5287.73','147.247','2.3825','3177:0 3179:0 3182:0 '), +('3179','bedg788','530','3522','3828','65','70','0','3187.06','5468.2','146.95','2.07226','3178:0 3175:0 3180:0 '), +('3180','bedg789','530','3522','3828','65','70','0','3116.13','5482.01','145.293','4.71905','3179:0 3181:0 3183:0 '), +('3181','bedg790','530','3522','3829','65','70','0','3130.15','5332.98','148.604','4.94682','3180:0 3182:0 '), +('3182','bedg791','530','3522','3829','65','70','0','3215.6','5360.26','142.29','5.42984','3181:0 3178:0 '), +('3183','bedg792','530','3522','3828','65','70','0','2956.95','5583.27','146.351','5.04498','3180:0 3184:0 3186:0 '), +('3184','bedg793','530','3522','3952','65','70','0','2952.85','5779.65','134.692','1.27116','3183:0 3185:0 '), +('3185','bedg794','530','3522','3952','65','70','0','3050.1','5991.54','130.725','1.20636','3184:0 3238:0 '), +('3186','bedg795','530','3522','3951','65','70','1','2949.59','5525.69','144.16','2.62095','3183:0 3187:0 '), +('3187','bedg796','530','3522','3828','65','70','0','2915.37','5365.92','147.713','1.64902','3186:0 3188:0 '), +('3188','bedg797','530','3522','3867','65','70','0','2973.41','5294.89','183.643','6.14688','3187:0 3189:0 '), +('3189','bedg798','530','3522','3867','65','70','0','3038.23','5281.8','219.214','4.54468','3188:0 3190:0 '), +('3190','bedg799','530','3522','3867','65','70','0','3021.8','5214.18','250.296','5.00414','3189:0 3191:0 '), +('3191','bedg800','530','3522','3867','65','70','0','3082.54','5063.01','264.326','4.87847','3190:0 3192:0 3211:0 3212:0 '), +('3192','bedg801','530','3522','3867','65','70','0','3099.95','4980.23','265.505','6.22935','3191:0 3193:0 3218:0 3221:0 '), +('3193','bedg802','530','3522','3867','65','70','0','3304.97','4970.79','264.838','5.68742','3192:0 3194:0 3210:0 '), +('3194','bedg803','530','3522','3867','65','70','0','3434.24','4937.21','262.546','5.47143','3193:0 3195:0 3209:0 3210:0 '), +('3195','bedg804','530','3522','3867','65','70','0','3574.68','4758.04','240.131','5.37718','3194:0 3196:0 3202:0 '), +('3196','bedg805','530','3522','3863','65','70','0','3564.29','4641.89','228.617','3.77105','3195:0 3197:0 '), +('3197','bedg806','530','3522','3863','65','70','0','3459.92','4594.26','206.487','3.08147','3196:0 3198:0 3199:0 '), +('3198','bedg807','530','3522','3962','65','70','0','3346.24','4594.8','221.66','2.70055','3197:0 '), +('3199','bedg808','530','3522','3863','65','70','0','3393.49','4543.75','179.957','4.49519','3197:0 3200:0 '), +('3200','bedg809','530','3522','3863','65','70','0','3360.85','4462.38','157.372','4.73472','3199:0 3201:0 3269:0 '), +('3201','bedg810','530','3522','3863','65','70','0','3368.28','4367.62','122.375','1.75021','3200:0 3269:0 '), +('3202','bedg811','530','3522','3866','65','70','0','3701.21','4770.37','244.343','1.75806','3195:0 3203:0 '), +('3203','bedg812','530','3522','3866','65','70','0','3761.47','4802.74','254.61','1.73842','3202:0 3204:0 '), +('3204','bedg813','530','3522','3866','65','70','0','3738.29','4879.52','256.491','2.75944','3203:0 3205:0 '), +('3205','bedg814','530','3522','3866','65','70','0','3668.47','4898.18','260.046','1.56956','3204:0 3206:0 '), +('3206','bedg815','530','3522','3866','65','70','0','3668.33','5008.48','266.738','4.7465','3205:0 3207:0 3208:0 '), +('3207','bedg816','530','3522','3866','65','70','0','3732.68','5010.06','273.797','4.86824','3206:0 '), +('3208','bedg817','530','3522','3866','65','70','0','3581.65','5022.38','265.138','6.01885','3206:0 3209:0 '), +('3209','bedg818','530','3522','3866','65','70','0','3523.16','4984.99','269.146','3.77261','3208:0 3194:0 '), +('3210','bedg819','530','3522','3867','65','70','0','3312.28','5066.6','254.195','4.73865','3193:0 3194:0 3211:0 '), +('3211','bedg820','530','3522','3867','65','70','0','3243.07','5149.71','260.466','2.97621','3210:0 3191:0 '), +('3212','bedg821','530','3522','3942','65','70','0','2961.39','5151.76','265.026','5.66383','3191:0 3213:0 3217:0 '), +('3213','bedg822','530','3522','3942','65','70','0','2940.58','5217.19','264.835','2.79713','3212:0 3214:0 '), +('3214','bedg823','530','3522','3942','65','70','0','2844.64','5234.99','267.298','3.70426','3213:0 3215:0 '), +('3215','bedg824','530','3522','3942','65','70','0','2724.25','5157.06','265.076','3.71605','3214:0 3216:0 3222:0 '), +('3216','bedg825','530','3522','3867','65','70','0','2816.5','5099.67','265.683','5.72666','3215:0 3217:0 '), +('3217','bedg826','530','3522','3867','65','70','0','2900.18','5096.37','265.127','0.775502','3212:0 3216:0 3218:0 3221:0 '), +('3218','bedg827','530','3522','3787','65','70','0','2929.12','4947.5','266.731','4.91062','3192:0 3217:0 3219:0 3221:0 '), +('3219','bedg828','530','3522','3787','65','70','0','2898.72','4782.92','277.957','4.52971','3218:0 3220:0 '), +('3220','bedg829','530','3522','3787','65','70','0','2984.26','4824.35','278.892','1.56875','3219:0 3221:0 '), +('3221','bedg830','530','3522','3787','65','70','0','2852.35','4933.54','267.974','6.12327','3220:0 3218:0 3192:0 3217:0 '), +('3222','bedg831','530','3522','3833','65','70','0','2545.57','5293.37','266.598','6.1005','3215:0 3223:0 3237:0 '), +('3223','bedg832','530','3522','3833','65','70','0','2456.71','5321.66','264.923','2.73899','3222:0 3224:0 3230:0 3237:0 '), +('3224','bedg833','530','3522','3833','65','70','0','2282.88','5171.44','264.676','3.69325','3223:0 3225:0 3230:0 '), +('3225','bedg834','530','3522','3953','65','70','0','2194.7','5122.32','258.602','4.28623','3224:0 3226:0 '), +('3226','bedg835','530','3522','3953','65','70','0','2189.16','5051.76','252.603','2.1395','3225:0 3227:0 '), +('3227','bedg836','530','3522','3953','65','70','0','2116.51','5085.08','256.974','5.94279','3226:0 3228:0 3231:0 '), +('3228','bedg837','530','3522','3953','65','70','0','2090.4','5162.61','265.04','2.37496','3227:0 3229:0 3233:0 '), +('3229','bedg838','530','3522','3953','65','70','0','2063.34','5235.11','265.179','0.122197','3228:0 3230:0 '), +('3230','bedg839','530','3522','3833','65','70','0','2316.24','5243.18','261.206','0.031876','3229:0 3224:0 3223:0 '), +('3231','bedg840','530','3522','3953','65','70','0','2089.3','5022.57','223.769','0.835746','3227:0 3232:0 '), +('3232','bedg841','530','3522','3953','65','70','0','2164.72','5005.6','178.623','5.23199','3231:0 3074:0 '), +('3233','bedg842','530','3522','3953','65','70','0','1740.09','5133.27','265.058','3.33132','3228:0 3234:0 '), +('3234','bedg843','530','3522','3919','65','70','0','1674.61','5254.04','265.321','1.92546','3233:0 3235:0 '), +('3235','bedg844','530','3522','3919','65','70','0','1612.29','5464.5','265.941','1.8587','3234:0 3044:0 3043:0 '), +('3236','bedg845','530','3522','3833','65','70','0','2395.42','5588.73','267.895','5.88623','3035:0 3237:0 '), +('3237','bedg846','530','3522','3833','65','70','0','2397.69','5390.93','264.287','4.72385','3236:0 3223:0 3222:0 '), +('3238','bedg847','530','3522','3954','65','70','0','3094.62','6101.88','130.589','1.3065','3185:0 3239:0 '), +('3239','bedg848','530','3522','3954','65','70','0','3120.37','6181.58','137.697','1.30738','3238:0 3240:0 '), +('3240','bedg849','530','3522','3954','65','70','0','3147.57','6248.67','124.212','0.486635','3239:0 3241:0 '), +('3241','bedg850','530','3522','3954','65','70','0','3217.19','6289.41','124.654','1.62939','3240:0 3242:0 '), +('3242','bedg851','530','3522','3954','65','70','0','3215.71','6365.94','119.805','1.02856','3241:0 3243:0 '), +('3243','bedg852','530','3522','3954','65','70','0','3279.94','6464.48','149.929','0.887189','3242:0 3244:0 '), +('3244','bedg853','530','3522','3830','65','70','0','3323.9','6505.86','159.443','0.796868','3243:0 3245:0 3260:0 '), +('3245','bedg854','530','3522','3781','65','70','0','3488.68','6673.14','148.361','0.895043','3244:0 3246:0 3260:0 '), +('3246','bedg855','530','3522','3781','65','70','0','3566.67','6807.32','137.289','2.01031','3245:0 3247:0 3256:0 '), +('3247','bedg856','530','3522','3830','65','70','0','3446.01','7089.42','152.831','1.97497','3246:0 3248:0 3250:0 3251:0 '), +('3248','bedg857','530','3522','3830','65','70','0','3286.14','7143.51','168.873','3.41617','3247:0 3249:0 '), +('3249','bedg858','530','3522','3830','65','70','0','3303.87','6962.97','164.125','0.482707','3248:0 3250:0 '), +('3250','bedg859','530','3522','3830','65','70','0','3360.25','6980.78','163.409','0.305992','3249:0 3247:0 '), +('3251','bedg860','530','3522','3903','65','70','0','3481.37','7222.76','141.365','1.30345','3247:0 3252:0 '), +('3252','bedg861','530','3522','3903','65','70','0','3590.93','7211.94','137.85','5.43858','3251:0 3253:0 '), +('3253','bedg862','530','3522','3903','65','70','0','3641.59','7154.45','142.814','5.19904','3252:0 3254:0 '), +('3254','bedg863','530','3522','3903','65','70','0','3697.31','7047.11','149.054','4.94771','3253:0 3255:0 '), +('3255','bedg864','530','3522','3830','65','70','0','3765.97','6838.37','141.277','4.82598','3254:0 3256:0 '), +('3256','bedg865','530','3522','3781','65','70','0','3736.74','6702.81','136.262','4.17018','3246:0 3255:0 3257:0 '), +('3257','bedg866','530','3522','3781','65','70','0','3630.09','6582.49','134.163','3.31724','3256:0 3258:0 '), +('3258','bedg867','530','3522','3781','65','70','0','3502.63','6555.2','131.654','3.74921','3257:0 3259:0 '), +('3259','bedg868','530','3522','3830','65','70','0','3447.34','6507.7','134.998','3.17979','3258:0 3260:0 '), +('3260','bedg869','530','3522','3830','65','70','0','3367.73','6538.54','150.282','2.78317','3244:0 3259:0 3245:0 '), +('3261','bedg870','530','3522','3824','65','70','0','1832.55','6314.24','0.000980531','2.39439','3131:0 3262:0 '), +('3262','bedg871','530','3522','3824','65','70','0','1739.41','6384.91','-10.3385','3.01878','3261:0 3263:0 '), +('3263','bedg872','530','3522','3776','65','70','0','1654.93','6407.76','-10.1815','2.87741','3262:0 3130:0 3264:0 '), +('3264','bedg873','530','3522','3776','65','70','0','1604.99','6316.74','1.22794','4.18509','3263:0 3265:0 '), +('3265','bedg874','530','3522','3824','65','70','0','1475.3','6532.8','-10.3385','5.60273','3264:0 3266:0 '), +('3266','bedg876','530','3522','3778','65','70','0','1361.1','6534.43','6.43101','6.15055','3265:0 3267:0 '), +('3267','bedg877','530','3522','3778','65','70','0','1339.54','6590.17','-8.16829','2.78314','3268:0 3266:0 '), +('3268','bedg878','530','3522','3778','65','70','0','1355.69','6688.28','-22.7054','2.60447','3267:0 '), +('3269','net880','530','3523','3868','67','70','0','3385.12','4257.44','122.681','4.84713','3200:0 3201:0 3270:0 '), +('3270','net881','530','3523','3523','67','70','0','3394.02','4139.94','151.096','1.574','3269:0 3271:0 '), +('3271','net883','530','3523','3523','67','70','0','3433.77','4036.61','177.446','4.57617','3272:0 3270:0 '), +('3272','net884','530','3523','3523','67','70','0','3443.07','3946.93','166.449','4.15205','3271:0 3273:0 '), +('3273','net885','530','3523','3523','67','70','0','3403.88','3894.6','153.082','3.72008','3272:0 3274:0 '), +('3274','net886','530','3523','3523','67','70','0','3356.69','3870.95','144.623','4.91389','3273:0 3275:0 3284:0 3285:0 '), +('3275','net887','530','3523','3523','67','70','0','3379','3814.14','142.822','4.46228','3274:0 3276:0 '), +('3276','net888','530','3523','3725','67','70','0','3356.46','3730.03','141.12','5.51863','3275:0 3277:0 '), +('3277','net889','530','3523','3725','67','70','0','3395.58','3698.26','144.989','5.91132','3276:0 3278:0 '), +('3278','net890','530','3523','3725','67','70','0','3463.55','3674.25','150.706','4.98848','3277:0 3279:0 '), +('3279','net891','530','3523','3725','67','70','0','3489.72','3601.83','158.293','4.35231','3278:0 3280:0 '), +('3280','net892','530','3523','3725','67','70','0','3537.5','3575.94','135.389','2.48697','3279:0 3281:0 '), +('3281','net893','530','3523','3725','67','70','0','3582.4','3548.11','124.35','1.42276','3280:0 3282:0 '), +('3282','net894','530','3523','3721','67','70','0','3601.15','3625.27','127.148','2.16889','3281:0 3283:0 '), +('3283','net895','530','3523','3725','67','70','0','3506.7','3744.78','141.004','1.82724','3282:0 3284:0 '), +('3284','net896','530','3523','3523','67','70','0','3492.41','3820.83','138.898','2.78542','3283:0 3274:0 '), +('3285','net897','530','3523','3523','67','70','0','3288.33','3814.38','139.103','3.2089','3274:0 3286:0 '), +('3286','net898','530','3523','3523','67','70','0','3228.8','3802.26','137.833','3.34242','3285:0 3287:0 3318:0 3319:0 '), +('3287','net899','530','3523','3712','67','70','0','3127.16','3690.44','142.929','3.42881','3286:0 3318:0 3319:0 3324:0 '), +('3288','net903','530','3523','3712','67','70','0','2999.35','3698.08','144.031','2.76314','3289:0 3315:0 3323:0 3316:0 '), +('3289','net904','530','3523','3712','67','70','0','2922.84','3722.08','143.833','1.69299','3290:0 3288:0 '), +('3290','net905','530','3523','3523','67','70','0','2885.06','3679.8','140.256','0.687686','3289:0 3291:0 '), +('3291','net906','530','3523','3523','67','70','0','2816.99','3697.81','120.765','3.2795','3290:0 3292:0 '), +('3292','net907','530','3523','3523','67','70','0','2757.67','3673.48','134.526','2.26241','3291:0 3293:0 3336:0 '), +('3293','net908','530','3523','3729','67','70','0','2719.32','3721.32','139.083','3.091','3292:0 3294:0 '), +('3294','net909','530','3523','3729','67','70','0','2639.32','3732.64','144.622','2.23884','3293:0 3295:0 '), +('3295','net910','530','3523','3729','67','70','0','2585.42','3776.42','143.482','2.4509','3294:0 3296:0 3339:0 '), +('3296','net911','530','3523','3729','67','70','0','2514.06','3815.49','129.888','2.12889','3295:0 3297:0 '), +('3297','net912','530','3523','3729','67','70','0','2508.94','3883.67','132.691','1.6655','3296:0 3298:0 '), +('3298','net913','530','3523','3729','67','70','0','2477.31','4067.17','130.201','5.03879','3297:0 3299:0 '), +('3299','net914','530','3523','3523','67','70','0','2545.65','4202.77','136.176','0.542382','3298:0 3300:0 '), +('3300','net915','530','3523','3523','67','70','0','2609.03','4303.08','140.386','0.000453472','3299:0 3301:0 '), +('3301','net916','530','3523','3726','67','70','0','2747.27','4290.97','151.997','6.19723','3300:0 3302:0 '), +('3302','net917','530','3523','3726','67','70','0','2895.85','4349.05','157.653','4.53219','3301:0 3303:0 '), +('3303','net918','530','3523','3726','67','70','0','2865.03','4280.18','159.366','5.10554','3302:0 3304:0 '), +('3304','net919','530','3523','3726','67','70','0','2890.62','4215.62','164.008','5.09376','3303:0 3305:0 3312:0 '), +('3305','net920','530','3523','3726','67','70','0','2940.62','4199.62','164.007','0.322464','3304:0 3306:0 3311:0 '), +('3306','net921','530','3523','3726','67','70','0','2999.27','4224.49','160.851','0.401004','3305:0 3307:0 '), +('3307','net922','530','3523','3726','67','70','0','3026.75','4163.73','153.291','4.74035','3306:0 3308:0 '), +('3308','net923','530','3523','3726','67','70','0','3010.04','4106.05','150.967','4.08455','3307:0 3309:0 '), +('3309','net924','530','3523','3726','67','70','0','2977.7','4033.08','148.218','1.89329','3308:0 3310:0 3313:0 '), +('3310','net925','530','3523','3726','67','70','0','2939.58','4099.08','162.599','2.04252','3309:0 3311:0 '), +('3311','net926','530','3523','3726','67','70','0','2918.39','4149.56','164.008','1.96398','3310:0 3305:0 3312:0 '), +('3312','net927','530','3523','3726','67','70','0','2870.91','4167.67','164.007','1.92863','3311:0 3304:0 '), +('3313','net928','530','3523','3523','67','70','0','2982.92','3921.89','146.316','1.62627','3309:0 3314:0 '), +('3314','net929','530','3523','3523','67','70','0','3051.56','3890.86','143.683','5.83993','3313:0 3315:0 '), +('3315','net930','530','3523','3712','67','70','0','3039.6','3790.04','145.363','4.28485','3314:0 3317:0 3288:0 '), +('3316','net931','530','3523','3712','67','70','0','3064.35','3704.05','142.478','3.15387','3288:0 3324:0 '), +('3317','net932','530','3523','3523','67','70','0','3092.9','3822.62','142.74','5.9106','3315:0 3318:0 '), +('3318','net933','530','3523','3523','67','70','0','3125.95','3779.17','142.072','0.118293','3317:0 3287:0 3286:0 '), +('3319','net934','530','3523','3523','67','70','0','3196.76','3711.71','129.813','5.90669','3287:0 3286:0 3320:0 '), +('3320','net935','530','3523','3523','67','70','0','3253.35','3630.84','126.607','3.66438','3319:0 3321:0 '), +('3321','net936','530','3523','3523','67','70','0','3139.27','3579.06','142.77','3.24419','3320:0 3322:0 '), +('3322','net937','530','3523','3712','67','70','0','3046.95','3595.34','143.235','1.59486','3321:0 3325:0 3324:0 '), +('3323','net938','530','3523','3712','1','60','0','2935.94','3648.71','132.577','0.379446','3288:0 3324:0 '), +('3324','net939','530','3523','3712','67','70','1','3062.95','3645.68','144.33','5.51596','3287:0 3322:0 3323:0 3316:0 '), +('3325','net940','530','3523','3523','67','70','0','3016.58','3539.17','143.581','3.54657','3322:0 3326:0 3340:0 '), +('3326','net941','530','3523','3523','67','70','0','2859.55','3471.67','136.322','0.389269','3325:0 3327:0 3334:0 '), +('3327','net942','530','3523','3723','67','70','0','2805.81','3360.76','144.743','4.68931','3326:0 3328:0 '), +('3328','net944','530','3523','3723','67','70','0','2802.57','3251.58','147.597','1.60466','3327:0 3329:0 '), +('3329','net945','530','3523','3723','67','70','0','2769.35','3153.34','149.314','2.61194','3330:0 3328:0 '), +('3330','net946','530','3523','3723','67','70','0','2713.25','3168.67','147.073','4.21416','3329:0 3331:0 '), +('3331','net947','530','3523','3721','67','70','0','2645.59','3055.71','123.459','0.911553','3330:0 3332:0 3347:0 3348:0 '), +('3332','net948','530','3523','3723','67','70','0','2635.1','3154.85','135.257','1.35923','3331:0 3333:0 '), +('3333','net949','530','3523','3523','67','70','0','2617.24','3347.94','142.696','1.66554','3332:0 3334:0 3337:0 '), +('3334','net950','530','3523','3523','67','70','0','2742.84','3444.31','140.23','4.12776','3333:0 3326:0 3335:0 '), +('3335','net951','530','3523','3523','67','70','0','2704.66','3571.67','134.738','5.57132','3334:0 3336:0 '), +('3336','net952','530','3523','3523','67','70','0','2828.97','3612.56','157.278','2.2157','3335:0 3292:0 '), +('3337','net953','530','3523','3721','67','70','0','2522.14','3469.45','130.706','1.47151','3333:0 3338:0 '), +('3338','net954','530','3523','3523','67','70','0','2544.17','3592.01','138.626','1.39297','3337:0 3339:0 '), +('3339','net955','530','3523','3721','67','70','0','2528.36','3697.54','134.608','0.964924','3338:0 3295:0 '), +('3340','net956','530','3523','3523','67','70','0','3051.39','3455.12','118.577','4.822','3325:0 3341:0 '), +('3341','net957','530','3523','3523','67','70','0','3057.51','3309.81','106.678','5.00264','3340:0 3342:0 '), +('3342','net958','530','3523','3721','67','70','0','3103.15','3197.03','101.208','5.67416','3341:0 3343:0 3344:0 '), +('3343','net959','530','3523','3721','67','70','0','3221.06','3114.67','102.79','5.68986','3342:0 3385:0 3386:0 '), +('3344','net960','530','3523','3921','67','70','0','2987.27','3194.73','134.342','4.05624','3342:0 3345:0 '), +('3345','net961','530','3523','3921','67','70','0','2929.07','3149.43','147.291','1.12512','3344:0 3346:0 '), +('3346','net962','530','3523','3721','67','70','0','2829.63','2992.81','120.478','2.75876','3345:0 3347:0 '), +('3347','net963','530','3523','3721','67','70','0','2726.18','3015.49','121.825','2.99045','3346:0 3331:0 '), +('3348','net964','530','3523','3721','67','70','0','2519.49','2940.34','119.757','3.88108','3331:0 3349:0 '), +('3349','net965','530','3523','3730','67','70','0','2455.26','2867.95','131.925','4.34839','3348:0 3350:0 3354:0 '), +('3350','net966','530','3523','3730','67','70','0','2531.85','2827','126.995','4.61936','3351:0 3349:0 '), +('3351','net967','530','3523','3730','67','70','0','2555.92','2738.68','119.45','1.55822','3350:0 3352:0 3376:0 '), +('3352','net970','530','3523','3730','67','70','0','2501.34','2718.83','131.657','2.74617','3353:0 3351:0 3375:0 '), +('3353','net971','530','3523','3730','67','70','0','2440.85','2747.78','134.493','2.69511','3354:0 3356:0 3352:0 '), +('3354','net972','530','3523','3730','67','70','0','2424.83','2798.75','134.493','1.0929','3353:0 3355:0 3349:0 '), +('3355','net973','530','3523','3730','67','70','0','2364.39','2781.17','133.656','5.81904','3354:0 3356:0 '), +('3356','net974','530','3523','3730','67','70','0','2392.65','2726.98','134.491','5.0199','3353:0 3355:0 3357:0 '), +('3357','net975','530','3523','3730','67','70','0','2364.76','2665.91','130.977','1.12433','3356:0 3358:0 '), +('3358','net976','530','3523','3523','67','70','0','2326','2600.19','126.548','4.98456','3357:0 3359:0 '), +('3359','net977','530','3523','3523','67','70','0','2287.03','2439.79','104.053','4.47406','3358:0 3360:0 '), +('3360','net978','530','3523','3523','67','70','0','2336.37','2367.16','115.816','5.18877','3359:0 3373:0 3361:0 '), +('3361','net979','530','3523','3523','67','70','0','2310.67','2271.59','98.2741','5.2673','3360:0 3362:0 3367:0 '), +('3362','net981','530','3523','3935','67','70','0','2246.67','2310.26','89.8977','3.71223','3363:0 3372:0 3361:0 '), +('3363','net982','530','3523','3934','67','70','0','2176.86','2262.3','76.0387','4.01068','3362:0 3364:0 3372:0 '), +('3364','net983','530','3523','3934','67','70','0','2119.56','2206.31','71.3039','6.1607','3363:0 3365:0 '), +('3365','net984','530','3523','3934','67','70','0','2180.34','2173.31','71.6002','5.61288','3364:0 3366:0 '), +('3366','net985','530','3523','3934','67','70','0','2233.52','2115.22','71.2016','2.29655','3365:0 3367:0 '), +('3367','net987','530','3523','3934','67','70','0','2294.17','2189.49','93.5883','6.28047','3368:0 3361:0 3366:0 '), +('3368','net990','530','3523','3934','67','70','0','2353.3','2183.05','89.3993','6.15874','3367:0 3369:0 '), +('3369','net991','530','3523','3879','67','70','0','2411.12','2183.37','89.9985','4.94529','3368:0 3370:0 '), +('3370','net992','530','3523','3879','67','70','0','2523.75','2185.83','102.693','5.98201','3369:0 3371:0 '), +('3371','net993','530','3523','3879','67','70','0','2484.6','2033.27','88.1469','4.36606','3370:0 '), +('3372','net997','530','3523','3935','67','70','0','2183.73','2382.64','98.0661','5.72675','3362:0 3363:0 '), +('3373','net999','530','3523','3837','67','70','0','2400.24','2395.93','142.613','3.9737','3374:0 3360:0 '), +('3374','net1000','530','3523','3837','67','70','0','2482.84','2424.13','134.571','3.45533','3373:0 3375:0 '), +('3375','net1001','530','3523','3523','67','70','0','2516.55','2638.79','129.583','6.18383','3374:0 3352:0 3376:0 '), +('3376','net1003','530','3523','3523','67','70','0','2599.94','2630.3','129.384','5.99453','3351:0 3375:0 3377:0 '), +('3377','net1004','530','3523','3721','67','70','0','2697.9','2608.37','102.479','0.0883238','3378:0 3376:0 '), +('3378','net1005','530','3523','3721','67','70','0','2824.9','2626.19','104.537','0.139375','3377:0 3379:0 '), +('3379','net1006','530','3523','3523','67','70','0','2944.38','2624.27','115.88','6.27726','3378:0 3380:0 '), +('3380','net1007','530','3523','3523','67','70','0','3044.31','2606.49','109.94','6.10839','3379:0 3381:0 3410:0 '), +('3381','net1008','530','3523','3523','67','70','0','3088.57','2759.93','115.588','1.28997','3380:0 3382:0 '), +('3382','net1009','530','3523','3523','67','70','0','3181.15','2839.31','132.63','0.00191784','3381:0 3383:0 '), +('3383','net1010','530','3523','3877','67','70','0','3314.68','2857.1','140.864','0.174705','3382:0 3384:0 3394:0 '), +('3384','net1011','530','3523','3523','67','70','0','3251.04','2889.15','139.525','5.61752','3383:0 3385:0 '), +('3385','net1012','530','3523','3523','67','70','0','3260.45','2979.53','133.71','1.72431','3384:0 3343:0 '), +('3386','net1013','530','3523','3721','67','70','0','3328.19','3109.33','123.938','0.003479','3343:0 3387:0 '), +('3387','net1014','530','3523','3721','67','70','0','3467.09','3253.66','100.83','0.804579','3386:0 3388:0 '), +('3388','net1015','530','3523','3523','67','70','0','3558.21','3192.19','105.717','5.68976','3387:0 3389:0 '), +('3389','net1016','530','3523','3877','67','70','0','3523.96','3113.12','130.592','4.30353','3388:0 3390:0 '), +('3390','net1017','530','3523','3877','67','70','0','3511.13','3048.77','142.826','5.09678','3389:0 3391:0 '), +('3391','net1018','530','3523','3877','67','70','0','3536.43','3000.68','143.097','5.20281','3390:0 3395:0 3392:0 '), +('3392','net1020','530','3523','3877','67','70','0','3646.43','3045.04','123.609','0.195882','3391:0 3393:0 '), +('3393','net1021','530','3523','3523','67','70','0','3924.41','3119.4','115.634','0.278359','3526:0 3392:0 '), +('3394','net1022','530','3523','3877','67','70','1','3410.82','2878.14','142.972','0.18804','3383:0 3395:0 3396:0 '), +('3395','net1023','530','3523','3877','67','70','0','3473.27','2973.11','143.266','0.989146','3394:0 3391:0 '), +('3396','net1024','530','3523','3877','67','70','0','3472.55','2819.87','145.201','4.69465','3394:0 3397:0 '), +('3397','net1025','530','3523','3523','67','70','0','3576.26','2719.73','151.525','3.15525','3396:0 3398:0 '), +('3398','net1026','530','3523','3523','67','70','0','3439.56','2608.19','150.833','0.802981','3397:0 3399:0 '), +('3399','net1027','530','3523','3523','67','70','0','3372.76','2617.96','145.564','2.25596','3398:0 3400:0 3403:0 '), +('3400','net1028','530','3523','3727','67','70','0','3328.76','2674.02','157.504','3.71287','3399:0 3401:0 '), +('3401','net1029','530','3523','3727','67','70','0','3250.52','2628.38','139.888','2.91962','3400:0 3402:0 '), +('3402','net1030','530','3523','3523','67','70','0','3285.42','2563.6','113.658','4.09772','3401:0 3403:0 '), +('3403','net1031','530','3523','3523','67','70','0','3352.87','2536.5','95.7401','1.21138','3399:0 3402:0 3404:0 '), +('3404','net1033','530','3523','3728','67','70','0','3246.86','2379.03','91.5897','4.38044','3403:0 3405:0 '), +('3405','net1035','530','3523','3728','67','70','0','3241.13','2272.31','99.5301','4.56501','3404:0 3406:0 '), +('3406','net1037','530','3523','3728','67','70','0','3202.86','2140.94','138.867','1.63547','3423:0 3405:0 3407:0 '), +('3407','net1039','530','3523','3728','67','70','0','3162.82','2326.75','134.887','1.78864','3406:0 3408:0 '), +('3408','net1040','530','3523','3728','67','70','0','3160.48','2406.47','131.04','1.60014','3409:0 3407:0 '), +('3409','net1041','530','3523','3728','67','70','0','3113.17','2449.04','120.183','2.1774','3408:0 3410:0 '), +('3410','net1042','530','3523','3728','67','70','0','3069.26','2504.52','114.18','1.78077','3409:0 3380:0 3411:0 '), +('3411','net1043','530','3523','3734','67','70','0','3083.75','2363.04','144.526','4.81242','3410:0 3412:0 '), +('3412','net1044','530','3523','3734','67','70','0','3085.33','2294.83','150.494','4.74173','3411:0 3413:0 3419:0 '), +('3413','net1045','530','3523','3734','67','70','0','3125.61','2238.22','150.562','3.67359','3412:0 3414:0 '), +('3414','net1046','530','3523','3734','67','70','0','3074.15','2209.78','161.33','3.55578','3413:0 3415:0 '), +('3415','net1047','530','3523','3734','67','70','0','3017.03','2183.87','165.329','3.55578','3414:0 3416:0 3418:0 '), +('3416','net1048','530','3523','3734','67','70','0','2998.08','2133.47','165.328','4.35295','3415:0 3417:0 3420:0 '), +('3417','net1049','530','3523','3734','67','70','0','2946.6','2153.28','165.328','2.77429','3416:0 3418:0 '), +('3418','net1050','530','3523','3734','67','70','0','2965.73','2206.06','165.328','1.22313','3417:0 3415:0 3419:0 '), +('3419','net1051','530','3523','3734','67','70','0','2936.31','2281.19','161.738','6.1684','3412:0 3418:0 '), +('3420','net1054','530','3523','3734','67','70','0','3024.32','2073.46','161.904','5.51533','3416:0 3421:0 '), +('3421','net1055','530','3523','3728','67','70','0','3109.36','1996.97','143.897','5.55067','3420:0 3422:0 '), +('3422','net1056','530','3523','3728','67','70','0','3166.19','1968.36','143.636','5.79022','3421:0 3423:0 3424:0 '), +('3423','net1057','530','3523','3728','67','70','0','3200.13','2051.68','140.786','4.08789','3422:0 3406:0 '), +('3424','net1058','530','3523','3728','67','70','0','3152.12','1897.92','143.899','6.07689','3422:0 3425:0 3427:0 '), +('3425','net1059','530','3523','3728','67','70','0','3101.78','1824.2','144.391','4.03094','3426:0 3424:0 '), +('3426','net1060','530','3523','3728','67','70','1','3001.93','1820.1','139.674','3.72463','3425:0 '), +('3427','net1061','530','3523','3728','67','70','0','3242.1','1874.48','139.223','3.2581','3424:0 3428:0 '), +('3428','net1062','530','3523','3721','67','70','0','3356.3','1846.42','102.117','6.15621','3427:0 3429:0 '), +('3429','net1063','530','3523','3721','67','70','0','3493.95','1840.83','95.7573','0.214661','3428:0 3430:0 '), +('3430','net1064','530','3523','3735','67','70','0','3613.29','1892.38','114.291','3.62722','3429:0 3459:0 3460:0 '), +('3431','net1066','530','3523','3736','67','70','0','3865.27','1760.4','212.947','0.885387','3432:0 3456:0 '), +('3432','net1067','530','3523','3736','67','70','0','3913.4','1809.95','227.674','2.24413','3431:0 3442:0 3433:0 '), +('3433','net1070','530','3523','3736','67','70','0','3881.31','1918.58','254.223','0.97178','3432:0 3434:0 '), +('3434','net1071','530','3523','3736','67','70','0','3915.93','1973.35','257.813','1.00712','3435:0 3437:0 3433:0 '), +('3435','net1072','530','3523','3736','67','70','0','3903.14','2024.48','257.813','1.77289','3434:0 3436:0 '), +('3436','net1073','530','3523','3736','67','70','0','3959.61','2037.62','257.813','0.233506','3435:0 3437:0 3438:0 '), +('3437','net1074','530','3523','3736','67','70','0','3969.45','1982.24','257.813','4.86736','3436:0 3434:0 '), +('3438','net1075','530','3523','3736','67','70','0','3996.64','2094.06','254.332','0.975711','3436:0 3439:0 '), +('3439','net1077','530','3523','3736','67','70','0','4094.87','2029.53','236.513','5.17366','3438:0 3440:0 '), +('3440','net1078','530','3523','3736','67','70','0','4145.6','1950.14','225.805','2.1106','3443:0 3441:0 3439:0 '), +('3441','net1079','530','3523','3736','67','70','0','4106.82','1883.96','228.654','3.273','3440:0 3442:0 '), +('3442','net1081','530','3523','3736','67','70','0','3993.09','1800.64','228.295','3.01381','3432:0 3441:0 '), +('3443','net1082','530','3523','3736','67','70','0','4192.46','2017.97','187.695','1.85243','3440:0 3444:0 '), +('3444','net1083','530','3523','3736','67','70','0','4174.2','2071.5','164.367','1.33406','3443:0 3445:0 '), +('3445','net1084','530','3523','3735','67','70','0','4198.79','2173.44','151.336','1.33406','3444:0 3446:0 3447:0 3477:0 '), +('3446','net1085','530','3523','3735','67','70','0','4292.53','2293.49','122.367','0.595787','3445:0 3478:0 '), +('3447','net1086','530','3523','3854','67','70','0','4247.96','2110.73','144.575','4.7741','3445:0 3448:0 '), +('3448','net1087','530','3523','3735','67','70','0','4252.18','1989.35','136.107','4.82514','3447:0 3449:0 '), +('3449','net1088','530','3523','3735','67','70','0','4268.67','1894.47','138.946','4.87226','3448:0 3450:0 3453:0 '), +('3450','net1089','530','3523','3735','67','70','0','4299.75','1730.52','113.615','4.18507','3449:0 3451:0 '), +('3451','net1090','530','3523','3735','67','70','0','4113.8','1674.62','130.483','2.68103','3450:0 3452:0 '), +('3452','net1091','530','3523','3735','67','70','0','4046.36','1742.75','145.812','2.35116','3451:0 3453:0 3454:0 '), +('3453','net1092','530','3523','3735','67','70','0','4181.61','1781.88','136.718','0.423012','3452:0 3449:0 '), +('3454','net1093','530','3523','3735','67','70','0','3930.55','1683.62','139.355','3.27165','3452:0 3455:0 '), +('3455','net1094','530','3523','3735','67','70','0','3819.95','1651.37','133.174','2.01794','3454:0 3457:0 3456:0 '), +('3456','net1095','530','3523','3735','67','70','0','3785.44','1751.5','175.377','2.55396','3431:0 3455:0 3459:0 '), +('3457','net1097','530','3523','3735','67','70','0','3779.17','1581.07','120.895','1.36998','3455:0 3458:0 '), +('3458','net1098','530','3523','3735','67','70','0','3710.46','1647.45','128.67','1.14614','3457:0 3459:0 '), +('3459','net1099','530','3523','3735','67','70','0','3682.83','1822.97','129.53','5.05546','3458:0 3430:0 3456:0 '), +('3460','net1100','530','3523','3735','67','70','0','3551.83','1960.87','89.9501','1.23472','3430:0 3461:0 3462:0 '), +('3461','net1101','530','3523','3735','67','70','0','3582.85','2061.58','97.1173','1.30541','3460:0 '), +('3462','net1102','530','3523','3735','67','70','0','3654.98','2151.19','121.699','1.26065','3460:0 3463:0 '), +('3463','net1103','530','3523','3735','67','70','0','3647.05','2320.07','95.0628','1.67298','3462:0 3464:0 '), +('3464','net1104','530','3523','3735','67','70','0','3753.6','2270.39','127.706','5.84031','3463:0 3465:0 '), +('3465','net1105','530','3523','3735','67','70','0','3743.54','2216.22','132.261','5.08632','3464:0 3466:0 '), +('3466','net1106','530','3523','3880','67','70','0','3752.43','2164.28','140.754','4.1949','3465:0 3467:0 3473:0 '), +('3467','net1107','530','3523','3880','67','70','0','3712.58','2091.74','151.686','5.93062','3466:0 3468:0 '), +('3468','net1108','530','3523','3880','67','70','0','3791.21','2078.46','153.464','0.004788','3467:0 3469:0 '), +('3469','net1109','530','3523','3880','67','70','0','3833.37','2045.96','145.651','5.41226','3468:0 3470:0 '), +('3470','net1110','530','3523','3880','67','70','0','3890.34','2027.56','147.129','1.10633','3469:0 3471:0 '), +('3471','net1112','530','3523','3880','67','70','0','3911.03','2086.24','156.158','1.07294','3470:0 3472:0 '), +('3472','net1113','530','3523','3880','67','70','0','3942.19','2139.62','160.422','6.24086','3471:0 '), +('3473','net1114','530','3523','3735','67','70','0','3790.48','2245.91','149.586','4.56011','3466:0 3474:0 '), +('3474','net1115','530','3523','3735','67','70','0','3843.92','2279.21','153.861','2.84204','3473:0 3475:0 '), +('3475','net1116','530','3523','3735','67','70','0','3944.34','2283.35','158.599','6.04059','3474:0 3476:0 '), +('3476','net1117','530','3523','3735','67','70','0','4063.98','2226.19','164.346','5.41228','3475:0 3477:0 '), +('3477','net1119','530','3523','3735','67','70','0','4123.87','2142.65','157.701','2.91076','3445:0 3476:0 '), +('3478','net1121','530','3523','3741','67','70','0','4466.87','2397.83','122.968','0.527102','3446:0 3479:0 '), +('3479','net1122','530','3523','3741','67','70','0','4530.13','2410.14','141.874','5.42799','3478:0 3480:0 3484:0 '), +('3480','net1123','530','3523','3741','67','70','0','4566.37','2316.31','146.43','5.07457','3479:0 3481:0 '), +('3481','net1124','530','3523','3741','67','70','0','4632.34','2295.23','162.104','1.47351','3480:0 3482:0 '), +('3482','net1125','530','3523','3741','67','70','0','4622.3','2365.33','187.361','1.96046','3481:0 3483:0 '), +('3483','net1126','530','3523','3741','67','70','0','4729.92','2383.24','198.355','3.24458','3482:0 '), +('3484','net1127','530','3523','3741','67','70','0','4575.52','2543.12','194.543','0.915856','3479:0 3485:0 '), +('3485','net1128','530','3523','3741','67','70','0','4579.8','2608.07','196.971','1.06115','3484:0 3486:0 '), +('3486','net1129','530','3523','3741','67','70','0','4616.02','2682.15','187.718','1.52847','3485:0 3487:0 '), +('3487','net1130','530','3523','3523','67','70','0','4613.1','2774.93','159.097','0.786306','3486:0 3488:0 3507:0 '), +('3488','net1131','530','3523','3523','67','70','0','4581.46','2841.92','146.738','1.62271','3487:0 3489:0 3508:0 '), +('3489','net1132','530','3523','3523','67','70','0','4623.84','2957.12','144.621','1.21823','3488:0 3490:0 3509:0 '), +('3490','net1133','530','3523','3850','67','70','0','4760.8','2985.3','129.535','0.205073','3489:0 3491:0 '), +('3491','net1134','530','3523','3850','67','70','0','4849.32','2917.43','146.288','5.62826','3490:0 3492:0 '), +('3492','net1135','530','3523','3874','67','70','0','4916.97','2942.46','157.256','0.212934','3491:0 3493:0 '), +('3493','net1136','530','3523','3850','67','70','0','4803.44','3004.17','134.11','2.64374','3492:0 3494:0 '), +('3494','net1137','530','3523','3523','67','70','0','4689.74','3022.54','131.238','0.00480604','3493:0 3495:0 '), +('3495','net1138','530','3523','3523','67','70','0','4770.95','3072.84','120.927','0.216864','3494:0 3496:0 '), +('3496','net1139','530','3523','3523','67','70','0','4841.73','3077.92','111.914','5.77749','3495:0 3497:0 3579:0 '), +('3497','net1140','530','3523','3874','67','70','0','4921.37','3026.16','99.5096','5.7068','3496:0 3498:0 '), +('3498','net1141','530','3523','3874','67','70','0','5030.73','3015.92','91.2729','6.18983','3497:0 3499:0 '), +('3499','net1142','530','3523','3874','67','70','0','5047.36','2928.45','88.8179','4.9057','3498:0 3500:0 '), +('3500','net1143','530','3523','3874','67','70','0','4988.68','2838.26','81.9239','3.64121','3499:0 3501:0 '), +('3501','net1144','530','3523','3850','67','70','0','4907.06','2817.68','91.5802','3.63336','3500:0 3502:0 '), +('3502','net1145','530','3523','3850','67','70','0','4903.41','2721.51','83.4709','4.33629','3501:0 3503:0 '), +('3503','net1146','530','3523','3850','67','70','0','4844.82','2634.49','94.0377','2.49061','3502:0 3504:0 '), +('3504','net1147','530','3523','3850','67','70','0','4803.14','2703.36','87.3788','2.22751','3503:0 3505:0 '), +('3505','net1148','530','3523','3850','67','70','0','4741.03','2771.78','92.6406','2.30998','3504:0 3506:0 '), +('3506','net1149','530','3523','3850','67','70','0','4719.31','2848.51','113.113','1.8623','3505:0 3507:0 '), +('3507','net1150','530','3523','3850','67','70','0','4656.43','2805.22','134.562','3.22497','3506:0 3487:0 '), +('3508','net1151','530','3523','3876','67','70','0','4484.96','2804.2','139.481','1.71701','3488:0 3530:0 '), +('3509','net1152','530','3523','3740','67','70','0','4590.85','3169.73','146.93','4.87431','3489:0 3510:0 3511:0 3516:0 3569:0 '), +('3510','net1153','530','3523','3740','67','70','0','4693.85','3149.86','170.153','5.31804','3509:0 3511:0 '), +('3511','net1154','530','3523','3740','1','60','0','4655.8','3065','169.231','2.59665','3510:0 3509:0 3512:0 '), +('3512','net1155','530','3523','3740','67','70','0','4685.7','3259.51','173.132','2.39442','3511:0 3513:0 3515:0 '), +('3513','net1156','530','3523','3740','67','70','0','4776.31','3293.39','196.421','1.55207','3512:0 3514:0 '), +('3514','net1157','530','3523','3740','67','70','0','4732.45','3359.96','201.252','3.83758','3513:0 3515:0 '), +('3515','net1158','530','3523','3740','67','70','0','4630.74','3310.08','170.694','5.45551','3514:0 3512:0 '), +('3516','net1159','530','3523','3523','67','70','0','4397.77','3265.13','143.404','2.67128','3509:0 3517:0 3521:0 3522:0 3569:0 3533:0 '), +('3517','net1160','530','3523','3739','67','70','0','4463.52','3434.86','167.212','1.19866','3516:0 3518:0 3521:0 '), +('3518','net1161','530','3523','3739','67','70','0','4387.49','3412.69','167.78','2.42782','3517:0 3519:0 '), +('3519','net1162','530','3523','3739','67','70','0','4350.61','3471.82','175.837','4.81542','3518:0 3520:0 '), +('3520','net1163','530','3523','3739','67','70','0','4364.97','3377.13','154.103','0.876653','3519:0 3521:0 '), +('3521','net1164','530','3523','3739','67','70','0','4427.16','3339.99','152.658','0.896303','3520:0 3516:0 3517:0 '), +('3522','net1165','530','3523','3738','67','70','0','4313.76','3185.04','161.274','0.774559','3516:0 3523:0 '), +('3523','net1166','530','3523','3738','67','70','1','4231.39','3159.2','178.712','0.566429','3522:0 3524:0 '), +('3524','net1167','530','3523','3738','67','70','0','4178.63','3151.63','175.35','3.55094','3523:0 3525:0 '), +('3525','net1168','530','3523','3738','67','70','0','4098.18','3118.59','159.275','3.31924','3524:0 3526:0 '), +('3526','net1169','530','3523','3738','67','70','0','4000.86','3107.64','138.035','3.24856','3525:0 3393:0 3527:0 '), +('3527','net1170','530','3523','3875','67','70','0','4031.23','3259.48','139.947','1.37536','3526:0 3528:0 '), +('3528','net1171','530','3523','3875','67','70','0','4071.98','3372.71','141.877','5.11779','3527:0 3529:0 3535:0 '), +('3529','net1172','530','3523','3875','67','70','0','4151.5','3229.24','196.056','3.95933','3528:0 '), +('3530','net1173','530','3523','3876','67','70','0','4466.66','2879.42','140.619','2.91867','3508:0 3531:0 '), +('3531','net1174','530','3523','3876','67','70','0','4370.42','2904.8','148.304','2.95794','3530:0 3532:0 '), +('3532','net1175','530','3523','3876','67','70','0','4330.52','2945.08','132.085','5.41232','3531:0 '), +('3533','net1176','530','3523','3523','67','70','0','4301.84','3317.13','147.441','2.74981','3516:0 3534:0 '), +('3534','net1178','530','3523','3523','67','70','0','4181.61','3456.1','144.034','2.38458','3535:0 3533:0 '), +('3535','net1179','530','3523','3875','67','70','0','4137.43','3393.24','142.177','3.93968','3534:0 3528:0 3536:0 '), +('3536','net1180','530','3523','3523','67','70','0','4138.78','3496.33','135.37','2.48591','3535:0 3537:0 3563:0 '), +('3537','net1181','530','3523','3852','67','70','0','4091.57','3538.26','117.689','1.98718','3536:0 3538:0 '), +('3538','net1182','530','3523','3852','67','70','0','4069.39','3591.81','114.31','2.34453','3537:0 3539:0 '), +('3539','net1183','530','3523','3722','67','70','0','3910.13','3724.58','112.828','2.44664','3538:0 3540:0 '), +('3540','net1184','530','3523','3722','67','70','0','3891.58','3773.08','119.636','1.37064','3539:0 3541:0 3556:0 '), +('3541','net1185','530','3523','3722','67','70','0','3908.92','3833.69','129.749','1.2921','3540:0 3542:0 '), +('3542','net1186','530','3523','3722','67','70','0','3990.07','3844.3','168.636','3.74058','3541:0 3543:0 '), +('3543','net1188','530','3523','3722','67','70','0','3883.22','3936.25','179.58','1.96754','3542:0 3544:0 '), +('3544','net1189','530','3523','3722','67','70','0','3884.07','3990.71','182.988','1.21356','3545:0 3555:0 3543:0 '), +('3545','net1190','530','3523','3722','67','70','0','3912.9','4038.34','190.499','6.07518','3544:0 3546:0 '), +('3546','net1191','530','3523','3722','67','70','0','3973.04','4023.83','194.227','6.0634','3545:0 3547:0 3549:0 '), +('3547','net1192','530','3523','3722','67','70','0','4018.79','4051.87','194.227','0.549902','3546:0 3548:0 3552:0 '), +('3548','net1193','530','3523','3722','67','70','0','4047.95','4004.82','194.226','3.89178','3547:0 3549:0 '), +('3549','net1194','530','3523','3722','67','70','0','4001.04','3975.21','194.226','3.74256','3548:0 3546:0 3550:0 '), +('3550','net1195','530','3523','3722','67','70','0','3987.35','3916.16','191.701','4.48476','3549:0 3551:0 '), +('3551','net1196','530','3523','3722','67','70','0','4046.25','3890.47','189.426','0.432092','3550:0 '), +('3552','net1197','530','3523','3722','67','70','0','4034.82','4111.42','191.69','4.44076','3547:0 3553:0 '), +('3553','net1198','530','3523','3722','67','70','0','4009.09','4170.41','196.566','3.6907','3552:0 3554:0 '), +('3554','net1199','530','3523','3722','67','70','0','3913.64','4108.63','193.142','4.23262','3553:0 3555:0 '), +('3555','net1200','530','3523','3722','67','70','0','3881.18','4041.13','183.645','4.80596','3554:0 3544:0 '), +('3556','net1202','530','3523','3722','67','70','0','3793.14','3905.54','108.811','1.48529','3540:0 3557:0 '), +('3557','net1203','530','3523','3881','67','70','0','3802.78','4013.52','122.041','4.63866','3558:0 3556:0 '), +('3558','net1204','530','3523','3881','67','70','0','3873.36','4009.36','122.335','5.89452','3557:0 3559:0 '), +('3559','net1205','530','3523','3881','67','70','0','3925.68','3980.18','123.291','0.96222','3558:0 3560:0 '), +('3560','net1206','530','3523','3881','67','70','0','3946.32','4028.85','115.31','5.07772','3559:0 3561:0 '), +('3561','net1207','530','3523','3881','67','70','0','4034.51','3981.16','124.954','0.0315266','3560:0 3562:0 '), +('3562','net1208','530','3523','3881','67','70','0','4093.15','3979.78','116.913','6.25973','3561:0 '), +('3563','net1209','530','3523','3523','67','70','0','4192.46','3560.85','141.75','3.80144','3536:0 3564:0 '), +('3564','net1210','530','3523','3721','67','70','0','4226.42','3607.77','132.686','0.593084','3563:0 3565:0 '), +('3565','net1212','530','3523','3721','67','70','0','4313.98','3676.81','118.894','5.57251','3564:0 3566:0 '), +('3566','net1213','530','3523','3523','67','70','0','4402.34','3609.63','125.622','5.65104','3567:0 3565:0 '), +('3567','net1214','530','3523','3523','67','70','0','4488.77','3626.57','104.723','0.252208','3566:0 3568:0 '), +('3568','net1215','530','3523','3721','67','70','0','4533.39','3589.74','117.794','5.57721','3567:0 '), +('3569','net1216','530','3523','3740','67','70','0','4515.18','3245.18','144.362','1.06117','3509:0 3516:0 3570:0 '), +('3570','net1217','530','3523','3740','67','70','0','4540.26','3296.59','134.776','0.813774','3569:0 3571:0 '), +('3571','net1218','530','3523','3523','67','70','0','4586.71','3342.4','125.714','0.566373','3570:0 3572:0 '), +('3572','net1219','530','3523','3523','67','70','0','4668.88','3392.34','110.519','0.444637','3571:0 3573:0 '), +('3573','net1220','530','3523','3900','67','70','0','4744.11','3423.26','102.517','0.629205','3572:0 3574:0 '), +('3574','net1221','530','3523','3900','67','70','0','4791.18','3452.42','102.703','5.32589','3573:0 3575:0 '), +('3575','net1222','530','3523','3523','67','70','0','4831.57','3384.27','122','4.79967','3574:0 3576:0 '), +('3576','net1223','530','3523','3523','67','70','0','4850.84','3313.75','131.13','3.99465','3575:0 3577:0 '), +('3577','net1224','530','3523','3523','67','70','0','4816.07','3269.64','149.703','0.982658','3576:0 3578:0 '), +('3578','net1225','530','3523','3523','67','70','0','4832.65','3173.12','139.44','1.878','3577:0 3579:0 '), +('3579','net1226','530','3523','3523','67','70','0','4806.1','3117.37','139.757','4.67009','3578:0 3496:0 '), +('3580','shmv1227','530','3520','3520','67','70','0','-2866.67','3174.8','12.0966','4.03472','2809:0 3581:0 '), +('3581','shmv1228','530','3520','3520','67','70','0','-2919.41','3080.6','39.7651','0.776879','3580:0 3582:0 '), +('3582','shmv1229','530','3520','3520','67','70','0','-2912.13','3021.5','54.1235','3.61375','3581:0 3583:0 '), +('3583','shmv1230','530','3520','3520','67','70','0','-3025.35','2947.82','86.1102','3.93969','3582:0 3584:0 '), +('3584','shmv1231','530','3520','3743','67','70','0','-3075.55','2877.97','82.0334','3.71192','3583:0 3585:0 3592:0 3749:0 '), +('3585','shmv1232','530','3520','3743','67','70','0','-3179.64','2866.95','94.9902','2.90296','3584:0 3586:0 '), +('3586','shmv1233','530','3520','3743','67','70','0','-3236.04','2882.68','114.136','3.1857','3585:0 3587:0 3746:0 '), +('3587','shmv1234','530','3520','3743','67','70','0','-3367.61','2868.56','158.738','3.24853','3586:0 3588:0 3591:0 '), +('3588','shmv1235','530','3520','3743','67','70','0','-3404.24','2930.17','169.88','0.354339','3587:0 3589:0 '), +('3589','shmv1236','530','3520','3743','67','70','0','-3346.8','2955.36','169.88','0.413244','3588:0 3590:0 '), +('3590','shmv1237','530','3520','3743','67','70','0','-3375.43','3001.18','170.817','2.12933','3589:0 3591:0 '), +('3591','shmv1238','530','3520','3743','67','70','0','-3444.14','2938.58','172.959','3.88077','3590:0 3587:0 '), +('3592','shmv1239','530','3520','3520','67','70','0','-3181.96','2718.28','68.7275','4.12424','3584:0 3593:0 3602:0 '), +('3593','shmv1240','530','3520','3744','67','70','0','-3176.94','2562.78','61.0113','0.102998','3592:0 3594:0 3603:0 '), +('3594','shmv1241','530','3520','3744','67','70','5','-3111.48','2564.12','61.7347','0.0205314','3593:0 3595:0 '), +('3595','shmv1242','530','3520','3744','67','70','5','-3059.25','2507.74','62.9718','4.55621','3594:0 3596:0 '), +('3596','shmv1243','530','3520','3744','67','70','4','-3069.01','2436.27','63.7223','4.4227','3595:0 3597:0 '), +('3597','shmv1244','530','3520','3520','67','70','0','-3123.06','2321.13','59.93','3.60981','3596:0 3598:0 3604:0 '), +('3598','shmv1245','530','3520','3520','67','70','0','-3231.28','2289.35','59.5698','4.92143','3597:0 3599:0 3652:0 '), +('3599','shmv1246','530','3520','3520','67','70','0','-3462.86','2282.37','63.8282','3.30352','3598:0 3600:0 3605:0 '), +('3600','shmv1247','530','3520','3949','67','70','0','-3455.95','2462.16','58.5579','1.65025','3599:0 3601:0 3652:0 '), +('3601','shmv1248','530','3520','3520','67','70','0','-3483.53','2605.88','62.0554','4.92536','3600:0 3602:0 3750:0 '), +('3602','shmv1249','530','3520','3520','67','70','0','-3301.81','2669.56','57.3856','0.154061','3601:0 3592:0 3603:0 '), +('3603','shmv1250','530','3520','3744','67','70','0','-3199.36','2470.72','62.1645','2.00446','3602:0 3593:0 3604:0 '), +('3604','shmv1251','530','3520','3520','67','70','0','-3210.03','2392.06','61.8107','5.34555','3597:0 3603:0 '), +('3605','shmv1252','530','3520','3520','67','70','0','-3690.32','2310.79','78.5618','6.17099','3599:0 3606:0 3612:0 3750:0 '), +('3606','shmv1253','530','3520','3752','67','70','0','-3792.84','2478.03','79.8242','1.51751','3605:0 3607:0 3611:0 '), +('3607','shmv1254','530','3520','3752','67','70','0','-3790.89','2565.75','91.2376','1.68244','3606:0 3608:0 3609:0 '), +('3608','shmv1255','530','3520','3752','67','70','0','-3793.32','2666.77','101.912','1.6471','3607:0 '), +('3609','shmv1256','530','3520','3752','67','70','0','-4020.54','2670.54','126.986','4.24676','3607:0 3610:0 '), +('3610','shmv1257','530','3520','3752','67','70','0','-4064.49','2549.64','134.278','5.45704','3609:0 3611:0 '), +('3611','shmv1258','530','3520','3752','67','70','0','-3950.4','2473.76','116.781','0.0024457','3610:0 3606:0 '), +('3612','shmv1259','530','3520','3745','67','70','0','-3762.45','2127.89','76.7131','3.78807','3605:0 3613:0 3616:0 '), +('3613','shmv1260','530','3520','3745','67','70','2','-3882.03','2033.78','96.002','2.18978','3612:0 3614:0 3616:0 '), +('3614','shmv1261','530','3520','3745','67','70','3','-3952.84','2136.12','98.2022','2.21727','3613:0 3615:0 '), +('3615','shmv1262','530','3520','3745','67','70','3','-3972.41','2203.81','101.676','5.04862','3614:0 '), +('3616','shmv1263','530','3520','3520','67','70','0','-3769.55','1947.31','94.6509','4.07473','3612:0 3613:0 3617:0 '), +('3617','shmv1264','530','3520','3520','67','70','0','-3890.57','1771.31','96.9587','3.89017','3616:0 3618:0 3653:0 '), +('3618','shmv1265','530','3520','3520','67','70','0','-4052.33','1643.87','94.7068','4.5499','3617:0 3619:0 '), +('3619','shmv1266','530','3520','3520','67','70','0','-4057.55','1395.83','83.9803','4.95438','3618:0 3620:0 3664:0 3679:0 '), +('3620','shmv1267','530','3520','3520','67','70','0','-4028.73','1253.84','79.0241','1.89525','3619:0 3621:0 '), +('3621','shmv1268','530','3520','3520','67','70','0','-3956.14','1083.59','27.6555','5.11538','3620:0 3622:0 3684:0 3771:0 '), +('3622','shmv1269','530','3520','3520','67','70','0','-3882.17','883.51','18.5293','4.46743','3621:0 3623:0 3685:0 '), +('3623','shmv1270','530','3520','3520','67','70','0','-3840.27','741.517','11.8649','0.0181477','3622:0 3624:0 3685:0 '), +('3624','shmv1271','530','3520','3520','67','70','0','-3586.49','737.497','-11.4566','0.0534906','3623:0 3625:0 3627:0 3778:0 '), +('3625','shmv1272','530','3520','3520','67','70','0','-3561.52','586.272','10.6201','4.78552','3624:0 3626:0 3693:0 3705:0 '), +('3626','shmv1273','530','3520','3520','67','70','0','-3569.67','423.902','28.3305','4.75019','3625:0 '), +('3627','shmv1274','530','3520','3520','67','70','0','-3346.71','753.909','-26.6716','0.0849111','3624:0 3628:0 3729:0 '), +('3628','shmv1275','530','3520','3520','67','70','0','-3232.2','784.314','-19.8198','0.00243998','3627:0 3629:0 '), +('3629','shmv1276','530','3520','3754','67','70','0','-3120.7','799.898','-22.5484','1.24337','3628:0 3630:0 3717:0 '), +('3630','shmv1277','530','3520','3520','67','70','0','-3121.46','1102.68','20.44','1.2473','3629:0 3631:0 3717:0 '), +('3631','shmv1278','530','3520','3753','67','70','0','-3106.91','1197.3','22.976','1.40439','3630:0 3632:0 3725:0 3726:0 '), +('3632','shmv1279','530','3520','3520','67','70','0','-3026.37','1314.44','8.68562','0.968488','3631:0 3633:0 3733:0 '), +('3633','shmv1280','530','3520','3520','67','70','0','-2904.03','1491.38','14.8027','0.960634','3632:0 3634:0 '), +('3634','shmv1281','530','3520','3750','67','70','0','-2867.58','1577.53','15.2282','2.85737','3633:0 3635:0 3751:0 '), +('3635','shmv1282','530','3520','3750','67','70','0','-2824.65','1682.74','22.3429','1.17662','3634:0 3636:0 '), +('3636','shmv1283','530','3520','3750','67','70','0','-2886.26','1742.5','43.8502','2.40576','3635:0 3637:0 '), +('3637','shmv1284','530','3520','3520','67','70','0','-2975.68','1908','104.519','2.08612','3636:0 3638:0 '), +('3638','shmv1285','530','3520','3520','67','70','0','-3102.48','1972.24','105.605','0.868752','3637:0 3639:0 3642:0 '), +('3639','shmv1286','530','3520','3520','67','70','0','-3180.63','2099.72','75.4619','0.291484','3638:0 3640:0 3643:0 '), +('3640','shmv1287','530','3520','3933','67','70','0','-3042.81','2156.41','79.2338','0.393586','3639:0 3641:0 '), +('3641','shmv1288','530','3520','3933','67','70','0','-3015.9','2104.22','90.4026','4.71721','3640:0 3642:0 '), +('3642','shmv1289','530','3520','3933','67','70','0','-3017.6','2046.19','96.9335','3.75903','3641:0 3638:0 '), +('3643','shmv1290','530','3520','3748','67','70','0','-3265.68','2066.96','73.9146','4.35987','3639:0 3652:0 3644:0 '), +('3644','shmv1291','530','3520','3748','67','70','0','-3287.9','1978.44','52.6152','2.07043','3643:0 3648:0 '), +('3645','shmv1294','530','3520','3748','67','70','0','-3389.68','1984.65','24.8931','1.53832','3646:0 3648:0 '), +('3646','shmv1295','530','3520','3748','67','70','0','-3405.52','2054.01','15.1476','3.06395','3647:0 3645:0 '), +('3647','shmv1296','530','3520','3748','67','70','0','-3321.42','2125.97','3.08638','2.40814','3646:0 '), +('3648','shmv1297','530','3520','3748','67','70','0','-3343.59','2008.19','32.1771','2.00758','3649:0 3645:0 3644:0 '), +('3649','shmv1298','530','3520','3748','67','70','0','-3419.14','2114.58','34.4588','2.18822','3648:0 3650:0 '), +('3650','shmv1299','530','3520','3748','67','70','0','-3495.43','2186.55','33.5276','5.96992','3649:0 3651:0 '), +('3651','shmv1301','530','3520','3748','67','70','0','-3410.98','2272.59','33.8565','1.54813','3650:0 '), +('3652','shmv1302','530','3520','3949','67','70','0','-3336.88','2207.97','74.9806','2.06645','3643:0 3598:0 3600:0 '), +('3653','shmv1303','530','3520','3932','67','70','0','-4003.29','1878.34','76.5277','5.50491','3617:0 3654:0 '), +('3654','shmv1304','530','3520','3932','67','70','0','-4085.04','1923.15','77.0726','5.10829','3653:0 3655:0 '), +('3655','shmv1305','530','3520','3932','67','70','0','-4058.27','1821.93','84.9857','4.00088','3654:0 3656:0 '), +('3656','shmv1306','530','3520','3932','67','70','0','-4103.15','1784.26','103.457','4.80985','3655:0 3657:0 '), +('3657','shmv1307','530','3520','3932','67','70','0','-4195.07','1861.58','109.916','5.5167','3656:0 3658:0 '), +('3658','shmv1308','530','3520','3822','67','70','0','-4334.08','1789.5','144.816','3.61996','3657:0 3659:0 '), +('3659','shmv1309','530','3520','3822','67','70','0','-4420.11','1797.35','157.396','4.44855','3658:0 3660:0 3668:0 '), +('3660','shmv1310','530','3520','3822','67','70','0','-4448.82','1724.98','158.876','4.33467','3659:0 3661:0 3666:0 '), +('3661','shmv1311','530','3520','3822','67','70','0','-4383.86','1707.44','156.573','5.0533','3660:0 3662:0 '), +('3662','shmv1312','530','3520','3822','67','70','0','-4361','1640.49','156.748','5.04152','3661:0 3663:0 3665:0 '), +('3663','shmv1313','530','3520','3822','67','70','0','-4252.69','1521.47','129.453','5.45072','3662:0 3664:0 3672:0 '), +('3664','shmv1314','530','3520','3822','67','70','0','-4141.51','1512.19','103.518','6.20863','3663:0 3619:0 '), +('3665','shmv1315','530','3520','3822','67','70','0','-4423.22','1575.75','164.241','3.96398','3662:0 3666:0 '), +('3666','shmv1316','530','3520','3822','67','70','0','-4491.12','1668.12','165.043','2.20469','3665:0 3660:0 3667:0 '), +('3667','shmv1317','530','3520','3822','67','70','0','-4564.8','1670.31','174.579','3.11182','3666:0 '), +('3668','shmv1318','530','3520','3520','67','70','0','-4426.81','1860.99','159.258','2.50232','3659:0 3669:0 '), +('3669','shmv1319','530','3520','3930','67','70','0','-4488.03','1934.26','144.075','1.12787','3668:0 3670:0 '), +('3670','shmv1320','530','3520','3930','67','70','0','-4450.04','1988.33','105.344','2.32482','3669:0 3671:0 '), +('3671','shmv1321','530','3520','3930','67','70','0','-4500.91','2022.31','100.196','4.19013','3670:0 '), +('3672','shmv1323','530','3520','3929','67','70','0','-4234.47','1405.21','129.348','4.72107','3663:0 3673:0 '), +('3673','shmv1324','530','3520','3929','67','70','0','-4309.54','1384.96','143.997','3.08352','3672:0 3674:0 '), +('3674','shmv1325','530','3520','3950','67','70','0','-4558.9','1328.47','135.392','6.27616','3673:0 3675:0 '), +('3675','shmv1326','530','3520','3929','67','70','0','-4452.67','1313.79','113.624','0.000826359','3674:0 3676:0 '), +('3676','shmv1327','530','3520','3929','67','70','0','-4318.73','1322.33','73.5928','6.21333','3675:0 3677:0 3680:0 '), +('3677','shmv1328','530','3520','3929','67','70','0','-4226.21','1305.14','56.0568','6.20155','3676:0 3678:0 '), +('3678','shmv1329','530','3520','3929','67','70','0','-4117.45','1299.67','52.606','1.29281','3677:0 3679:0 '), +('3679','shmv1330','530','3520','3520','67','70','0','-4096.52','1451.91','87.6478','5.2198','3678:0 3619:0 '), +('3680','shmv1331','530','3520','3929','67','70','0','-4403.59','1300.45','84.0575','3.42125','3676:0 3681:0 '), +('3681','shmv1332','530','3520','3520','67','70','0','-4511.47','1203.3','34.9082','3.43695','3680:0 3682:0 '), +('3682','shmv1333','530','3520','3945','67','70','0','-4641.99','1163.55','-14.0182','0.205018','3681:0 3683:0 '), +('3683','shmv1334','530','3520','3945','67','70','0','-4660.62','1088.53','0.90432','5.98162','3682:0 '), +('3684','shmv1335','530','3520','3938','67','70','1','-4090.85','1104.54','41.0582','5.28662','3621:0 3685:0 '), +('3685','shmv1336','530','3520','3758','67','70','0','-4110.45','861.06','9.26995','4.56397','3684:0 3622:0 3623:0 3686:0 '), +('3686','shmv1337','530','3520','3939','67','70','0','-4149.68','586.65','8.64057','4.66214','3685:0 3687:0 '), +('3687','shmv1338','530','3520','3939','67','70','0','-4184.61','489.528','29.5585','1.24958','3688:0 3689:0 3686:0 '), +('3688','shmv1339','530','3520','3939','67','70','0','-4114.39','424.914','34.9882','2.74575','3687:0 '), +('3689','shmv1341','530','3520','3939','67','70','0','-4224.07','420.493','51.1341','1.46947','3687:0 3690:0 '), +('3690','shmv1342','530','3520','3939','67','70','0','-4299.45','334.074','109.437','3.99453','3689:0 3691:0 '), +('3691','shmv1344','530','3520','3939','67','70','0','-4202.19','250.869','124.805','2.69862','3690:0 3692:0 '), +('3692','shmv1345','530','3520','3939','67','70','0','-4203.68','345.854','115.285','1.493','3691:0 '), +('3693','shmv1346','530','3520','3520','67','70','0','-3501.72','557.894','16.7235','0.323666','3625:0 3694:0 '), +('3694','shmv1347','530','3520','3757','67','70','0','-3426.99','580.376','42.2179','5.95105','3693:0 3695:0 '), +('3695','shmv1348','530','3520','3757','67','70','0','-3327.45','544.282','74.5225','4.63944','3694:0 3696:0 '), +('3696','shmv1349','530','3520','3757','67','70','0','-3333.62','454.382','99.8124','6.05315','3695:0 3697:0 3704:0 '), +('3697','shmv1350','530','3520','3757','67','70','0','-3228.24','439.514','104.731','3.31604','3696:0 3698:0 '), +('3698','shmv1351','530','3520','3757','67','70','0','-3392.06','415.921','103.96','4.61195','3697:0 3699:0 3704:0 '), +('3699','shmv1352','530','3520','3757','67','70','0','-3390.43','293.527','103.961','4.72583','3698:0 3700:0 '), +('3700','shmv1353','530','3520','3757','67','70','0','-3337.54','294.86','116.382','1.36824','3699:0 3701:0 '), +('3701','shmv1354','530','3520','3757','67','70','0','-3324.03','371.217','120.458','5.9746','3700:0 3702:0 '), +('3702','shmv1355','530','3520','3757','67','70','0','-3231.77','337.352','127.504','4.49021','3701:0 3703:0 '), +('3703','shmv1356','530','3520','3757','67','70','0','-3239.92','279.443','137.131','4.57267','3702:0 '), +('3704','shmv1357','530','3520','3757','67','70','0','-3416.2','459.709','103.935','5.91964','3696:0 3698:0 '), +('3705','shmv1358','530','3520','3756','67','70','0','-3685.39','550.038','35.0742','2.93905','3625:0 3706:0 '), +('3706','shmv1359','530','3520','3756','67','70','0','-3813.8','565.02','71.7251','4.91432','3705:0 3707:0 '), +('3707','shmv1360','530','3520','3756','67','70','0','-3802.41','461.803','99.9172','4.68655','3706:0 3708:0 3716:0 '), +('3708','shmv1361','530','3520','3756','67','70','0','-3896.83','442.087','104.97','6.26668','3707:0 3709:0 '), +('3709','shmv1362','530','3520','3756','67','70','0','-3719.39','417.216','104.061','2.6833','3708:0 3710:0 3716:0 '), +('3710','shmv1363','530','3520','3756','67','70','0','-3732.23','295.716','104.066','3.10939','3709:0 3711:0 '), +('3711','shmv1364','530','3520','3756','67','70','0','-3786.92','297.908','116.455','3.10153','3710:0 3712:0 '), +('3712','shmv1365','530','3520','3756','67','70','0','-3812.37','248.065','120.604','1.80405','3711:0 3713:0 '), +('3713','shmv1366','530','3520','3756','67','70','0','-3838.38','357.549','120.582','1.80405','3712:0 3714:0 '), +('3714','shmv1367','530','3520','3756','67','70','0','-3904.87','343.016','127.62','4.76657','3713:0 3715:0 '), +('3715','shmv1368','530','3520','3756','67','70','0','-3890.45','282.542','137.239','3.8665','3714:0 '), +('3716','shmv1369','530','3520','3756','67','70','0','-3719.66','475.869','104.038','4.2914','3709:0 3707:0 '), +('3717','shmv1370','530','3520','3754','67','70','0','-3124.49','886.832','-14.3889','1.56135','3629:0 3630:0 3718:0 '), +('3718','shmv1371','530','3520','3754','67','70','1','-3061.33','830.398','-10.215','4.92836','3717:0 3719:0 '), +('3719','shmv1372','530','3520','3520','67','70','0','-3030.95','544.921','-1.12904','0.00390148','3718:0 3720:0 '), +('3720','shmv1373','530','3520','3520','67','70','0','-2881.37','547.854','-13.197','0.0196085','3719:0 3721:0 '), +('3721','shmv1374','530','3520','3520','67','70','0','-2744.62','677.917','-16.4588','1.42468','3720:0 3722:0 '), +('3722','shmv1375','530','3520','3520','67','70','0','-2720.25','857.215','-3.3875','4.54664','3721:0 3723:0 '), +('3723','shmv1376','530','3520','3520','67','70','0','-2768.24','985.559','-2.57223','2.06085','3722:0 3724:0 '), +('3724','shmv1377','530','3520','3520','67','70','0','-2871.08','1130.08','8.92665','3.16433','3723:0 3725:0 '), +('3725','shmv1378','530','3520','3520','67','70','0','-2984.92','1134.44','-3.92959','2.62712','3724:0 3631:0 '), +('3726','shmv1379','530','3520','3753','67','70','0','-3245.6','1189.8','59.4774','4.33927','3631:0 3727:0 3731:0 '), +('3727','shmv1380','530','3520','3753','67','70','0','-3222.89','1092.48','64.7857','6.14175','3726:0 3728:0 3731:0 3732:0 '), +('3728','shmv1381','530','3520','3753','67','70','0','-3292.57','985.163','40.7467','5.15608','3727:0 3730:0 '), +('3729','shmv1382','530','3520','3753','67','70','0','-3344.18','870.1','-14.6762','4.80342','3627:0 3730:0 '), +('3730','shmv1383','530','3520','3753','67','70','0','-3205.87','897.037','49.1172','0.192352','3729:0 3728:0 '), +('3731','shmv1384','530','3520','3753','67','70','0','-3399.1','1175.31','50.0129','3.15252','3726:0 3727:0 3732:0 '), +('3732','shmv1385','530','3520','3753','67','70','0','-3335.34','1043.32','47.1116','0.383668','3727:0 3731:0 3772:0 '), +('3733','shmv1386','530','3520','3749','67','70','0','-2929.04','1324.33','7.92349','5.43769','3632:0 3734:0 '), +('3734','shmv1387','530','3520','3749','67','70','0','-2845.89','1301.54','6.02801','3.20519','3733:0 3735:0 '), +('3735','shmv1388','530','3520','3749','67','70','0','-2847.46','1231.33','6.78906','1.62065','3734:0 3736:0 '), +('3736','shmv1390','530','3520','3749','67','70','0','-2732.51','1138.3','2.89233','2.40802','3735:0 3737:0 '), +('3737','shmv1391','530','3520','3749','67','70','0','-2634.08','1230.04','11.146','1.86804','3738:0 3736:0 '), +('3738','shmv1392','530','3520','3749','67','70','0','-2651.9','1282.45','26.4813','3.01869','3737:0 3739:0 '), +('3739','shmv1393','530','3520','3749','67','70','0','-2710.39','1300.75','33.7332','1.28218','3738:0 3740:0 '), +('3740','shmv1394','530','3520','3749','67','70','0','-2690.89','1361.98','35.0836','6.28124','3739:0 3741:0 '), +('3741','shmv1395','530','3520','3749','67','70','0','-2617.04','1355.97','37.2382','0.18183','3740:0 3742:0 '), +('3742','shmv1396','530','3520','3749','67','70','0','-2769.22','1301.12','33.2111','3.48993','3741:0 3743:0 3745:0 '), +('3743','shmv1397','530','3520','3749','67','70','0','-2721.9','1224.71','33.0771','5.26886','3742:0 3744:0 '), +('3744','shmv1398','530','3520','3749','67','70','0','-2817.63','1365.79','38.5145','5.53669','3743:0 3745:0 '), +('3745','shmv1399','530','3520','3749','67','70','0','-2766.14','1383.17','37.6732','0.325571','3744:0 3742:0 '), +('3746','shmv1400','530','3520','3743','67','70','0','-3280.76','3002.45','141.658','1.22092','3586:0 3747:0 '), +('3747','shmv1401','530','3520','3743','67','70','0','-3273.62','3077.71','139.787','0.31771','3746:0 3748:0 '), +('3748','shmv1402','530','3520','3743','67','70','0','-3211.67','3079.46','126.239','5.52566','3747:0 3749:0 '), +('3749','shmv1403','530','3520','3743','67','70','0','-3143.09','3024.9','104.296','5.41086','3584:0 3748:0 '), +('3750','shmv1405','530','3520','3752','67','70','0','-3656.3','2553.97','77.5087','0.0120506','3601:0 3605:0 '), +('3751','shmv1406','530','3520','3750','67','70','0','-2941.74','1589.4','40.8211','2.08903','3634:0 3752:0 '), +('3752','shmv1407','530','3520','3750','67','70','0','-2978.15','1677.35','67.3118','2.1715','3751:0 3753:0 '), +('3753','shmv1408','530','3520','3750','67','70','0','-3032.93','1672.83','67.4735','3.60878','3752:0 3754:0 3757:0 '), +('3754','shmv1409','530','3520','3750','67','70','0','-3131.22','1754.16','79.6664','2.79589','3753:0 3755:0 '), +('3755','shmv1410','530','3520','3750','67','70','0','-3191.61','1765.69','88.3304','2.95297','3754:0 3756:0 '), +('3756','shmv1411','530','3520','3520','67','70','0','-3345.81','1700.42','95.7466','5.65474','3755:0 3757:0 '), +('3757','shmv1412','530','3520','3520','67','70','0','-3188.39','1586.02','63.3792','0.565352','3756:0 3753:0 3758:0 3760:0 '), +('3758','shmv1413','530','3520','3520','67','70','0','-3052.53','1476.53','17.9049','2.50057','3757:0 3759:0 '), +('3759','shmv1414','530','3520','3520','67','70','0','-3107.69','1379.15','12.0022','4.19702','3758:0 '), +('3760','shmv1415','530','3520','3750','67','70','0','-3103.19','1550.28','39.5694','2.77978','3757:0 3761:0 '), +('3761','shmv1416','530','3520','3520','67','70','0','-3196.31','1501.96','57.1479','2.95256','3760:0 3762:0 '), +('3762','shmv1417','530','3520','3747','67','70','0','-3282.99','1527.47','52.2295','5.92921','3761:0 3763:0 '), +('3763','shmv1418','530','3520','3747','67','70','0','-3394.49','1550.46','48.1579','2.93214','3762:0 3764:0 '), +('3764','shmv1419','530','3520','3747','67','70','0','-3500.47','1622.81','43.8418','3.38295','3763:0 3765:0 '), +('3765','shmv1420','530','3520','3747','67','70','0','-3693.06','1558.48','46.9758','1.02649','3764:0 3766:0 '), +('3766','shmv1421','530','3520','3747','67','70','0','-3828.74','1475.85','43.1674','4.10996','3765:0 3767:0 '), +('3767','shmv1422','530','3520','3747','67','70','0','-3888.12','1398.74','43.6642','4.04478','3766:0 3768:0 '), +('3768','shmv1423','530','3520','3747','67','70','0','-3858.68','1336.7','42.2274','4.47911','3767:0 3769:0 '), +('3769','shmv1424','530','3520','3747','67','70','0','-3883.95','1244.97','42.1164','4.44376','3768:0 3770:0 3771:0 '), +('3770','shmv1425','530','3520','3520','67','70','0','-3774.38','1225.16','84.9138','6.04598','3769:0 '), +('3771','shmv1426','530','3520','3520','67','70','0','-3969.44','1164.97','46.6673','5.0878','3769:0 3621:0 '), +('3772','shmv1427','530','3520','3753','67','70','0','-3423.61','1074.97','42.8376','3.2539','3732:0 3773:0 '), +('3773','shmv1428','530','3520','3520','67','70','0','-3574.75','1067.86','40.499','3.19107','3772:0 3774:0 '), +('3774','shmv1429','530','3520','3821','67','70','0','-3680.33','1058.59','68.153','2.90833','3773:0 3775:0 3776:0 '), +('3775','shmv1430','530','3520','3821','67','70','0','-3760.76','1068.11','70.1361','6.14809','3774:0 '), +('3776','shmv1431','530','3520','3520','67','70','0','-3642.27','894.441','45.4124','5.92247','3774:0 3777:0 '), +('3777','shmv1432','530','3520','3520','67','70','0','-3522.57','841.492','-2.90146','4.68834','3776:0 3778:0 '), +('3778','shmv1433','530','3520','3520','67','70','0','-3522.09','776.814','-14.2281','4.71976','3777:0 3624:0 '), +('3779','bt001','571','3537','4129','68','73','5','2817.61','6168.46','85.5727','3.94245','3780:0 '), +('3780','bt002','571','3537','4020','68','73','5','2726.11','6082.88','72.3512','3.45943','3779:0 3781:0 '), +('3781','bt003','571','3537','4020','68','73','0','2604.99','6083.81','53.2961','2.26563','3780:0 3782:0 3832:0 '), +('3782','bt004','571','3537','4020','68','73','0','2505.63','6178.07','53.1691','5.59574','3781:0 3783:0 3839:0 '), +('3783','bt005','571','3537','4130','68','73','0','2589.16','6313.74','86.8706','0.137219','3782:0 3784:0 3832:0 '), +('3784','bt006','571','3537','3537','68','73','0','2818.32','6342.97','101.266','3.15078','3783:0 3785:0 3791:0 '), +('3785','bt007','571','3537','3537','68','73','0','2823.08','6550.12','55.029','4.6077','3784:0 3786:0 3790:0 '), +('3786','bt008','571','3537','4106','68','73','0','2946.88','6730.38','13.9226','0.998791','3785:0 3787:0 3788:0 '), +('3787','bt009','571','3537','4106','68','73','0','3100','6716.93','0.818624','2.88374','3786:0 '), +('3788','bt010','571','3537','4106','68','73','0','2769.6','6817.13','0.382068','5.63656','3786:0 3789:0 '), +('3789','bt011','571','3537','4031','68','73','0','2511.71','6709.76','8.65944','0.559735','3788:0 3790:0 '), +('3790','bt012','571','3537','4130','68','73','0','2598.94','6513.07','41.9513','4.98704','3789:0 3785:0 '), +('3791','bt013','571','3537','3537','68','73','0','2977.63','6387.87','96.0276','3.55919','3784:0 3792:0 '), +('3792','bt014','571','3537','3537','68','73','0','3117.74','6401.57','85.8869','4.84331','3791:0 3793:0 '), +('3793','bt015','571','3537','3537','68','73','0','3146.95','6242.85','96.6515','5.78972','3792:0 3794:0 '), +('3794','bt016','571','3537','4105','68','73','0','3332.48','6165.54','75.7667','2.87983','3793:0 '), +('3795','bt017','571','3537','3537','68','73','0','3270.88','5961.4','84.9677','4.81845','3796:0 3797:0 3826:0 '), +('3796','bt018','571','3537','3537','68','73','1','3624.25','5933.39','136.215','3.44596','3797:0 3795:0 '), +('3797','bt019','571','3537','4109','68','73','0','3297.83','5691.4','59.3737','4.58728','3796:0 3798:0 3795:0 '), +('3798','bt020','571','3537','3537','68','73','0','3304.79','5470.21','55.3479','4.73257','3797:0 3799:0 3852:0 '), +('3799','bt021','571','3537','3537','68','73','0','3482.47','5425.21','50.7507','1.49045','3798:0 3800:0 '), +('3800','bt022','571','3537','3537','68','73','0','3532.8','5540.21','65.5113','4.20635','3799:0 3801:0 3937:0 '), +('3801','bt023','571','3537','3537','68','73','0','3504.09','5638.44','63.5206','1.09146','3800:0 3802:0 3937:0 '), +('3802','bt024','571','3537','3537','68','73','0','3651.69','5686.01','66.2432','0.569961','3801:0 3803:0 3937:0 '), +('3803','bt025','571','3537','3537','68','73','0','3809.81','5773.85','68.3307','0.134065','3802:0 3804:0 3937:0 '), +('3804','bt026','571','3537','4127','68','73','0','3969.32','5781.55','74.0472','0.432523','3803:0 3805:0 3896:0 '), +('3805','bt027','571','3537','3537','68','73','0','4170.05','5855.36','62.8346','0.137999','3804:0 3806:0 3816:0 3819:0 '), +('3806','bt028','571','3537','3537','68','73','0','4324.68','6015.4','25.5474','1.07655','3805:0 3807:0 '), +('3807','bt029','571','3537','4099','68','73','0','4401.85','6148.33','0.553693','4.54723','3806:0 3808:0 '), +('3808','bt030','571','3537','4033','68','73','0','4287.27','6236.78','0.383892','3.29845','3807:0 3809:0 '), +('3809','bt031','571','3537','4033','68','73','0','4184.73','6193.42','9.12228','1.7952','3808:0 3810:0 '), +('3810','bt032','571','3537','4033','68','73','0','4149.36','6252.75','30.8789','2.52797','3809:0 3811:0 3812:0 '), +('3811','bt033','571','3537','4097','68','73','0','4056.14','6303.43','25.1363','1.18887','3810:0 '), +('3812','bt034','571','3537','4097','68','73','0','4052.65','6367.67','27.4251','1.08833','3810:0 3813:0 3815:0 '), +('3813','bt035','571','3537','4097','68','73','0','3991.71','6304.02','8.08314','0.901407','3812:0 '), +('3814','bt036','571','3537','4097','68','73','0','3854.4','6397.67','24.5748','5.34115','3815:0 '), +('3815','bt037','571','3537','4097','68','73','0','3960.88','6426.15','11.3447','0.519589','3812:0 3814:0 '), +('3816','bt038','571','3537','4134','68','73','0','4404.37','5906.55','55.8142','0.126085','3805:0 3817:0 3818:0 '), +('3817','bt039','571','3537','4132','68','73','0','4547.19','6017.64','76.7052','4.37901','3816:0 '), +('3818','bt040','571','3537','4122','68','73','5','4484.77','5763.64','79.4306','1.41963','3816:0 3819:0 '), +('3819','bt041','571','3537','4133','68','73','0','4321.96','5776.59','72.5697','2.66048','3818:0 3805:0 '), +('3820','bt042','571','3537','4032','68','73','3','2227.28','5312.07','10.5729','1.23497','3821:0 3851:0 '), +('3821','bt043','571','3537','4101','68','73','0','2273.96','5439.49','1.81269','1.1525','3820:0 3822:0 3833:0 3916:0 '), +('3822','bt044','571','3537','4101','68','73','0','2418.25','5390.85','3.76168','0.12363','3821:0 3823:0 '), +('3823','bt045','571','3537','3537','68','73','0','2601.94','5522.47','38.0963','6.28036','3822:0 3824:0 3833:0 3840:0 3848:0 '), +('3824','bt046','571','3537','3537','68','73','0','2887.65','5415.26','58.4905','5.60492','3823:0 3852:0 3859:0 '), +('3825','bt047','571','3537','4021','68','73','0','2976.39','5660.32','52.2738','1.38733','3826:0 '), +('3826','bt048','571','3537','4021','68','73','0','3019.96','5816.72','88.3166','0.8297','3825:0 3827:0 3795:0 '), +('3827','bt049','571','3537','4020','68','73','0','2993.07','5973.66','112.995','5.04023','3826:0 3828:0 '), +('3828','bt050','571','3537','4129','68','73','0','2915.6','6074.59','78.0969','1.4863','3827:0 3829:0 3844:0 '), +('3829','bt051','571','3537','4020','68','73','0','2970.01','6149.83','61.7495','2.0298','3828:0 3830:0 '), +('3830','bt052','571','3537','4020','68','73','0','2899.1','6282.26','61.4057','2.97228','3829:0 3831:0 '), +('3831','bt053','571','3537','4020','68','73','0','2773.74','6303.56','77.083','3.07674','3830:0 3832:0 '), +('3832','bt054','571','3537','4020','68','73','0','2654.28','6218.95','38.8344','4.55721','3831:0 3781:0 3783:0 '), +('3833','bt055','571','3537','3537','68','73','0','2316.99','5624.68','36.408','5.33239','3823:0 3821:0 3834:0 '), +('3834','bt056','571','3537','3537','68','73','0','2143.16','5781.38','58.7998','1.77058','3833:0 3837:0 '), +('3835','bt057','571','3537','4028','68','73','0','1868.5','5774.99','0.860459','1.34802','3917:0 '), +('3836','bt058','571','3537','4028','68','73','0','1923.91','6030.9','10.7946','1.48154','3837:0 3918:0 '), +('3837','bt059','571','3537','3537','68','73','0','2116.07','6079.06','52.9292','3.89037','3836:0 3834:0 3838:0 3846:0 '), +('3838','bt060','571','3537','4021','68','73','0','2104.14','6314.43','60.0072','0.106311','3837:0 3839:0 '), +('3839','bt061','571','3537','4021','68','73','0','2357.04','6208.05','40.7146','2.64945','3838:0 3782:0 '), +('3840','bt062','571','3537','4022','68','73','0','2647.51','5693.12','46.7099','1.50275','3823:0 3841:0 '), +('3841','bt063','571','3537','4022','68','73','0','2631.68','5852.04','45.3245','5.86957','3840:0 3842:0 '), +('3842','bt064','571','3537','4022','68','73','0','2763.87','5811.38','59.8902','6.23478','3841:0 3843:0 '), +('3843','bt065','571','3537','4021','68','73','0','2848.66','5818.04','101.625','5.91276','3842:0 3844:0 3845:0 '), +('3844','bt066','571','3537','4020','68','73','0','2837.49','5974.58','114.197','5.01191','3843:0 3828:0 '), +('3845','bt067','571','3537','4021','68','73','0','2607.08','5956.42','94.4153','5.56483','3843:0 3846:0 3847:0 '), +('3846','bt068','571','3537','4022','68','73','0','2327.62','5973.77','98.5945','4.36317','3837:0 3845:0 '), +('3847','bt069','571','3537','4022','68','73','0','2489.14','5813.21','139.945','0.867364','3845:0 '), +('3848','bt070','571','3537','4111','68','73','0','2684.66','5308.92','31.1745','1.9606','3823:0 3849:0 '), +('3849','bt071','571','3537','4111','68','73','0','2495.15','5199.97','32.4263','0.802134','3848:0 3850:0 '), +('3850','bt072','571','3537','4032','68','73','0','2416.87','5247.86','1.88448','3.32877','3849:0 3851:0 '), +('3851','bt073','571','3537','4032','68','73','3','2334.99','5249.7','9.01216','2.3698','3820:0 3850:0 '), +('3852','bt074','571','3537','3537','68','73','0','3128.49','5378.84','58.5466','0.00337601','3824:0 3798:0 3853:0 3859:0 '), +('3853','bt075','571','3537','3537','68','73','0','3264.11','5285.37','39.4799','3.97355','3852:0 3854:0 3891:0 '), +('3854','bt076','571','3537','4043','68','73','0','3152.24','5121.1','38.3072','4.18325','3853:0 3855:0 3860:0 3891:0 '), +('3855','bt077','571','3537','4043','68','73','0','2949.22','5102.38','35.483','3.44497','3856:0 3859:0 3854:0 '), +('3856','bt078','571','3537','4043','68','73','0','2736.6','5010.38','28.1472','3.12924','3855:0 3857:0 '), +('3857','bt079','571','3537','4115','68','73','0','2737.31','5202.71','29.0469','0.938766','3856:0 3858:0 3859:0 '), +('3858','bt080','571','3537','4115','68','73','0','2535.47','5082.9','7.68824','3.5887','3857:0 '), +('3859','bt081','571','3537','3537','68','73','0','2918.19','5295.64','60.2339','1.05579','3824:0 3855:0 3857:0 3852:0 '), +('3860','bt082','571','3537','4041','68','73','0','3128.73','4853.66','2.08073','1.16652','3854:0 3861:0 '), +('3861','bt083','571','3537','4041','68','73','0','3050.52','4655.31','2.27362','4.47304','3860:0 3862:0 '), +('3862','bt084','571','3537','4043','68','73','0','3249.06','4558.38','29.5811','5.62916','3861:0 3863:0 '), +('3863','bt085','571','3537','4043','68','73','0','3284.24','4264.38','24.2109','5.22075','3862:0 3864:0 3938:0 '), +('3864','bt086','571','3537','4037','68','73','4','3412.42','4072.4','16.8945','0.69685','3863:0 3865:0 3866:0 3873:0 3939:0 '), +('3865','bt087','571','3537','4037','68','73','5','3463.15','4158.38','17.1932','1.45869','3864:0 '), +('3866','bt088','571','3537','4043','68','73','0','3662.21','3965.65','26.4976','5.18541','3864:0 3867:0 3878:0 '), +('3867','bt089','571','3537','4125','68','73','0','3676.8','3741.97','49.5236','5.32678','3866:0 3868:0 3878:0 '), +('3868','bt090','571','3537','4125','68','73','0','3823.07','3647','46.2292','5.63701','3867:0 3869:0 3871:0 3872:0 '), +('3869','bt091','571','3537','4135','68','73','0','4026.86','3532.95','104.358','5.75874','3868:0 3870:0 3872:0 '), +('3870','bt092','571','3537','4125','68','73','0','3935.56','3404.04','80.8418','5.58987','3869:0 3871:0 '), +('3871','bt093','571','3537','4125','68','73','0','3825.9','3505.46','64.8278','1.04239','3868:0 3870:0 '), +('3872','bt094','571','3537','4125','68','73','0','3966.88','3721.48','61.9247','4.14707','3868:0 3869:0 '), +('3873','bt095','571','3537','4042','68','73','0','3416.97','3883.04','26.1293','5.22225','3864:0 3874:0 3939:0 '), +('3874','bt096','571','3537','4042','68','73','0','3450.42','3652.26','27.974','2.1592','3873:0 3875:0 3940:0 '), +('3875','bt097','571','3537','4042','68','73','0','3456.33','3430.49','17.996','4.9827','3874:0 3876:0 '), +('3876','bt098','571','3537','4042','68','73','0','3518.4','3187.54','25.0806','4.80206','3875:0 3877:0 '), +('3877','bt099','571','65','4478','68','73','0','3572.51','3037.08','26.2165','3.86352','3876:0 4109:0 '), +('3878','bt100','571','3537','3537','68','73','0','3907.42','4000.44','45.1941','4.47613','3866:0 3867:0 3879:0 '), +('3879','bt101','571','3537','3537','68','73','0','4042.02','4199.98','49.6907','0.696007','3878:0 3880:0 '), +('3880','bt102','571','3537','3537','68','73','0','4186.94','4464.21','27.3466','1.05336','3879:0 3881:0 '), +('3881','bt103','571','3537','4119','68','73','0','4191.29','4697.78','12.0312','2.68463','3880:0 3882:0 3883:0 3884:0 3908:0 '), +('3882','bt104','571','3537','4118','68','73','0','4358.92','4576.58','16.3385','5.84976','3881:0 '), +('3883','bt105','571','3537','4036','68','73','0','3992.84','4812.66','11.7213','5.55524','3881:0 '), +('3884','bt106','571','3537','4119','68','73','0','3978.85','4564.24','-2.51875','3.44252','3881:0 3885:0 3914:0 '), +('3885','bt107','571','3537','4117','68','73','0','3699.55','4487.54','-0.818554','2.97836','3884:0 3886:0 '), +('3886','bt108','571','3537','4117','68','73','0','3443.04','4422.46','-0.530089','0.645731','3885:0 3887:0 '), +('3887','bt109','571','3537','4035','68','73','0','3568.75','4584.96','-13.5973','0.527922','3886:0 3888:0 '), +('3888','bt110','571','3537','4035','68','73','0','3686.72','4734.84','-12.073','1.14839','3887:0 3889:0 3915:0 '), +('3889','bt111','571','3537','4035','68','73','0','3703.7','4927.72','-12.9951','2.73489','3888:0 3890:0 3912:0 '), +('3890','bt112','571','3537','4116','68','73','0','3479.66','5024.98','-1.50934','2.71133','3889:0 3891:0 '), +('3891','bt113','571','3537','4116','68','73','0','3344.22','5119.48','13.0385','2.55424','3890:0 3853:0 3854:0 3892:0 '), +('3892','bt114','571','3537','4116','68','73','0','3589.81','5076.79','13.0073','6.10424','3891:0 3893:0 '), +('3893','bt115','571','3537','3537','68','73','0','3668.09','5254.47','34.8015','1.05413','3892:0 3894:0 3911:0 '), +('3894','bt116','571','3537','3537','68','73','0','3717.74','5416.1','41.956','1.99268','3893:0 3895:0 3937:0 '), +('3895','bt117','571','3537','3537','68','73','0','3919.2','5534.82','36.9202','0.89312','3894:0 3896:0 '), +('3896','bt118','571','3537','3537','68','73','0','4101.61','5617.94','41.1963','5.88275','3895:0 3804:0 3897:0 '), +('3897','bt119','571','3537','3537','68','73','0','4252.89','5577.24','46.4033','5.33689','3896:0 3898:0 3903:0 '), +('3898','bt120','571','3537','4123','68','73','0','4429.17','5431.27','38.6653','5.43664','3897:0 3899:0 '), +('3899','bt121','571','3537','4123','68','73','0','4389.18','5365.34','1.39626','5.27172','3898:0 3900:0 '), +('3900','bt122','571','3537','4123','68','73','0','4489.75','5395.86','-16.0358','0.830292','3899:0 3901:0 '), +('3901','bt123','571','3537','4123','68','73','0','4444.7','5327.16','-22.3982','5.89611','3900:0 3902:0 '), +('3902','bt124','571','3537','4123','68','73','0','4542.22','5322.8','-31.8246','3.56034','3901:0 '), +('3903','bt125','571','3537','4123','68','73','0','4522.99','5561.63','52.4235','6.06731','3897:0 3904:0 4644:0 '), +('3904','bt126','571','3537','4123','68','73','0','4530.95','5430.29','84.8166','1.59052','3903:0 3905:0 '), +('3905','bt127','571','3537','4123','68','73','0','4525.48','5247.8','73.7381','4.37083','3904:0 3906:0 '), +('3906','bt128','571','3537','4123','68','73','0','4475.24','5066.41','51.9566','0.934718','3905:0 3907:0 '), +('3907','bt129','571','3537','4123','68','73','0','4378.74','5017.74','24.8345','2.46704','3906:0 3908:0 3909:0 '), +('3908','bt130','571','3537','3537','68','73','0','4344.7','4804.82','24.8231','4.24124','3907:0 3881:0 '), +('3909','bt131','571','3537','4116','68','73','0','4205.12','5146.23','13.0876','5.95813','3907:0 3910:0 '), +('3910','bt132','571','3537','4108','68','73','3','4060.38','5267.77','24.7438','5.56545','3909:0 3911:0 '), +('3911','bt133','571','3537','4116','68','73','0','3893.4','5186.84','13.0414','3.37026','3910:0 3893:0 3912:0 '), +('3912','bt134','571','3537','4116','68','73','0','3840.13','5037.99','-1.50988','0.413233','3911:0 3889:0 3913:0 '), +('3913','bt135','571','3537','4035','68','73','0','3925.28','4915.39','-12.887','4.60333','3912:0 3914:0 '), +('3914','bt136','571','3537','4035','68','73','0','3887.73','4693.73','-12.995','4.14386','3913:0 3884:0 3915:0 '), +('3915','bt137','571','3537','4035','68','73','0','3732.85','4647.89','-12.6525','0.821628','3914:0 3888:0 '), +('3916','bt138','571','3537','4101','68','73','0','2084.44','5480.67','0.253263','0.428928','3821:0 3917:0 '), +('3917','bt139','571','3537','3537','68','73','0','1935.03','5589.92','0.355845','5.97934','3916:0 3835:0 '), +('3918','bt140','571','3537','4029','68','73','0','1494.92','5829.11','1.38787','4.0072','3836:0 3919:0 '), +('3919','bt141','571','3537','4029','68','73','0','1384.5','5858.44','15.4001','4.19177','3918:0 3920:0 '), +('3920','bt142','571','3537','4029','68','73','0','1449.43','5734.18','1.3254','1.10514','3919:0 3921:0 '), +('3921','bt143','571','3537','4029','68','73','0','1402.52','5714.94','23.5628','4.00326','3920:0 3922:0 '), +('3922','bt144','571','3537','4029','68','73','0','1316.7','5605.69','5.52566','5.40127','3921:0 '), +('3923','bt145','571','3537','4024','68','73','0','4152.49','7034.79','165.636','2.43875','3924:0 3936:0 '), +('3924','bt146','571','3537','4024','68','73','0','3999','7159.1','167.793','0.0786215','3923:0 3925:0 '), +('3925','bt147','571','3537','4024','68','73','0','3840.49','7254.47','172.045','3.52653','3924:0 3926:0 3928:0 '), +('3926','bt148','571','3537','4024','68','73','0','3676.61','7110.41','160.171','4.50434','3925:0 3927:0 3929:0 '), +('3927','bt149','571','3537','4024','68','73','0','3597.15','7199.39','221.934','1.51276','3926:0 3928:0 3929:0 '), +('3928','bt150','571','3537','4024','68','73','0','3751.72','7333.67','208.956','0.378648','3927:0 3925:0 '), +('3929','bt151','571','3537','4024','68','73','0','3611.29','6969.11','170.734','1.15225','3926:0 3927:0 3930:0 '), +('3930','bt152','571','3537','4024','68','73','0','3644.46','6805.44','166.924','3.99932','3929:0 3931:0 '), +('3931','bt153','571','3537','4121','68','73','1','3587.41','6661.1','195.429','4.60014','3930:0 3932:0 '), +('3932','bt154','571','3537','4024','68','73','0','3749.63','6602.97','169.446','3.8658','3931:0 3933:0 3935:0 '), +('3933','bt155','571','3537','4024','68','73','0','3912.17','6596.2','168.227','0.700643','3932:0 3934:0 '), +('3934','bt156','571','3537','4024','68','73','0','3971.08','6785.73','156.758','4.88682','3933:0 3935:0 3936:0 '), +('3935','bt157','571','3537','4024','68','73','0','3826.86','6694.29','152.782','4.14698','3934:0 3932:0 '), +('3936','bt158','571','3537','4024','68','73','0','4074.45','6887.11','166.976','1.01716','3923:0 3934:0 '), +('3937','bt159','571','3537','4039','68','73','0','3663.21','5603.77','32.8151','3.07099','3800:0 3801:0 3802:0 3803:0 3894:0 '), +('3938','bt160','571','3537','4113','68','73','1','2913.96','4067.33','1.63263','0.498753','3863:0 3939:0 '), +('3939','bt161','571','3537','4138','68','73','3','3111.58','3838.69','22.5558','0.795238','3864:0 3873:0 3940:0 3938:0 '), +('3940','bt162','571','3537','4138','68','73','0','3323.93','3770.67','24.5945','5.57327','3939:0 3874:0 '), +('3941','hf163','571','495','3998','68','73','3','1418.1','-3190.73','162.344','4.57189','3942:0 '), +('3942','hf164','571','495','3998','68','73','2','1365.12','-3385.23','185.736','4.52084','3941:0 3943:0 '), +('3943','hf165','571','495','4048','68','73','0','1219.72','-3550.97','164.966','4.23495','3942:0 3944:0 '), +('3944','hf166','571','495','4048','68','73','0','1121.91','-3657.07','156.248','4.07787','3943:0 3945:0 3985:0 '), +('3945','hf167','571','495','4048','68','73','0','1046.11','-3833.51','162.833','4.6512','3944:0 3946:0 3958:0 3985:0 '), +('3946','hf168','571','495','4048','68','73','0','920.447','-4015.29','170.279','3.21786','3945:0 3947:0 3958:0 '), +('3947','hf169','571','495','3992','68','73','0','694.778','-3872.94','246.328','2.73877','3946:0 3948:0 '), +('3948','hf170','571','495','495','68','73','0','507.763','-4025.28','259.283','4.81221','3947:0 3949:0 '), +('3949','hf171','571','495','4260','68','73','0','397.137','-4223.42','249.354','3.78334','3948:0 3950:0 3979:0 '), +('3950','hf172','571','495','495','68','73','0','222.582','-4391.88','248.592','4.40773','3949:0 3951:0 4086:0 '), +('3951','hf173','571','495','3991','68','73','0','253.033','-4572.61','240.212','5.55441','3950:0 3952:0 4082:0 '), +('3952','hf174','571','495','3991','68','73','5','371.97','-4633.54','245.156','2.78588','3951:0 3953:0 3957:0 4085:0 '), +('3953','hf175','571','495','3991','68','73','4','407.209','-4712.58','228.909','5.83714','3952:0 3954:0 '), +('3954','hf176','571','495','3991','68','73','0','510.661','-4763.01','208.318','0.924468','3953:0 3955:0 4082:0 '), +('3955','hf177','571','495','495','68','73','0','628.585','-4581.53','203.482','0.904833','3954:0 3956:0 '), +('3956','hf178','571','495','3991','68','73','0','460.455','-4423.52','211.022','2.64078','3955:0 3957:0 3979:0 '), +('3957','hf179','571','495','3991','68','73','4','383.884','-4495.8','236.504','4.36866','3956:0 3952:0 '), +('3958','hf180','571','495','495','68','73','0','1081.71','-4036.81','154.348','4.76135','3945:0 3946:0 3985:0 '), +('3959','hf181','571','495','495','68','73','0','1215.66','-4263.51','149.864','5.26401','3960:0 '), +('3960','hf182','571','495','495','68','73','0','1258.74','-4522.15','173.24','3.83852','3959:0 3961:0 '), +('3961','hf183','571','495','495','68','73','0','1124.26','-4644.36','183.694','3.62646','3960:0 3962:0 '), +('3962','hf184','571','495','495','68','73','0','857.223','-4730.31','113.82','0.273598','3961:0 3963:0 '), +('3963','hf185','571','495','495','68','73','0','706.968','-4825.12','79.1984','4.0553','3962:0 3964:0 '), +('3964','hf186','571','495','3981','68','73','2','634.301','-4890.02','28.6664','4.92709','3963:0 3965:0 3978:0 '), +('3965','hf187','571','495','3981','68','73','2','682.36','-4963','6.24529','0.414974','3964:0 3966:0 3978:0 '), +('3966','hf188','571','495','3982','68','73','0','854.979','-4933.54','4.27721','0.230407','3965:0 3967:0 '), +('3967','hf189','571','495','3982','68','73','0','1039.98','-4925.08','9.42985','0.387488','3966:0 3968:0 '), +('3968','hf190','571','495','3982','68','73','0','945.95','-4873.37','4.59329','0.540618','3967:0 3969:0 '), +('3969','hf191','571','495','3982','68','73','0','1068.1','-4819.17','26.6749','3.6728','3968:0 3970:0 '), +('3970','hf192','571','495','3983','68','73','0','1158.2','-4813.94','28.9399','0.405537','3969:0 3971:0 '), +('3971','hf193','571','495','3983','68','73','0','1104.87','-4804.91','-6.8197','0.572031','3970:0 3972:0 '), +('3972','hf194','571','495','3983','1','60','0','980.459','-4819.09','-72.4413','0.31677','3971:0 3973:0 '), +('3973','hf195','571','495','3983','68','73','0','841.596','-4863.98','-72.4161','5.00561','3972:0 3974:0 3975:0 '), +('3974','hf196','571','495','3983','68','73','0','875.874','-4966.04','-72.4404','1.87186','3973:0 '), +('3975','hf197','571','495','495','68','73','0','795.673','-4726.51','-96.2356','1.47131','3973:0 3976:0 '), +('3976','hf198','571','495','3983','68','73','0','835.937','-4866.89','-115.736','2.10747','3975:0 3977:0 '), +('3977','hf199','571','495','3983','68','73','0','945.547','-4827.44','-116.158','0.281421','3976:0 '), +('3978','hf200','571','495','3981','68','73','3','554.553','-4986.67','10.1867','0.251579','3964:0 3965:0 '), +('3979','hf201','571','495','495','68','73','0','628.213','-4255.8','210.916','3.49527','3949:0 3956:0 3980:0 '), +('3980','hf202','571','495','3994','68','73','0','765.99','-4299.85','185.031','3.00833','3979:0 3981:0 '), +('3981','hf203','571','495','3994','68','73','0','798.758','-4362.47','161.294','4.9679','3980:0 3982:0 '), +('3982','hf204','571','495','3994','68','73','0','902.1','-4478.73','144.331','5.55301','3981:0 3983:0 3984:0 '), +('3983','hf205','571','495','3994','68','73','0','1004.65','-4395.88','150.922','4.0804','3982:0 3984:0 '), +('3984','hf206','571','495','3994','68','73','0','909.858','-4395.54','139.649','4.34665','3983:0 3982:0 '), +('3985','hf207','571','495','4048','68','73','0','1203.65','-3825.84','142.684','1.05976','3944:0 3945:0 3958:0 3986:0 '), +('3986','hf208','571','495','495','68','73','0','1408.56','-3737.55','135.962','1.67629','3985:0 3987:0 '), +('3987','hf209','571','495','495','68','73','0','1558.28','-3740.43','144.691','0.00336027','3986:0 3988:0 '), +('3988','hf210','571','495','495','68','73','1','1706.02','-3730.74','148.901','3.19209','3987:0 3989:0 4027:0 '), +('3989','hf211','571','495','495','68','73','0','1803.26','-3895.57','170.472','0.273537','3988:0 3990:0 '), +('3990','hf212','571','495','495','68','73','0','1949.63','-3842.81','173.777','1.70846','3989:0 3991:0 '), +('3991','hf213','571','495','4068','68','73','0','2008.1','-3689.61','173.671','1.9378','3990:0 3992:0 '), +('3992','hf214','571','495','4068','68','73','0','1967.39','-3604.53','158.109','5.18071','3991:0 3993:0 4031:0 '), +('3993','hf215','571','495','495','68','73','0','2096.74','-3545.48','153.897','6.17032','3992:0 3994:0 '), +('3994','hf216','571','495','495','68','73','0','2318.6','-3583.13','165.228','0.334813','3993:0 3995:0 '), +('3995','hf217','571','495','4006','68','73','0','2535.68','-3475.65','183.975','0.49582','3994:0 3996:0 3999:0 '), +('3996','hf218','571','495','4006','68','73','0','2624.04','-3562.9','205.26','5.49331','3995:0 3997:0 3999:0 '), +('3997','hf219','571','495','4006','68','73','0','2727.06','-3698.08','221.722','5.46582','3996:0 3998:0 '), +('3998','hf220','571','495','4065','68','73','0','2838.83','-3844.63','244.563','5.37','3997:0 '), +('3999','hf221','571','495','4006','68','73','0','2613.9','-3384.95','183','4.543','3995:0 3996:0 4000:0 4001:0 '), +('4000','hf222','571','495','4006','68','73','0','2724.44','-3424.92','224.685','0.694536','3999:0 '), +('4001','hf223','571','495','4054','68','73','0','2578.54','-3191.48','141.337','1.90327','3999:0 4002:0 '), +('4002','hf224','571','495','495','68','73','0','2515.29','-2957.64','111.213','1.93861','4001:0 4003:0 4011:0 4318:0 '), +('4003','hf225','571','495','4054','68','73','0','2301.09','-3135.29','143.7','3.73482','4002:0 4004:0 4010:0 '), +('4004','hf226','571','495','4054','68','73','0','2243.04','-3261.92','151.734','2.70201','4003:0 4005:0 '), +('4005','hf227','571','495','3999','68','73','0','2145.39','-3331.7','144.475','1.2019','4004:0 4006:0 '), +('4006','hf228','571','495','3999','68','73','0','2086.44','-3330','104.792','3.06722','4005:0 4007:0 '), +('4007','hf229','571','495','3999','68','73','0','1983.23','-3286.99','111.741','1.78624','4006:0 4008:0 '), +('4008','hf230','571','495','3999','68','73','0','2045.23','-3172.46','139.784','4.15736','4007:0 4009:0 '), +('4009','hf231','571','495','4062','68','73','5','2094.18','-2981.02','144.898','4.45895','4008:0 4010:0 '), +('4010','hf232','571','495','495','68','73','0','2264.88','-3015.48','135.084','5.19722','4009:0 4003:0 '), +('4011','hf233','571','495','495','68','73','0','2606.41','-2825.71','69.3506','1.32522','4002:0 4012:0 4318:0 '), +('4012','hf234','571','495','4070','68','73','0','2558.46','-2678.02','3.25504','5.60171','4011:0 4013:0 4322:0 '), +('4013','hf235','571','495','4070','68','73','0','2430.12','-2758.92','5.62901','3.37275','4012:0 4014:0 '), +('4014','hf236','571','495','4070','68','73','0','2281.27','-2774.11','4.91082','0.329327','4013:0 4015:0 '), +('4015','hf237','571','495','4070','68','73','0','2153.11','-2770.66','4.86218','3.08765','4014:0 4016:0 '), +('4016','hf238','571','495','4070','68','73','0','1967.15','-2762.53','3.5219','3.17561','4015:0 4017:0 '), +('4017','hf239','571','495','4070','68','73','0','1835.91','-2814.57','3.48005','3.54396','4016:0 4018:0 '), +('4018','hf240','571','495','4070','68','73','0','1677.38','-2975.29','3.95501','4.155','4017:0 4019:0 '), +('4019','hf241','571','495','4071','68','73','0','1576.78','-3123.46','2.99581','4.44402','4018:0 4020:0 4029:0 '), +('4020','hf242','571','495','4071','68','73','0','1609.87','-3258.66','3.34169','4.86186','4019:0 4021:0 '), +('4021','hf243','571','495','4071','68','73','0','1629.47','-3440.1','29.9326','3.35783','4020:0 4022:0 4030:0 '), +('4022','hf244','571','495','4071','68','73','0','1490.07','-3428.63','88.8301','0.0332379','4021:0 4023:0 '), +('4023','hf245','571','495','4071','68','73','0','1554.39','-3419.81','97.6795','4.70636','4022:0 4024:0 4026:0 4028:0 '), +('4024','hf246','571','495','4071','68','73','0','1638.14','-3283.78','76.0526','5.23256','4023:0 4025:0 '), +('4025','hf247','571','495','4071','68','73','0','1772.14','-3327.31','81.7587','4.30737','4024:0 '), +('4026','hf248','571','495','4071','68','73','0','1641.48','-3512.7','92.7675','1.7077','4023:0 4027:0 '), +('4027','hf249','571','495','495','68','73','0','1670.45','-3682.65','149.579','1.75245','4026:0 3988:0 4028:0 '), +('4028','hf250','571','495','4071','68','73','0','1565.95','-3550.52','112.177','1.48387','4027:0 4023:0 '), +('4029','hf251','571','495','495','68','73','0','1489.29','-3099.07','39.9696','5.34802','4019:0 4030:0 '), +('4030','hf252','571','495','4071','68','73','0','1511.33','-3269.6','74.5615','0.545315','4029:0 4021:0 '), +('4031','hf253','571','495','495','68','73','0','2104.47','-3840.68','209.54','2.27867','3992:0 4032:0 '), +('4032','hf254','571','495','4002','68','73','0','2068.75','-4073.32','236.821','4.87048','4031:0 4033:0 4039:0 '), +('4033','hf255','571','495','4002','68','73','0','1987.13','-4141.88','216.964','3.23293','4032:0 4034:0 '), +('4034','hf256','571','495','4002','68','73','0','1838.02','-4120.86','223.042','2.70435','4033:0 4035:0 4038:0 '), +('4035','hf257','571','495','4002','68','73','0','1672.75','-4022.82','249.031','3.18894','4034:0 4036:0 '), +('4036','hf258','571','495','4002','68','73','0','1693.59','-4091.47','270.671','4.86183','4035:0 4037:0 '), +('4037','hf259','571','495','4002','68','73','0','1644.9','-4213.64','258.417','6.27555','4036:0 4038:0 '), +('4038','hf260','571','495','4002','68','73','0','1773.12','-4215.36','238.947','0.851593','4037:0 4034:0 '), +('4039','hf261','571','495','495','68','73','0','2130.43','-4316.44','219.989','1.40137','4032:0 4040:0 '), +('4040','hf262','571','495','495','68','73','0','2183.63','-4535.73','216.514','4.86106','4039:0 4041:0 '), +('4041','hf263','571','495','495','68','73','0','2377.58','-4396.75','228.263','0.804472','4040:0 4042:0 '), +('4042','hf264','571','495','495','68','73','0','2551.09','-4320.92','279.902','6.10198','4041:0 4043:0 '), +('4043','hf265','571','495','4018','68','73','5','2613.73','-4366.35','276.555','2.42318','4042:0 4044:0 '), +('4044','hf266','571','495','4018','68','73','4','2716.59','-4368.81','272.672','5.25063','4043:0 4045:0 '), +('4045','hf267','571','495','495','68','73','0','2797.23','-4621.23','277.95','5.09748','4044:0 4046:0 4352:0 '), +('4046','hf268','571','495','495','68','73','0','2607.42','-4744.93','256.769','3.77173','4045:0 4047:0 '), +('4047','hf269','571','495','495','68','73','0','2455.43','-4885.82','260.773','3.54789','4046:0 4048:0 4051:0 '), +('4048','hf270','571','495','495','68','73','3','2431.15','-4962.71','273.723','1.34484','4047:0 4049:0 '), +('4049','hf271','571','495','4003','68','73','2','2510.75','-4994.59','283.35','2.79784','4048:0 4050:0 '), +('4050','hf272','571','495','4003','68','73','2','2456.13','-5113.98','277.313','4.08197','4049:0 '), +('4051','hf273','571','495','495','68','73','0','2232.27','-4879.42','237.068','0.178543','4047:0 4052:0 '), +('4052','hf274','571','495','495','68','73','0','2035.15','-4874.68','210.788','3.93982','4051:0 4053:0 4056:0 '), +('4053','hf275','571','495','495','68','73','0','1888.77','-4884.63','168.445','3.11672','4052:0 4054:0 '), +('4054','hf276','571','495','4059','68','73','0','1712.12','-4879.28','126.121','3.13635','4053:0 4055:0 '), +('4055','hf277','571','495','4059','68','73','0','1563','-4951.92','143.133','4.04662','4054:0 '), +('4056','hf278','571','495','495','68','73','0','1915.43','-5076.01','179.861','4.59721','4052:0 4057:0 '), +('4057','hf279','571','495','495','68','73','0','1824.95','-5278.57','182.601','4.39143','4056:0 4058:0 4064:0 '), +('4058','hf280','571','495','4061','68','73','0','1965.2','-5427.02','202.866','6.04468','4057:0 4059:0 '), +('4059','hf281','571','495','4061','68','73','0','2137.38','-5461.32','239.114','6.25123','4058:0 4060:0 '), +('4060','hf282','571','495','4061','68','73','0','2177.05','-5614.25','226.937','4.76369','4059:0 4061:0 4397:0 '), +('4061','hf283','571','495','4061','68','73','0','2173.72','-5813.08','225.029','3.53925','4060:0 4062:0 4397:0 '), +('4062','hf284','571','495','4061','68','73','0','1978.82','-5763.87','223.278','2.62034','4061:0 4063:0 '), +('4063','hf285','571','495','4061','68','73','0','1834.38','-5629.13','215.972','1.48308','4062:0 4064:0 '), +('4064','hf286','571','495','495','68','73','1','1667.21','-5453.46','197.385','2.72951','4063:0 4057:0 4065:0 4068:0 '), +('4065','hf287','571','495','4001','68','73','0','1532.35','-5419.75','189.418','2.63761','4064:0 4066:0 '), +('4066','hf288','571','495','4001','68','73','0','1486.1','-5319.6','195.08','1.17912','4065:0 4067:0 '), +('4067','hf289','571','495','4001','68','73','0','1504.6','-5264.18','206.615','0.745568','4066:0 '), +('4068','hf290','571','495','495','68','73','0','1492.09','-5524.43','200.456','3.74892','4064:0 4069:0 4072:0 '), +('4069','hf291','571','495','4403','68','73','0','1532.33','-5613.29','226.19','4.85477','4068:0 4070:0 '), +('4070','hf292','571','495','4402','68','73','4','1541.33','-5689.58','248.356','1.64643','4069:0 4071:0 '), +('4071','hf293','571','495','495','68','73','5','1631.27','-5779.62','258.389','2.51272','4070:0 '), +('4072','hf294','571','495','495','68','73','0','1394.07','-5574.59','205.361','0.236628','4068:0 4073:0 '), +('4073','hf295','571','495','495','68','73','0','1234.61','-5679.7','225.93','3.5408','4072:0 4074:0 4104:0 '), +('4074','hf296','571','495','3984','68','73','0','1037.41','-5697.06','228.706','3.34052','4073:0 4075:0 4108:0 '), +('4075','hf297','571','495','495','68','73','0','877.228','-5789.41','249.962','3.77328','4074:0 4076:0 '), +('4076','hf298','571','495','495','68','73','0','739.705','-5863.89','287.056','4.11336','4075:0 4077:0 '), +('4077','hf299','571','495','495','68','73','3','483.188','-5922.44','308.68','1.33265','4076:0 4078:0 '), +('4078','hf300','571','495','495','68','73','0','513.926','-5588.33','281.883','2.6321','4077:0 4079:0 '), +('4079','hf301','571','495','495','68','73','0','342.715','-5438.85','298.648','2.30775','4078:0 4080:0 '), +('4080','hf302','571','495','495','68','73','0','221.35','-5242.73','299.653','1.84436','4079:0 4081:0 '), +('4081','hf303','571','495','4057','68','73','0','161.173','-4925.94','298.444','1.79331','4080:0 4082:0 4083:0 4085:0 '), +('4082','hf304','571','495','495','68','73','0','285.533','-4807.39','248.989','1.63624','4081:0 3951:0 3954:0 '), +('4083','hf305','571','495','4057','68','73','0','-28.8267','-4914.36','302.775','3.12851','4081:0 4084:0 '), +('4084','hf306','571','495','4057','68','73','0','-143.121','-5118.6','323.883','1.16107','4083:0 '), +('4085','hf307','571','495','495','68','73','0','115.019','-4716.56','289.321','1.68335','4081:0 3952:0 4086:0 '), +('4086','hf308','571','495','495','68','73','0','156.917','-4507.79','256.612','1.23411','4085:0 3950:0 '), +('4087','hf309','571','495','3988','68','73','1','778.297','-2898.22','7.01161','4.8391','4088:0 '), +('4088','hf310','571','495','3987','68','73','0','741.908','-3049.69','25.2093','3.79609','4087:0 4089:0 4092:0 4093:0 4094:0 4095:0 '), +('4089','hf311','571','495','3987','68','73','0','590.131','-3156.57','40.1902','3.94689','4088:0 4090:0 4099:0 '), +('4090','hf312','571','495','3987','68','73','0','471.48','-3318.84','48.7074','3.51255','4089:0 4091:0 '), +('4091','hf313','571','495','3987','68','73','0','386.081','-3089.98','71.2014','1.81688','4090:0 4092:0 '), +('4092','hf314','571','495','3987','68','73','0','508.887','-3028.15','24.4901','2.16561','4091:0 4088:0 '), +('4093','hf315','571','495','3987','68','73','0','986.117','-3034.77','57.92','0.46364','4088:0 4094:0 '), +('4094','hf316','571','495','3987','68','73','0','749.692','-3240.83','24.9581','2.15066','4088:0 4093:0 4097:0 '), +('4095','hf317','571','495','3987','68','73','0','675.991','-3161.57','61.349','1.00634','4088:0 4096:0 '), +('4096','hf318','571','495','3987','68','73','0','677.454','-3393.96','67.7866','4.88621','4095:0 '), +('4097','hf319','571','495','3987','68','73','0','566.586','-3287.72','26.0315','4.65294','4094:0 4098:0 '), +('4098','hf320','571','495','4226','68','73','0','551.1','-3494.05','3.24654','4.58853','4097:0 '), +('4099','hf321','571','495','3987','68','73','0','370.473','-3216.41','23.421','3.95629','4089:0 4100:0 '), +('4100','hf322','571','495','4474','68','73','0','223.035','-3291.75','0.0638989','3.59893','4099:0 4101:0 '), +('4101','hf323','571','495','4474','68','73','0','114.305','-3330.69','0.616794','0.370948','4100:0 4102:0 '), +('4102','hf324','571','495','3989','68','73','0','-16.534','-3453.36','32.7157','1.36763','4101:0 4103:0 '), +('4103','hf325','571','495','3990','68','73','0','-130.721','-3573.93','3.18551','1.92133','4102:0 '), +('4104','hf326','571','495','495','68','73','0','1337.07','-5914.2','292.65','4.10948','4073:0 4105:0 '), +('4105','hf327','571','495','4005','68','73','0','1323.93','-6111.92','266.982','3.40577','4104:0 4106:0 '), +('4106','hf328','571','495','4005','68','73','0','1181.44','-6169.63','232.169','2.26694','4105:0 4107:0 '), +('4107','hf329','571','495','4005','68','73','0','1118.44','-6156.85','260.616','1.64883','4106:0 4108:0 '), +('4108','hf330','571','495','495','68','73','0','1131.33','-5884.77','271.705','2.04153','4107:0 4074:0 '), +('4109','db331','571','65','4478','70','74','0','3509.64','2944.69','24.9497','4.10306','3877:0 '), +('4110','db332','571','65','65','70','74','0','3526.72','2777.23','49.7018','4.66854','4111:0 4267:0 4274:0 '), +('4111','db333','571','65','4157','70','74','0','3369.46','2655.03','38.9276','4.90416','4110:0 4112:0 '), +('4112','db334','571','65','4157','70','74','0','3440.83','2477.55','45.9125','4.61748','4111:0 4113:0 '), +('4113','db335','571','65','4157','70','74','0','3395.92','2379.05','36.7537','2.28876','4112:0 4114:0 '), +('4114','db336','571','65','4157','70','74','0','3343.5','2223.35','29.9841','5.39185','4113:0 4116:0 4115:0 '), +('4115','db337','571','65','65','70','74','3','3489.08','1998.02','64.8654','1.46526','4114:0 4116:0 4260:0 '), +('4116','db338','571','65','4160','70','74','0','3228.78','1905.36','132.824','3.97029','4117:0 4114:0 4115:0 '), +('4117','db339','571','65','4160','70','74','0','2981.37','1896.75','127.972','2.94456','4116:0 4118:0 '), +('4118','db340','571','65','4160','70','74','0','2800.15','1812.41','147.093','5.30074','4117:0 4119:0 '), +('4119','db341','571','65','4160','70','74','0','2944.01','1641.57','152.165','4.64493','4118:0 4120:0 '), +('4120','db342','571','65','4153','70','74','0','2869.62','1469.27','157.603','5.17349','4119:0 4121:0 '), +('4121','db343','571','65','4153','70','74','0','2927.17','1282.72','151.277','5.62666','4120:0 4122:0 '), +('4122','db344','571','65','4153','70','74','0','3121.27','1266.58','158.478','3.9082','4121:0 4123:0 4128:0 '), +('4123','db345','571','65','4154','70','74','0','3098.59','1055.28','114.785','4.75643','4122:0 4124:0 4127:0 '), +('4124','db346','571','65','4152','70','74','0','3015.6','886.551','57.0023','3.13067','4123:0 4125:0 4127:0 '), +('4125','db347','571','65','4152','70','74','0','2864.53','878.189','19.1406','2.82436','4124:0 4126:0 '), +('4126','db348','571','65','4152','70','74','1','2657.93','892.145','4.37495','3.30424','4125:0 '), +('4127','db349','571','65','4154','70','74','0','3216.12','884.158','119.499','2.58873','4123:0 4124:0 4128:0 '), +('4128','db350','571','65','4154','70','74','0','3342.09','1163.09','140.364','0.88363','4122:0 4127:0 4129:0 '), +('4129','db351','571','65','4154','70','74','0','3499.57','1159.74','129.579','0.224683','4128:0 4130:0 4254:0 '), +('4130','db352','571','65','4254','70','74','0','3512.34','930.529','67.6824','4.436','4129:0 4131:0 '), +('4131','db353','571','65','4254','70','74','0','3439.05','725.86','74.9159','3.52022','4130:0 4132:0 '), +('4132','db354','571','65','4254','70','74','0','3383.4','548.065','79.6602','4.13597','4131:0 4133:0 '), +('4133','db355','571','65','4254','70','74','0','3284.71','349.551','72.0866','4.38102','4132:0 4134:0 '), +('4134','db356','571','65','4254','70','74','0','3258.62','161.424','74.762','4.63863','4133:0 4135:0 '), +('4135','db357','571','65','4254','70','74','0','3199.28','-65.6434','75.8214','5.73582','4134:0 4136:0 '), +('4136','db358','571','65','4254','70','74','0','3396.24','-138.385','72.6226','6.16308','4135:0 4137:0 4222:0 '), +('4137','db359','571','65','4176','70','74','0','3614.83','-219.548','59.6726','5.91567','4136:0 4138:0 '), +('4138','db360','571','65','4254','70','74','0','3790.53','-250.696','72.8378','0.182258','4137:0 4221:0 '), +('4139','db361','571','65','4254','70','74','0','3996.13','-105.238','55.0057','0.538831','4140:0 '), +('4140','db362','571','65','4254','70','74','0','4209.42','-149.468','68.6085','5.25672','4139:0 4141:0 '), +('4141','db363','571','65','4254','70','74','0','4346.6','-340.238','86.1907','5.65727','4140:0 4185:0 '), +('4142','db364','571','65','4254','70','74','0','4531.54','-157.776','94.5998','0.726543','4143:0 '), +('4143','db365','571','65','4253','70','74','0','4529.69','-5.32293','72.4325','1.1538','4142:0 4144:0 '), +('4144','db366','571','65','4187','70','74','0','4719.33','26.5065','64.7658','0.68649','4143:0 4145:0 '), +('4145','db367','571','65','4187','70','74','0','4857.73','152.013','95.1974','0.64722','4144:0 4146:0 4148:0 '), +('4146','db368','571','65','4187','70','74','0','5023.32','291.159','160.107','0.61659','4145:0 4147:0 '), +('4147','db369','571','65','4187','70','74','0','5185.63','203.224','194.36','2.54081','4146:0 4631:0 '), +('4148','db370','571','65','4187','70','74','0','4916.98','373.751','114.702','2.28006','4145:0 4149:0 '), +('4149','db371','571','65','4396','70','74','0','4715.84','582.426','121.086','2.36646','4148:0 4150:0 4182:0 '), +('4150','db372','571','65','4254','70','74','0','4669.25','828.097','110.706','2.44265','4151:0 4179:0 4149:0 '), +('4151','db373','571','65','4254','70','74','0','4441.38','887.155','84.3941','2.82121','4150:0 4152:0 4153:0 '), +('4152','db374','571','65','4254','70','74','0','4443.49','662.985','67.5233','2.64843','4151:0 4153:0 '), +('4153','db375','571','65','4254','70','74','0','4350.16','761.448','67.5378','5.48843','4152:0 4151:0 4154:0 '), +('4154','db376','571','65','4173','70','74','0','4171.02','645.93','64.3628','5.72325','4153:0 4155:0 '), +('4155','db377','571','65','4173','70','74','0','4234.66','525','11.3008','1.28578','4154:0 4156:0 '), +('4156','db378','571','65','4173','70','74','0','4325.75','527.107','-8.31338','5.11853','4155:0 4157:0 '), +('4157','db379','571','65','4173','70','74','0','4340.91','393.399','-6.35666','0.319745','4156:0 4158:0 '), +('4158','db380','571','65','4173','70','74','0','4445.71','448.569','48.7618','3.60429','4157:0 4159:0 '), +('4159','db381','571','65','4254','70','74','0','4396.23','306.166','44.9454','3.30034','4158:0 4160:0 '), +('4160','db382','571','65','4254','70','74','0','4238.24','300.479','69.4901','3.54617','4159:0 4161:0 '), +('4161','db383','571','65','4254','70','74','0','4069.57','365.886','52.151','2.76941','4160:0 4162:0 '), +('4162','db384','571','65','4254','70','74','0','3905.43','429.963','42.0653','2.76941','4161:0 4163:0 '), +('4163','db385','571','65','4254','70','74','0','3784.72','555.764','65.4753','2.52593','4162:0 4164:0 '), +('4164','db386','571','65','4254','70','74','0','3856.57','647.11','62.7715','2.10102','4163:0 4165:0 '), +('4165','db387','571','65','4254','70','74','0','3750.11','814.498','76.6706','1.69811','4164:0 4166:0 4169:0 '), +('4166','db388','571','65','4168','70','74','0','3669.68','935.971','55.9559','1.74995','4165:0 4167:0 '), +('4167','db389','571','65','4168','70','74','0','3656.82','1058.49','58.5011','5.83088','4166:0 4168:0 '), +('4168','db390','571','65','4168','70','74','0','3822.88','1076.68','57.7067','4.99837','4167:0 4169:0 '), +('4169','db391','571','65','4168','70','74','0','3851.01','940.099','56.0659','4.66535','4168:0 4165:0 4170:0 '), +('4170','db392','571','65','4254','70','74','0','4005.33','1003','53.2598','6.17489','4169:0 4171:0 '), +('4171','db393','571','65','4254','70','74','0','4142.41','981.525','78.4202','0.463487','4170:0 4172:0 '), +('4172','db394','571','65','4254','70','74','0','4305.2','1039.06','63.1545','6.25972','4171:0 4173:0 '), +('4173','db395','571','65','65','70','74','0','4276.99','1183.3','141.02','6.17961','4172:0 4174:0 '), +('4174','db396','571','65','65','70','74','0','4489.67','1187.53','136.792','1.18448','4173:0 4175:0 4179:0 '), +('4175','db397','571','65','4169','70','74','2','4570.48','1377.12','189.542','1.18173','4174:0 4176:0 '), +('4176','db398','571','65','4169','70','74','3','4589.48','1435.55','189.222','0.103772','4175:0 4177:0 4178:0 '), +('4177','db399','571','65','4169','70','74','2','4621.53','1507.35','204.751','1.03447','4176:0 '), +('4178','db400','571','65','4171','70','74','2','4711.55','1446.21','175.428','5.44841','4176:0 '), +('4179','db401','571','65','65','70','74','0','4690.88','1098.68','123.008','2.0861','4180:0 4174:0 4150:0 '), +('4180','db402','571','65','4170','70','74','4','4841.36','1140.74','171.495','1.04781','4181:0 4179:0 '), +('4181','db403','571','65','4170','70','74','5','4947.98','1224.66','225.705','1.29599','4180:0 '), +('4182','db404','571','65','4230','70','74','0','4857.56','636.316','157.933','3.35764','4183:0 4149:0 '), +('4183','db405','571','65','4230','70','74','0','4919.39','858.686','179.409','4.72424','4182:0 4184:0 '), +('4184','db406','571','65','4230','70','74','0','5115.8','1070.31','223.911','0.494084','4183:0 '), +('4185','db407','571','65','65','70','74','0','4361.47','-666.576','126.529','4.95513','4141:0 4186:0 4190:0 '), +('4186','db408','571','65','4193','70','74','0','4506.23','-611.765','138.674','0.191691','4185:0 4187:0 '), +('4187','db409','571','65','4193','70','74','0','4635.8','-532.061','166.561','4.05115','4186:0 4188:0 '), +('4188','db410','571','65','4193','70','74','0','4701.77','-386.751','178.452','4.03308','4187:0 4189:0 '), +('4189','db411','571','65','65','70','74','0','4744.71','-265.341','170.449','0.542767','4188:0 '), +('4190','db412','571','65','65','70','74','0','4381.74','-907.37','164.196','4.73833','4185:0 4191:0 '), +('4191','db413','571','65','65','70','74','0','4453.65','-1048.77','166.994','5.44283','4190:0 4192:0 4193:0 '), +('4192','db414','571','65','4191','70','74','1','4598.46','-1084.15','165.481','5.41142','4191:0 4197:0 '), +('4193','db415','571','65','65','70','74','0','4459.56','-1263.85','163.284','4.29694','4191:0 4194:0 4198:0 4199:0 '), +('4194','db416','571','65','4194','70','74','0','4561.66','-1361.99','156.723','5.94234','4193:0 4195:0 '), +('4195','db417','571','65','4194','70','74','0','4677.53','-1359.08','162.34','2.13944','4194:0 4196:0 4451:0 '), +('4196','db418','571','65','4194','70','74','0','4810.96','-1263.2','168.051','2.83451','4195:0 4197:0 '), +('4197','db419','571','65','65','70','74','0','4682.42','-1190.97','164.492','2.29651','4196:0 4192:0 '), +('4198','db420','571','65','65','70','74','0','4426.84','-1508.64','159.241','4.50584','4193:0 4276:0 '), +('4199','db421','571','65','65','70','74','0','4306.11','-1358.64','159.518','2.56747','4193:0 4200:0 '), +('4200','db422','571','65','4189','70','74','0','4174.24','-1186.41','145.827','2.02005','4199:0 4201:0 '), +('4201','db423','571','65','4189','70','74','0','4007.83','-1254.48','133.053','2.98923','4200:0 4202:0 '), +('4202','db424','571','65','65','70','74','0','3870.4','-1277.63','147.246','3.74243','4201:0 4203:0 4211:0 '), +('4203','db425','571','65','4188','70','74','0','3760.87','-1379.1','135.314','4.06287','4202:0 4204:0 '), +('4204','db426','571','65','4188','70','74','0','3749.27','-1504.65','141.102','2.59496','4203:0 4205:0 '), +('4205','db427','571','65','4188','70','74','0','3628.59','-1546.14','118.335','2.62324','4204:0 4206:0 '), +('4206','db428','571','65','4188','70','74','0','3581.08','-1357.67','107.693','1.20402','4205:0 4207:0 '), +('4207','db429','571','65','4188','70','74','0','3627.72','-1237','112.462','1.20323','4206:0 4208:0 '), +('4208','db430','571','65','4188','70','74','0','3662.83','-1064.27','125.377','1.29591','4207:0 4209:0 '), +('4209','db431','571','65','4188','70','74','0','3755.72','-1010.08','121.509','5.72713','4208:0 4210:0 4212:0 '), +('4210','db432','571','65','4188','70','74','0','3850.53','-1132.6','122.334','6.27534','4209:0 4211:0 '), +('4211','db433','571','65','65','70','74','0','3942.27','-1148.57','140.166','4.29771','4210:0 4202:0 '), +('4212','db434','571','65','4177','70','74','0','3746.15','-891.172','162.614','2.06089','4209:0 4213:0 4216:0 '), +('4213','db435','571','65','4177','70','74','0','3650.12','-879.646','162.72','3.17459','4212:0 4214:0 '), +('4214','db436','571','65','4177','70','74','2','3590.79','-835.238','172.37','1.65563','4213:0 4215:0 4240:0 '), +('4215','db437','571','65','4243','70','74','0','3599.65','-781.467','147.621','0.0926948','4214:0 '), +('4216','db438','571','65','4177','70','74','3','3768.81','-779.501','195.937','4.02283','4212:0 4217:0 '), +('4217','db439','571','65','4177','70','74','2','3925.37','-698.927','241.206','0.865546','4216:0 4218:0 '), +('4218','db440','571','65','4175','70','74','0','3986.58','-553.999','216.076','2.58635','4217:0 4219:0 '), +('4219','db441','571','65','4254','70','74','0','3809.46','-473.161','169.556','2.73087','4218:0 4220:0 '), +('4220','db442','571','65','65','70','74','0','3724.29','-439.939','157.058','3.21388','4219:0 4221:0 '), +('4221','db443','571','65','4254','70','74','0','3784.04','-368.672','97.0346','1.91644','4220:0 4138:0 '), +('4222','db444','571','65','4254','70','74','0','3263.75','-275.159','91.4928','3.40319','4136:0 4223:0 '), +('4223','db445','571','65','65','70','74','0','3244.55','-472.809','140.618','1.83632','4222:0 4224:0 4234:0 '), +('4224','db446','571','65','65','70','74','0','3148.48','-604.869','113.192','1.5842','4223:0 4225:0 4237:0 '), +('4225','db447','571','65','4180','70','74','0','2993.93','-429.733','123.402','2.59501','4224:0 4226:0 '), +('4226','db448','571','65','4180','70','74','0','2867.29','-384.503','112.462','2.7623','4225:0 4227:0 4233:0 '), +('4227','db449','571','65','4180','70','74','0','2773.28','-465.739','116.162','4.48387','4226:0 4228:0 '), +('4228','db450','571','65','4180','70','74','0','2687.72','-431.283','71.3279','2.92957','4227:0 4229:0 '), +('4229','db451','571','65','4181','70','74','0','2536.4','-415.378','2.90773','1.58496','4228:0 4230:0 4253:0 '), +('4230','db452','571','65','4181','70','74','0','2612.51','-244.345','1.72779','6.26044','4229:0 4231:0 '), +('4231','db453','571','65','4180','70','74','0','2656.87','-171.598','62.2241','1.32029','4230:0 4232:0 '), +('4232','db454','571','65','4180','70','74','0','2737.23','-100.56','114.159','4.23648','4231:0 4233:0 '), +('4233','db455','571','65','4180','70','74','0','2792.04','-264.842','132.149','5.26534','4232:0 4226:0 '), +('4234','db456','571','65','4186','70','74','5','3231.05','-661.255','166.734','1.42239','4223:0 4235:0 '), +('4235','db457','571','65','4186','70','74','4','3272.19','-751.348','168.143','4.40298','4234:0 4236:0 '), +('4236','db458','571','65','65','70','74','0','3325.52','-1005.39','123.604','2.0507','4235:0 4237:0 4238:0 4249:0 '), +('4237','db459','571','65','65','70','74','0','3207.68','-802.517','112.975','2.23134','4236:0 4224:0 '), +('4238','db460','571','65','65','70','74','0','3400.63','-1169.43','112.122','5.78762','4236:0 4239:0 4241:0 '), +('4239','db461','571','65','65','70','74','0','3494.11','-1049.52','124.775','0.832553','4238:0 4240:0 '), +('4240','db462','571','65','4177','70','74','2','3600.71','-914.48','156.552','1.53549','4239:0 4214:0 '), +('4241','db463','571','65','65','70','74','0','3235.06','-1355.04','70.8979','3.83276','4238:0 4242:0 '), +('4242','db464','571','65','4257','70','74','0','3180.86','-1542.39','40.4347','4.7124','4241:0 4243:0 4277:0 '), +('4243','db465','571','65','4241','70','74','0','3051.36','-1392.98','57.3848','2.94133','4242:0 4244:0 '), +('4244','db466','571','65','4241','70','74','0','2889.8','-1432.73','58.3338','3.04186','4243:0 4245:0 '), +('4245','db467','571','65','4241','70','74','0','2772.55','-1373.06','40.6788','6.18974','4244:0 4246:0 '), +('4246','db468','571','65','4185','70','74','0','2882.77','-1303.94','6.48389','1.04459','4245:0 4247:0 '), +('4247','db469','571','65','4185','70','74','0','2938.22','-1169.74','6.81704','5.99025','4246:0 4248:0 4250:0 '), +('4248','db470','571','65','4185','70','74','0','3085.09','-1193.85','12.4717','5.4876','4247:0 4249:0 '), +('4249','db471','571','65','4185','70','74','0','3180.57','-1059.52','44.9918','0.29769','4248:0 4236:0 '), +('4250','db472','571','65','4185','70','74','0','2929.48','-986.506','4.6099','1.77817','4247:0 4251:0 '), +('4251','db473','571','65','4185','70','74','0','2865.52','-836.167','15.7438','2.35701','4250:0 4252:0 '), +('4252','db474','571','65','4185','70','74','0','2691.63','-707.31','8.95638','2.62875','4251:0 4253:0 '), +('4253','db475','571','65','4181','70','74','0','2662.91','-565.045','4.97671','2.87851','4252:0 4229:0 '), +('4254','db476','571','65','65','70','74','0','3597.9','1391.95','92.2854','0.792481','4129:0 4255:0 '), +('4255','db477','571','65','4165','70','74','4','3768.55','1529.38','86.92','0.415486','4254:0 4256:0 '), +('4256','db478','571','65','4165','70','74','5','3841.3','1533.41','89.7247','1.82685','4255:0 4257:0 '), +('4257','db479','571','65','4165','70','74','4','3890.52','1637.61','96.2566','0.644815','4256:0 4258:0 '), +('4258','db480','571','65','65','70','74','0','4003.51','1741.08','142.414','3.8728','4257:0 4259:0 '), +('4259','db481','571','65','65','70','74','0','3800.66','1801.11','107.946','0.35893','4258:0 4260:0 4273:0 '), +('4260','db482','571','65','65','70','74','0','3642.7','1910.26','75.9736','1.69646','4259:0 4261:0 4115:0 '), +('4261','db483','571','65','65','70','74','0','3615.88','2050.78','74.6297','1.7962','4260:0 4262:0 4265:0 '), +('4262','db484','571','65','4164','70','74','0','3709.48','2144.81','53.4192','3.56571','4261:0 4263:0 4264:0 '), +('4263','db485','571','65','4164','70','74','0','3706.64','2076.92','21.8594','1.15533','4262:0 4264:0 '), +('4264','db486','571','65','4164','70','74','0','3772.34','2169.36','27.3597','4.8828','4263:0 4262:0 '), +('4265','db487','571','65','65','70','74','0','3638.81','2223.64','76.3879','1.34851','4261:0 4266:0 '), +('4266','db488','571','65','65','70','74','0','3608.56','2424.79','84.7521','0.47515','4265:0 4267:0 '), +('4267','db489','571','65','65','70','74','0','3667.57','2606.35','104.934','2.34754','4266:0 4110:0 4268:0 '), +('4268','db490','571','65','65','70','74','0','3821.64','2594.47','138.086','5.63442','4267:0 4269:0 '), +('4269','db491','571','65','4163','70','74','0','3877.75','2397.8','152.255','5.33596','4268:0 4270:0 '), +('4270','db492','571','65','4163','70','74','0','4003.82','2286.07','153.425','6.19125','4269:0 4271:0 '), +('4271','db493','571','65','4163','70','74','0','4084.73','2159.31','153.682','4.48536','4270:0 4272:0 '), +('4272','db494','571','65','4163','70','74','0','3961.11','2124.54','131.41','3.85941','4271:0 4273:0 '), +('4273','db495','571','65','65','70','74','0','3825.91','1954.77','104.913','4.14765','4272:0 4259:0 '), +('4274','db496','571','65','4151','70','74','4','3706.43','2835.58','88.7151','0.419359','4110:0 4275:0 '), +('4275','db497','571','65','4151','70','74','5','3828.74','2884','90.3388','0.171959','4274:0 '), +('4276','gh498','571','394','394','72','76','0','4412.91','-1618.03','162.125','4.64085','4198:0 4289:0 '), +('4277','gh499','571','394','394','72','76','0','3182.34','-1659.02','38.7919','4.89218','4242:0 4278:0 '), +('4278','gh500','571','394','394','72','76','0','3170.1','-1849.63','70.0118','5.13957','4277:0 4279:0 '), +('4279','gh501','571','394','4236','72','76','0','3193.69','-1991.71','85.0008','6.13546','4278:0 4280:0 4306:0 '), +('4280','gh502','571','394','394','72','76','0','3351.7','-1957.7','116.26','3.20042','4279:0 4281:0 4302:0 '), +('4281','gh503','571','394','394','72','76','0','3506.14','-1975.54','165.694','5.76004','4280:0 4282:0 4301:0 '), +('4282','gh504','571','394','394','72','76','0','3654.24','-2134.59','147.89','4.94165','4281:0 4283:0 4331:0 '), +('4283','gh505','571','394','394','72','76','0','3768.13','-2266.2','173.421','6.00979','4282:0 4284:0 4297:0 4331:0 '), +('4284','gh506','571','394','394','72','76','0','3965.13','-2347.84','213.634','6.13467','4283:0 4285:0 4446:0 '), +('4285','gh507','571','394','394','72','76','0','4179.57','-2348.21','225.457','0.204908','4284:0 4286:0 4435:0 '), +('4286','gh508','571','394','394','72','76','0','4360.95','-2316.79','201.282','0.15857','4285:0 4287:0 4296:0 '), +('4287','gh509','571','394','394','72','76','0','4456.7','-2146.34','174.126','1.54558','4286:0 4288:0 '), +('4288','gh510','571','394','394','72','76','0','4445.16','-1972.91','158.144','1.95792','4287:0 4289:0 '), +('4289','gh511','571','394','394','72','76','0','4441.13','-1782.37','162.404','1.80319','4288:0 4276:0 4290:0 '), +('4290','gh512','571','394','394','72','76','0','4395.14','-1892.04','161.168','3.62925','4289:0 4291:0 '), +('4291','gh513','571','394','4209','72','76','0','4294.12','-1906.66','197.755','3.31587','4290:0 4292:0 4293:0 4294:0 '), +('4292','gh514','571','394','4209','72','76','0','4245.43','-2019.66','235.391','1.60371','4291:0 '), +('4293','gh515','571','394','4209','72','76','0','4219.61','-1824.36','202.548','2.41267','4291:0 '), +('4294','gh516','571','394','4209','72','76','0','4141.35','-1993.19','212.874','4.66597','4291:0 4295:0 '), +('4295','gh517','571','394','4209','72','76','0','4200.73','-2152.09','218.191','0.0124693','4294:0 4296:0 '), +('4296','gh518','571','394','394','72','76','0','4337.91','-2212.2','191.313','5.10972','4295:0 4286:0 '), +('4297','gh519','571','394','394','72','76','0','3919.84','-2211.56','210.955','0.375331','4283:0 4298:0 '), +('4298','gh520','571','394','394','72','76','0','3841.19','-2055.11','210.168','1.65396','4297:0 4299:0 '), +('4299','gh521','571','394','394','72','76','0','3817.14','-1865.71','214.679','1.96419','4298:0 4300:0 '), +('4300','gh522','571','394','394','72','76','0','3654.79','-1866.78','173.387','2.8674','4299:0 4301:0 '), +('4301','gh523','571','394','394','72','76','0','3559.72','-1897.37','155.876','3.47686','4300:0 4281:0 '), +('4302','gh524','571','394','394','72','76','0','3387.84','-2133.26','124.553','5.44191','4280:0 4303:0 '), +('4303','gh525','571','394','4206','72','76','4','3345.18','-2217.45','119.375','3.61665','4302:0 4304:0 '), +('4304','gh526','571','394','4206','72','76','5','3256.98','-2231.76','116.09','1.0425','4303:0 4305:0 '), +('4305','gh527','571','394','394','72','76','0','3086.49','-2267.46','94.942','1.73561','4304:0 4306:0 4314:0 '), +('4306','gh528','571','394','394','72','76','0','3084.66','-2084.36','87.6685','1.27615','4305:0 4279:0 4307:0 '), +('4307','gh529','571','394','394','72','76','0','2953.2','-1963.36','51.3225','2.81396','4306:0 4308:0 '), +('4308','gh530','571','394','4242','72','76','0','2828.38','-1917.21','9.14124','2.88465','4307:0 4309:0 4310:0 '), +('4309','gh531','571','394','4242','72','76','0','2698.31','-2040.99','3.89624','0.465604','4308:0 4313:0 '), +('4310','gh532','571','394','4242','72','76','0','2755.77','-1781.45','5.41989','5.167','4308:0 4311:0 '), +('4311','gh533','571','394','4242','72','76','0','2565.61','-1807.96','9.92644','3.51374','4310:0 4312:0 '), +('4312','gh534','571','394','4242','72','76','0','2542.58','-1990.88','8.43344','5.20077','4311:0 4313:0 '), +('4313','gh535','571','394','4242','72','76','0','2617','-2122.41','7.0106','0.525292','4312:0 4309:0 '), +('4314','gh536','571','394','394','72','76','0','3111.6','-2469.39','60.839','4.64079','4305:0 4315:0 '), +('4315','gh537','571','394','394','72','76','0','3050.93','-2690.26','65.2339','4.32742','4314:0 4316:0 '), +('4316','gh538','571','394','394','72','76','0','2996.43','-2911.54','99.7541','3.55929','4315:0 4317:0 4323:0 '), +('4317','gh539','571','394','394','72','76','0','2806.9','-2891.38','64.0493','3.18937','4316:0 4318:0 '), +('4318','gh540','571','394','394','72','76','0','2688.1','-2877.41','68.4325','1.86676','4319:0 4317:0 4011:0 4002:0 '), +('4319','gh541','571','394','4207','72','76','0','2863.15','-2691.55','84.6612','0.692585','4320:0 4318:0 '), +('4320','gh542','571','394','4207','72','76','0','2913.28','-2523.08','77.2727','2.2516','4319:0 4321:0 '), +('4321','gh543','571','394','4207','72','76','0','2759.62','-2433.25','39.5256','4.18681','4320:0 4322:0 '), +('4322','gh544','571','394','394','72','76','0','2655.99','-2570.44','12.8707','0.715349','4321:0 4012:0 '), +('4323','gh545','571','394','4240','72','76','0','3159.97','-2966.67','125.976','5.16462','4316:0 4324:0 '), +('4324','gh546','571','394','394','72','76','0','3229.99','-3097.97','155.647','5.16069','4323:0 4325:0 4333:0 '), +('4325','gh547','571','394','394','72','76','0','3347.86','-2939.95','194.535','0.886556','4324:0 4326:0 '), +('4326','gh548','571','394','4204','72','76','3','3409.22','-2819.78','200.664','5.07272','4325:0 4327:0 '), +('4327','gh549','571','394','394','72','76','0','3563.32','-2837.92','196.693','3.21211','4326:0 4328:0 '), +('4328','gh550','571','394','394','72','76','0','3639.37','-2888.65','220.844','1.05698','4327:0 4329:0 4332:0 4432:0 '), +('4329','gh551','571','394','394','72','76','0','3750.73','-2701.59','175.222','0.808005','4328:0 4330:0 4448:0 '), +('4330','gh552','571','394','394','72','76','0','3701.9','-2499.94','160.171','2.01988','4329:0 4331:0 '), +('4331','gh553','571','394','394','72','76','0','3667.41','-2329.55','156.4','0.83785','4330:0 4283:0 4282:0 '), +('4332','gh554','571','394','394','72','76','0','3567.56','-3072.84','238.573','4.01871','4328:0 4333:0 4334:0 '), +('4333','gh555','571','394','394','72','76','0','3355.89','-3183.89','200.874','2.94663','4332:0 4324:0 '), +('4334','gh556','571','394','394','72','76','0','3543.08','-3277.22','242.517','4.88658','4332:0 4335:0 '), +('4335','gh557','571','394','394','72','76','0','3676.96','-3469.09','242.035','5.54866','4334:0 4336:0 4429:0 '), +('4336','gh558','571','394','394','72','76','0','3696.59','-3688.31','209.189','4.55906','4335:0 4337:0 '), +('4337','gh559','571','394','394','72','76','0','3728.04','-3862.81','183.316','0.164743','4336:0 4338:0 4340:0 4347:0 '), +('4338','gh560','571','394','4215','72','76','0','3590.62','-3901.08','196.64','2.77227','4337:0 4339:0 '), +('4339','gh561','571','394','4215','72','76','0','3525.9','-3755.42','228.317','2.26176','4338:0 '), +('4340','gh562','571','394','395','72','76','0','3841.06','-3743.21','175.461','6.13141','4337:0 4341:0 '), +('4341','gh563','571','394','395','72','76','0','3974.14','-3774.46','151.082','6.17382','4340:0 4342:0 '), +('4342','gh564','571','394','395','72','76','0','4052.02','-3740.14','220.288','4.50485','4341:0 4343:0 '), +('4343','gh565','571','394','395','72','76','0','4135.39','-3710.81','180.991','2.0819','4342:0 4344:0 '), +('4344','gh566','571','394','395','72','76','0','4194.15','-3814.05','181.668','4.59518','4343:0 4345:0 '), +('4345','gh567','571','394','395','72','76','0','4181.7','-3982.87','170.498','4.5402','4344:0 4346:0 4409:0 '), +('4346','gh568','571','394','394','72','76','0','4024.2','-4043.93','176.713','3.04794','4345:0 4347:0 4409:0 '), +('4347','gh569','571','394','394','72','76','0','3787.44','-3987.74','181.678','2.65995','4346:0 4337:0 4348:0 '), +('4348','gh570','571','394','394','72','76','0','3718.3','-4188.38','192.248','4.50249','4347:0 4349:0 4356:0 '), +('4349','gh571','571','394','394','72','76','0','3545.36','-4300.26','227.855','3.59221','4348:0 4350:0 '), +('4350','gh572','571','394','394','72','76','0','3375.07','-4408.72','245.826','4.29907','4349:0 4351:0 4353:0 '), +('4351','gh573','571','394','4480','72','76','0','3261.77','-4590.54','305.739','3.95742','4350:0 4352:0 '), +('4352','gh574','571','394','4480','72','76','0','3089.14','-4641.2','316.308','3.35188','4351:0 4045:0 '), +('4353','gh575','571','394','4218','72','76','0','3502.07','-4548.51','221.433','3.38171','4350:0 4354:0 '), +('4354','gh576','571','394','394','72','76','0','3656.98','-4455.52','184.363','5.84315','4353:0 4355:0 '), +('4355','gh577','571','394','394','72','76','0','3731.98','-4511.53','193.763','0.757702','4354:0 4356:0 '), +('4356','gh578','571','394','394','72','76','5','3851.11','-4369.54','192.918','1.37424','4355:0 4348:0 4357:0 '), +('4357','gh579','571','394','4205','72','76','0','3939.37','-4409.34','238.255','4.98392','4356:0 4358:0 '), +('4358','gh580','571','394','4205','72','76','0','4062.37','-4401.54','260.429','0.235388','4357:0 4359:0 4408:0 4410:0 '), +('4359','gh581','571','394','394','72','76','0','4171.31','-4502.24','219.959','1.1284','4358:0 4360:0 '), +('4360','gh582','571','394','4205','72','76','0','4003.62','-4566.46','195.348','3.13431','4359:0 4361:0 '), +('4361','gh583','571','394','394','72','76','0','4008.13','-4692.66','141.153','5.45436','4360:0 4362:0 '), +('4362','gh584','571','394','4231','72','76','0','4082.28','-4754.21','96.3716','4.76714','4361:0 4363:0 '), +('4363','gh585','571','394','4249','72','76','0','4193.08','-4880.87','42.4572','3.89614','4362:0 4364:0 4374:0 4382:0 '), +('4364','gh586','571','394','4249','72','76','0','4317.27','-4702.62','75.8335','0.62967','4363:0 4365:0 '), +('4365','gh587','571','394','4249','72','76','0','4444.07','-4566.46','104.898','1.65462','4364:0 4366:0 '), +('4366','gh588','571','394','4249','72','76','0','4428.45','-4387.5','151.381','1.08913','4365:0 4367:0 '), +('4367','gh589','571','394','4159','72','76','0','4438.72','-4286.42','161.56','6.09212','4366:0 4368:0 4420:0 '), +('4368','gh590','571','394','4249','72','76','0','4621.51','-4357.67','181.183','5.95781','4367:0 4369:0 '), +('4369','gh591','571','394','4221','72','76','0','4732.05','-4454.6','194.355','5.45673','4368:0 4370:0 '), +('4370','gh592','571','394','4221','72','76','0','4820.92','-4522.91','200.404','5.83294','4369:0 4371:0 '), +('4371','gh593','571','394','4221','72','76','0','4909.98','-4586.39','219.616','4.43492','4370:0 4372:0 '), +('4372','gh594','571','394','4221','72','76','0','4984.98','-4692.79','219.491','5.1245','4371:0 4373:0 '), +('4373','gh595','571','394','4221','72','76','0','5041.25','-4817','219.501','2.53662','4372:0 '), +('4374','gh596','571','394','4212','72','76','0','4339.89','-4870.51','34.7802','0.17335','4363:0 4375:0 '), +('4375','gh597','571','394','4216','72','76','0','4474.49','-4921.22','14.201','5.96566','4374:0 4376:0 4377:0 '), +('4376','gh598','571','394','4212','72','76','0','4518.04','-5017.36','3.25924','5.12764','4375:0 '), +('4377','gh599','571','394','4216','72','76','0','4569.4','-4865.21','47.9919','0.149002','4375:0 4378:0 4379:0 4380:0 '), +('4378','gh600','571','394','4216','72','76','0','4707.48','-4794.55','47.992','5.06167','4377:0 4379:0 '), +('4379','gh601','571','394','4216','72','76','0','4708.25','-4910.12','47.9928','1.36166','4377:0 4378:0 '), +('4380','gh602','571','394','4216','72','76','0','4744.53','-4859.91','26.2962','0.387759','4377:0 4381:0 '), +('4381','gh603','571','394','4216','72','76','0','4816.99','-4789.4','25.4718','0.872349','4380:0 '), +('4382','gh604','571','394','4249','72','76','0','4022.33','-4997.21','41.214','3.78223','4363:0 4383:0 4391:0 '), +('4383','gh605','571','394','4249','72','76','0','3816.35','-5011.96','106.526','3.31727','4382:0 4384:0 4392:0 '), +('4384','gh606','571','394','4220','72','76','0','3646.81','-5000.53','175.852','4.1867','4383:0 4385:0 '), +('4385','gh607','571','394','4220','72','76','0','3524.22','-5120.65','233.744','2.61591','4384:0 4386:0 4389:0 '), +('4386','gh608','571','394','4220','72','76','0','3419.01','-4995.77','285.739','2.5845','4385:0 4387:0 '), +('4387','gh609','571','394','4220','72','76','0','3356.74','-5082.19','325.289','3.94009','4386:0 4388:0 '), +('4388','gh610','571','394','4220','72','76','0','3310.99','-5122.5','340.978','1.36004','4387:0 '), +('4389','gh611','571','394','4220','72','76','0','3433.34','-5250.17','269.339','1.01289','4385:0 4390:0 '), +('4390','gh612','571','394','4220','72','76','0','3407.12','-5388.15','267.856','1.78573','4389:0 '), +('4391','gh613','571','394','4212','72','76','0','4006.9','-5232.47','7.75033','1.18098','4382:0 4402:0 '), +('4392','gh614','571','394','394','72','76','0','3757.96','-5153.08','119.324','4.14823','4383:0 4393:0 '), +('4393','gh615','571','394','394','72','76','0','3598.76','-5357.93','139.869','4.05555','4392:0 4394:0 '), +('4394','gh616','571','394','4213','72','76','0','3443.06','-5490.97','198.431','3.26623','4393:0 4395:0 '), +('4395','gh617','571','394','4213','72','76','0','3274.45','-5572.3','213.641','3.71783','4394:0 4396:0 '), +('4396','gh618','571','394','4213','72','76','0','3100.03','-5709.35','220.326','3.74532','4395:0 4401:0 '), +('4397','hf619','571','495','4061','68','73','0','2323.35','-5813.06','251.394','3.16805','4060:0 4061:0 4398:0 '), +('4398','hf620','571','495','495','68','73','0','2519.29','-5851.8','276.256','4.73492','4397:0 4399:0 '), +('4399','hf621','571','495','495','68','73','0','2703.09','-5953.52','271.511','6.11722','4398:0 4400:0 '), +('4400','hf622','571','495','4252','68','73','0','2849.67','-5840.96','273.44','0.629637','4399:0 4401:0 '), +('4401','hf623','571','495','4252','68','73','0','3003.04','-5779.08','254.433','0.523608','4400:0 4396:0 '), +('4402','gh624','571','394','4212','72','76','0','4153.59','-5224.2','7.42476','6.08423','4391:0 4403:0 '), +('4403','gh625','571','394','4212','72','76','0','4327.94','-5342.7','4.49671','5.82112','4402:0 4404:0 '), +('4404','gh626','571','394','4244','72','76','0','4543.08','-5485.22','2.18391','5.50775','4403:0 4405:0 '), +('4405','gh627','571','394','4244','72','76','0','4695.42','-5635.74','77.5219','4.55742','4404:0 4406:0 '), +('4406','gh628','571','394','4245','72','76','0','4616.66','-5660.81','114.593','4.22877','4405:0 4407:0 '), +('4407','gh629','571','394','4245','72','76','0','4593.57','-5707.41','184.506','1.02041','4406:0 '), +('4408','gh630','571','394','394','72','76','0','4113.47','-4233.27','243.732','1.89258','4358:0 4409:0 '), +('4409','gh631','571','394','394','72','76','1','4125.58','-4095.14','182.823','2.77224','4408:0 4346:0 4345:0 '), +('4410','gh632','571','394','394','72','76','0','4172.63','-4310.2','238.733','2.85314','4358:0 4411:0 '), +('4411','gh633','571','394','394','72','76','0','4326.78','-4119.6','193.825','0.547988','4410:0 4412:0 '), +('4412','gh634','571','394','394','72','76','0','4486.09','-3977.4','186.997','5.61774','4411:0 4413:0 4422:0 '), +('4413','gh635','571','394','394','72','76','0','4625.2','-4092.13','199.11','2.30728','4412:0 4414:0 '), +('4414','gh636','571','394','394','72','76','0','4734.83','-4181.86','231.297','2.31358','4413:0 4415:0 '), +('4415','gh637','571','394','394','72','76','0','4906.69','-4306.46','257.585','0.692519','4414:0 4416:0 4449:0 '), +('4416','gh638','571','394','4214','72','76','0','5016.3','-4437.54','278.117','5.31851','4415:0 4417:0 '), +('4417','gh639','571','394','4214','72','76','0','5101.89','-4569.13','283.736','5.11902','4416:0 4418:0 '), +('4418','gh640','571','394','4214','72','76','0','5193.65','-4729.96','293.156','5.98138','4417:0 4419:0 '), +('4419','gh641','571','394','4214','72','76','0','5380.16','-4761.41','305.589','6.26491','4418:0 '), +('4420','gh642','571','394','4159','72','76','2','4508.01','-4224.81','167.488','4.05322','4367:0 4421:0 '), +('4421','gh643','571','394','4159','72','76','3','4593.24','-4223.94','178.654','3.05419','4420:0 '), +('4422','gh644','571','394','394','72','76','0','4397.58','-3823.41','207.663','1.98605','4412:0 4423:0 '), +('4423','gh645','571','394','4235','72','76','0','4363.62','-3661.89','253.292','1.03572','4422:0 4424:0 4426:0 '), +('4424','gh646','571','394','4270','72','76','0','4464.25','-3523.69','231.349','0.662651','4423:0 4425:0 '), +('4425','gh647','571','394','4270','72','76','0','4540.35','-3448.14','226.932','0.666578','4424:0 4450:0 '), +('4426','gh648','571','394','394','72','76','0','4254.62','-3537.34','262.169','3.11545','4423:0 4427:0 '), +('4427','gh649','571','394','394','72','76','0','4082.82','-3452.53','279.273','2.96073','4426:0 4428:0 '), +('4428','gh650','571','394','4267','72','76','0','4017.93','-3398.46','291.406','1.89652','4427:0 4429:0 4430:0 '), +('4429','gh651','571','394','394','72','76','0','3805.22','-3452.83','276.397','3.26311','4428:0 4335:0 '), +('4430','gh652','571','394','394','72','76','0','3927.34','-3218.24','296.696','2.32848','4428:0 4431:0 4438:0 '), +('4431','gh653','571','394','394','72','76','0','3809.69','-3057.95','274.256','2.59945','4430:0 4432:0 4445:0 '), +('4432','gh654','571','394','394','72','76','0','3727.35','-2986.21','236.602','2.62301','4431:0 4328:0 4433:0 '), +('4433','gh655','571','394','4203','72','76','0','3799.13','-2875.46','222.297','0.855862','4432:0 4434:0 '), +('4434','gh656','571','394','394','72','76','0','3912.6','-2792.74','176.353','3.96212','4433:0 '), +('4435','gh657','571','394','4199','72','76','0','4204.22','-2467.24','229.848','5.58004','4285:0 4436:0 4437:0 '), +('4436','gh658','571','394','4199','72','76','1','4355.92','-2485.04','242.225','3.56707','4435:0 4437:0 '), +('4437','gh659','571','394','4199','72','76','0','4320.51','-2585.42','246.459','1.97429','4436:0 4435:0 '), +('4438','gh660','571','394','4222','72','76','0','4055.91','-3147.98','278.056','5.70496','4430:0 4439:0 '), +('4439','gh661','571','394','4222','72','76','0','4250.54','-3177.85','307.936','0.898319','4438:0 4440:0 '), +('4440','gh662','571','394','4222','72','76','0','4326.86','-3320.66','310.551','0.546446','4439:0 4441:0 '), +('4441','gh663','571','394','4222','72','76','0','4447.75','-3199.73','313.463','2.16044','4440:0 4442:0 '), +('4442','gh664','571','394','4222','72','76','0','4418.84','-2984.71','309.349','1.76303','4441:0 4443:0 '), +('4443','gh665','571','394','4222','72','76','0','4226.67','-2862.19','281.474','3.5718','4442:0 4444:0 '), +('4444','gh666','571','394','4222','72','76','0','4035.13','-2946.41','276.096','3.8946','4443:0 4445:0 '), +('4445','gh667','571','394','4222','72','76','0','3928.19','-3008.94','275.401','3.52703','4444:0 4431:0 '), +('4446','gh668','571','394','394','72','76','0','3974.25','-2486.57','216.336','4.66038','4284:0 4447:0 '), +('4447','gh669','571','394','4202','72','76','0','3933.08','-2603.19','206.74','5.22586','4446:0 4448:0 '), +('4448','gh670','571','394','394','72','76','0','3783','-2602.59','191.183','4.81351','4447:0 4329:0 '), +('4449','zd671','571','66','66','73','77','0','5075.22','-4157.1','351.671','0.704319','4415:0 4576:0 4577:0 '), +('4450','zd672','571','66','66','73','77','0','4642.75','-3362.88','292.419','0.845695','4425:0 4516:0 '), +('4451','zd673','571','66','66','73','77','0','4845.51','-1504.94','248.946','5.30284','4195:0 4452:0 '), +('4452','zd674','571','66','66','73','77','0','4915.37','-1688.61','248.309','4.74834','4451:0 4453:0 '), +('4453','zd675','571','66','66','73','77','0','4913.71','-1902.76','248.309','4.71615','4452:0 4454:0 4456:0 '), +('4454','zd676','571','66','66','73','77','0','4907.24','-2149.46','248.308','4.51509','4453:0 4455:0 4456:0 '), +('4455','zd677','571','66','4316','73','77','0','4935.58','-2282.22','243.077','5.02638','4454:0 4492:0 '), +('4456','zd678','571','66','66','73','77','0','4948.85','-2047.71','248.309','0.0634512','4454:0 4453:0 4457:0 '), +('4457','zd679','571','66','66','73','77','0','5154.72','-2050.3','248.297','1.1198','4456:0 4458:0 4488:0 4489:0 '), +('4458','zd680','571','66','66','73','77','0','5198.14','-1908','243.765','1.97824','4457:0 4459:0 '), +('4459','zd681','571','66','4311','73','77','0','5168.01','-1763.65','243.57','1.69157','4458:0 4460:0 '), +('4460','zd682','571','66','4311','73','77','0','5227.64','-1629.23','235.86','1.70729','4459:0 4461:0 '), +('4461','zd683','571','66','66','73','77','0','5202.17','-1444.6','235.3','1.67195','4460:0 4462:0 4463:0 '), +('4462','zd684','571','66','4312','73','77','1','5228.85','-1333.15','242.312','5.2078','4461:0 4463:0 '), +('4463','zd685','571','66','66','73','77','0','5369.86','-1421.32','236.955','3.0236','4462:0 4461:0 4464:0 '), +('4464','zd686','571','66','66','73','77','0','5446.59','-1338.52','239.221','4.34385','4463:0 4465:0 4466:0 4486:0 '), +('4465','zd687','571','66','66','73','77','0','5443.61','-1214.88','247.443','1.55882','4464:0 4607:0 '), +('4466','zd688','571','66','4313','73','77','0','5555.66','-1397.11','239.86','2.99374','4464:0 4467:0 '), +('4467','zd689','571','66','4313','73','77','0','5695.79','-1400.51','234.446','5.54941','4466:0 4468:0 4481:0 '), +('4468','zd690','571','66','4313','73','77','0','5775.25','-1545.64','229.906','6.10312','4467:0 4469:0 4481:0 '), +('4469','zd691','571','66','4469','73','77','0','5886.45','-1638.86','237.734','5.61617','4468:0 4470:0 4482:0 '), +('4470','zd692','571','66','4469','73','77','0','5972.87','-1716.32','231.167','5.38055','4469:0 4471:0 '), +('4471','zd693','571','66','4469','73','77','0','6050.18','-1854.06','239.866','4.90932','4470:0 4472:0 '), +('4472','zd694','571','66','4315','73','77','0','6083.8','-1956.71','235.309','5.74812','4471:0 4473:0 4475:0 '), +('4473','zd695','571','66','4315','73','77','0','6239.25','-1977.17','235.732','4.86297','4472:0 4474:0 '), +('4474','zd696','571','66','4315','73','77','0','6209.7','-2182.71','236.212','3.31259','4473:0 4475:0 '), +('4475','zd697','571','66','4315','73','77','0','6062.56','-2208.12','234.275','1.69703','4474:0 4472:0 4476:0 '), +('4476','zd698','571','66','4315','73','77','0','5846.96','-2232.9','236.499','3.12253','4475:0 4477:0 '), +('4477','zd699','571','66','4468','73','77','0','5680.87','-2213.84','239.83','3.10525','4476:0 4478:0 4483:0 '), +('4478','zd700','571','66','4468','73','77','0','5565.53','-2198.52','235.496','1.86432','4477:0 4479:0 '), +('4479','zd701','571','66','4468','73','77','0','5571.97','-1996.67','237.88','1.26977','4478:0 4480:0 4483:0 4484:0 '), +('4480','zd702','571','66','4468','73','77','0','5653.64','-1810.94','236.435','1.32868','4479:0 4481:0 4482:0 '), +('4481','zd703','571','66','4313','73','77','0','5609.8','-1610.43','237.022','0.732563','4480:0 4468:0 4467:0 '), +('4482','zd704','571','66','4468','73','77','0','5833.14','-1759.05','233.346','1.1936','4480:0 4469:0 4483:0 '), +('4483','zd705','571','66','4468','73','77','0','5766.32','-1991.79','235.993','4.71376','4482:0 4477:0 4479:0 '), +('4484','zd706','571','66','66','73','77','0','5444.04','-1955.64','248.222','4.72161','4479:0 4485:0 4487:0 4488:0 '), +('4485','zd707','571','66','66','73','77','0','5446.88','-1747.93','248.337','1.71275','4484:0 4486:0 '), +('4486','zd708','571','66','66','73','77','0','5445.34','-1527.93','248.26','1.56823','4485:0 4464:0 '), +('4487','zd709','571','66','66','73','77','0','5449.03','-2186.49','248.309','4.69804','4484:0 4493:0 '), +('4488','zd710','571','66','66','73','77','0','5286.44','-2043.61','246.138','3.10918','4484:0 4457:0 '), +('4489','zd711','571','66','4317','73','77','1','5168.98','-2192.3','236.538','5.78582','4457:0 4490:0 '), +('4490','zd712','571','66','66','73','77','0','5057.15','-2200.22','244.382','3.79877','4489:0 4491:0 '), +('4491','zd713','571','66','66','73','77','0','5039.31','-2353.08','242.172','3.30084','4490:0 4492:0 '), +('4492','zd714','571','66','4316','73','77','0','4870.93','-2382.92','234.006','2.40312','4491:0 4455:0 '), +('4493','zd715','571','66','4275','73','77','0','5451.23','-2302.91','297.236','1.61772','4487:0 4494:0 4495:0 4522:0 '), +('4494','zd716','571','66','4275','73','77','0','5448.61','-2517.18','292.419','4.61951','4493:0 4495:0 4511:0 4522:0 '), +('4495','zd717','571','66','4275','73','77','0','5555.82','-2440.17','290.942','2.83587','4494:0 4493:0 4496:0 4522:0 '), +('4496','zd718','571','66','4318','73','77','0','5688.46','-2430.58','287.55','3.11862','4495:0 4497:0 '), +('4497','zd719','571','66','4294','73','77','0','5806.54','-2342.64','290.484','5.7662','4496:0 4498:0 '), +('4498','zd720','571','66','4294','73','77','0','5929.41','-2434.64','292.384','5.92563','4497:0 4499:0 '), +('4499','zd721','571','66','4294','73','77','0','6095.28','-2375.23','290.191','0.345376','4498:0 4500:0 '), +('4500','zd722','571','66','4276','73','77','0','6180.98','-2386.29','307.72','4.9046','4499:0 4501:0 '), +('4501','zd723','571','66','4276','73','77','0','6189.69','-2520.06','304.709','4.73102','4500:0 4502:0 4507:0 '), +('4502','zd724','571','66','4276','73','77','0','6265.08','-2506.2','302.978','4.68313','4501:0 4503:0 '), +('4503','zd725','571','66','4276','73','77','0','6245.94','-2735.34','302.749','3.45398','4502:0 4504:0 4505:0 '), +('4504','zd726','571','66','4276','73','77','0','6057.61','-2737.89','302.8','1.51562','4503:0 4506:0 '), +('4505','zd727','571','66','66','73','77','0','6297.01','-2862.98','293.934','2.17613','4503:0 '), +('4506','zd728','571','66','66','73','77','0','6048.63','-2898.7','296.891','3.34009','4504:0 '), +('4507','zd729','571','66','4276','73','77','0','6198.47','-2622.9','293.162','1.74809','4501:0 4508:0 '), +('4508','zd730','571','66','4276','73','77','0','6057.09','-2616.09','302.748','3.26077','4507:0 4509:0 '), +('4509','zd731','571','66','66','73','77','0','5884.73','-2617.57','292.418','3.15396','4508:0 4510:0 '), +('4510','zd732','571','66','66','73','77','0','5672.49','-2619.11','292.418','3.13825','4509:0 4511:0 4523:0 '), +('4511','zd733','571','66','4275','73','77','1','5457.16','-2620.72','306.551','1.83449','4510:0 4494:0 4512:0 '), +('4512','zd734','571','66','66','73','77','0','5332.55','-2708.7','292.419','3.89224','4511:0 4513:0 4520:0 '), +('4513','zd735','571','66','66','73','77','0','5189.96','-2843.82','292.419','5.47639','4512:0 4514:0 4517:0 4519:0 4520:0 '), +('4514','zd736','571','66','66','73','77','0','5035.14','-2975.52','292.29','3.86318','4513:0 4515:0 4519:0 4529:0 '), +('4515','zd737','571','66','66','73','77','0','4891.99','-3121.16','292.419','3.92601','4514:0 4516:0 4532:0 '), +('4516','zd738','571','66','66','73','77','0','4770.68','-3237.29','292.419','3.91659','4515:0 4450:0 4533:0 '), +('4517','zd739','571','66','66','73','77','0','5358.22','-3018.37','292.42','5.50546','4513:0 4518:0 4529:0 '), +('4518','zd740','571','66','66','73','77','0','5504.36','-3161.64','327.623','5.5251','4517:0 4528:0 4537:0 '), +('4519','zd741','571','66','4278','73','77','0','4997.42','-2791.64','287.647','5.40334','4514:0 4513:0 4520:0 '), +('4520','zd742','571','66','4278','73','77','0','5158.28','-2673.27','288.27','0.638333','4519:0 4512:0 4513:0 4521:0 '), +('4521','zd743','571','66','66','73','77','0','5256.37','-2555.92','288.451','1.56824','4520:0 4522:0 '), +('4522','zd744','571','66','4275','73','77','0','5338.16','-2432.94','289.035','0.00530624','4521:0 4494:0 4493:0 4495:0 '), +('4523','zd745','571','66','4280','73','77','0','5779.01','-2690.72','276.583','2.21778','4510:0 4524:0 '), +('4524','zd746','571','66','4320','73','77','0','5721.5','-2843.26','274.479','3.99436','4523:0 4525:0 4527:0 '), +('4525','zd747','571','66','4320','73','77','1','5795.09','-3017.85','286.307','5.12926','4524:0 4526:0 '), +('4526','zd748','571','66','4320','73','77','0','5829.57','-3089.62','343.078','5.24393','4525:0 4542:0 '), +('4527','zd749','571','66','4280','73','77','0','5611.83','-2947.53','274.379','3.44223','4524:0 4528:0 '), +('4528','zd750','571','66','4280','73','77','0','5467.65','-3021.98','288.17','4.29281','4527:0 4518:0 '), +('4529','zd751','571','66','4279','73','77','0','5183.53','-3158.14','271.373','0.983891','4517:0 4514:0 4530:0 '), +('4530','zd752','571','66','4279','73','77','0','5277.96','-3280.48','281.073','4.24508','4529:0 4531:0 '), +('4531','zd753','571','66','4279','73','77','0','5170.84','-3397.25','289.096','2.18261','4530:0 4532:0 4534:0 '), +('4532','zd754','571','66','4279','73','77','0','5037.17','-3252.81','278.555','2.42609','4531:0 4515:0 '), +('4533','zd755','571','66','4299','73','77','0','4868.35','-3381.36','292.891','5.68473','4516:0 4534:0 '), +('4534','zd756','571','66','4299','73','77','0','5062.51','-3481.16','289.586','4.38568','4533:0 4531:0 4535:0 '), +('4535','zd757','571','66','4299','73','77','0','5037.37','-3645.39','298.622','4.61344','4534:0 4536:0 '), +('4536','zd758','571','66','4299','73','77','0','5087.62','-3673.69','368.159','1.40275','4535:0 '), +('4537','zd759','571','66','66','73','77','0','5653.1','-3291.77','372.841','5.39966','4518:0 4538:0 4542:0 4573:0 '), +('4538','zd760','571','66','4323','73','77','0','5760.12','-3480.35','382.038','4.42027','4537:0 4539:0 4542:0 4573:0 '), +('4539','zd761','571','66','4323','73','77','1','5763.21','-3592.34','386.495','5.18723','4538:0 4540:0 '), +('4540','zd762','571','66','66','73','77','0','5805.54','-3730.07','371.987','2.07548','4539:0 4541:0 4558:0 4568:0 '), +('4541','zd763','571','66','66','73','77','0','5926.35','-3596.12','371.987','2.21685','4540:0 4542:0 4559:0 '), +('4542','zd764','571','66','66','73','77','0','5852.16','-3456.19','373.499','0.462273','4537:0 4541:0 4538:0 4543:0 4526:0 '), +('4543','zd765','571','66','4327','73','77','0','6015.43','-3354.83','351.334','0.898171','4542:0 4544:0 4546:0 '), +('4544','zd766','571','66','4327','73','77','0','6064.66','-3220.41','351.084','5.24378','4543:0 4545:0 '), +('4545','zd767','571','66','4327','73','77','0','6170.15','-3269.78','354.099','4.83851','4544:0 4546:0 4550:0 '), +('4546','zd768','571','66','4327','73','77','0','6123.58','-3376.38','350.427','3.6502','4545:0 4543:0 4547:0 '), +('4547','zd769','571','66','4328','73','77','0','6121.57','-3498.34','384.702','6.11321','4546:0 4549:0 4548:0 '), +('4548','zd770','571','66','4328','73','77','0','6262.56','-3570.42','383.729','4.01857','4547:0 4549:0 4556:0 '), +('4549','zd771','571','66','4328','73','77','0','6183.8','-3653.66','384.553','1.57362','4547:0 4548:0 4559:0 '), +('4550','zd772','571','66','4321','73','77','0','6322.87','-3285.06','388.539','6.26715','4545:0 4551:0 4555:0 4556:0 '), +('4551','zd773','571','66','4321','73','77','0','6411.23','-3132.08','389.357','0.676679','4550:0 4552:0 '), +('4552','zd774','571','66','4321','73','77','0','6573.73','-3100.58','392.875','3.25828','4551:0 4553:0 '), +('4553','zd775','571','66','4321','73','77','0','6591.66','-3194.21','412.812','2.18937','4552:0 4554:0 '), +('4554','zd776','571','66','4321','73','77','0','6440.64','-3192.66','402.475','4.61546','4553:0 4555:0 '), +('4555','zd777','571','66','4321','73','77','0','6464.24','-3278.19','402.912','0.40965','4554:0 4550:0 '), +('4556','zd778','571','66','4321','73','77','0','6447.03','-3450.35','388.772','5.0215','4550:0 4557:0 4548:0 '), +('4557','zd779','571','66','66','73','77','0','6351.61','-3625.18','379.474','1.56496','4556:0 '), +('4558','zd780','571','66','4326','73','77','0','5890.65','-3784.26','361.962','2.65353','4540:0 4559:0 4560:0 '), +('4559','zd781','571','66','66','73','77','0','6002.17','-3684.49','371.988','2.91507','4558:0 4541:0 4549:0 4581:0 '), +('4560','zd782','571','66','66','73','77','0','5871.01','-3998.48','364.264','5.0278','4558:0 4561:0 '), +('4561','zd783','571','66','4371','73','77','0','5936.71','-4166.52','353.36','6.12894','4560:0 4562:0 4564:0 4565:0 '), +('4562','zd784','571','66','4371','73','77','0','6121.47','-4241.02','320.493','2.98578','4561:0 4563:0 '), +('4563','zd785','571','66','4371','73','77','0','6077.47','-4432.62','362.867','1.61132','4562:0 4564:0 '), +('4564','zd786','571','66','4371','73','77','0','5913.09','-4334','361.983','1.57991','4561:0 4563:0 '), +('4565','zd787','571','66','4325','73','77','0','5789.33','-4263.74','371.348','4.26205','4561:0 4566:0 '), +('4566','zd788','571','66','4325','73','77','0','5716.79','-4325.83','373.993','4.5605','4565:0 4567:0 4580:0 '), +('4567','zd789','571','66','4325','73','77','0','5716.89','-4120.29','353.305','1.62547','4566:0 4568:0 4580:0 '), +('4568','zd790','571','66','66','73','77','0','5706.28','-3833.4','371.987','0.940598','4567:0 4540:0 4569:0 '), +('4569','zd791','571','66','66','73','77','0','5537.84','-3840.96','372.119','2.47998','4568:0 4570:0 4571:0 '), +('4570','zd792','571','66','4322','73','77','0','5351.53','-3648.35','361.961','2.27578','4569:0 4571:0 4572:0 4574:0 '), +('4571','zd793','571','66','4322','73','77','0','5343.06','-3764.54','373.087','0.190553','4569:0 4570:0 '), +('4572','zd794','571','66','66','73','77','0','5524.4','-3572.01','365.432','0.730907','4570:0 4573:0 '), +('4573','zd795','571','66','4324','73','77','0','5626.9','-3422.21','363.833','0.990089','4572:0 4537:0 4538:0 '), +('4574','zd796','571','66','4322','73','77','0','5153.32','-3723.32','360.396','4.07514','4570:0 4575:0 '), +('4575','zd797','571','66','4322','73','77','0','5076.21','-3849.59','356.664','4.99406','4574:0 4576:0 '), +('4576','zd798','571','66','66','73','77','0','5136.04','-4040.57','355.425','5.10401','4575:0 4449:0 4577:0 '), +('4577','zd799','571','66','66','73','77','0','5278.78','-4177.66','363.104','2.86954','4576:0 4449:0 4578:0 '), +('4578','zd800','571','66','66','73','77','0','5437.96','-4288.78','363.086','5.803','4577:0 4579:0 4580:0 '), +('4579','zd801','571','66','66','73','77','0','5440.87','-4452.18','379.545','5.85562','4578:0 '), +('4580','zd802','571','66','4325','73','77','0','5627.9','-4207.17','364.009','5.93258','4578:0 4567:0 4566:0 '), +('4581','zd803','571','66','66','73','77','0','6164.11','-3834.22','436.687','5.57209','4559:0 4582:0 '), +('4582','zd804','571','66','66','73','77','0','6297.28','-3979.47','456.92','5.47784','4581:0 4583:0 '), +('4583','zd805','571','66','66','73','77','0','6443.44','-4126.1','462.301','5.40087','4582:0 4584:0 4594:0 '), +('4584','zd806','571','66','4329','73','77','0','6406.23','-4295.64','457.247','4.84558','4583:0 4585:0 4593:0 '), +('4585','zd807','571','66','66','73','77','0','6273.9','-4442.23','450.834','3.83242','4584:0 4586:0 4589:0 '), +('4586','zd808','571','66','4329','73','77','0','6178.17','-4399.01','456.879','1.41967','4585:0 4587:0 '), +('4587','zd809','571','66','4329','73','77','0','6227.62','-4232.4','436.632','4.94847','4586:0 4588:0 '), +('4588','zd810','571','66','4329','73','77','0','6214.01','-4138.28','447.456','5.0066','4587:0 '), +('4589','zd811','571','66','4373','73','77','0','6412.14','-4626.89','453.335','2.16502','4585:0 4590:0 '), +('4590','zd812','571','66','4375','73','77','0','6602.74','-4663.95','450.613','0.474835','4589:0 4591:0 4605:0 '), +('4591','zd813','571','66','4375','73','77','0','6698.48','-4568.11','450.518','2.11632','4590:0 4592:0 '), +('4592','zd814','571','66','4375','73','77','0','6638.09','-4503.12','485.547','2.39985','4591:0 4593:0 '), +('4593','zd815','571','66','4373','73','77','0','6532.06','-4368.66','450.654','5.95377','4592:0 4584:0 4594:0 '), +('4594','zd816','571','66','66','73','77','0','6586.21','-4259.92','452.582','2.52315','4593:0 4583:0 4595:0 '), +('4595','zd817','571','66','66','73','77','0','6681.53','-4181.26','455.784','5.78336','4594:0 4596:0 4603:0 '), +('4596','zd818','571','66','66','73','77','0','6821.02','-4227.92','452.785','0.387671','4595:0 4597:0 4603:0 '), +('4597','zd819','571','66','4579','73','77','1','6899.77','-4113.21','467.355','4.92777','4596:0 4598:0 '), +('4598','zd820','571','66','66','73','77','0','7055.14','-4222.88','450.363','3.95496','4597:0 4599:0 '), +('4599','zd821','571','66','66','73','77','0','7139.88','-4413.45','457.062','4.921','4598:0 4600:0 '), +('4600','zd822','571','66','4375','73','77','0','7003.8','-4522.32','450.519','3.23161','4599:0 4601:0 '), +('4601','zd823','571','66','4375','73','77','0','6879.7','-4603.96','452.922','4.36494','4600:0 4602:0 4604:0 '), +('4602','zd824','571','66','4375','73','77','0','6779.05','-4455.52','441.057','2.48784','4601:0 4603:0 '), +('4603','zd825','571','66','4375','73','77','0','6738.67','-4314.66','450.026','0.835357','4602:0 4596:0 4595:0 '), +('4604','zd826','571','66','4375','73','77','0','6817.32','-4743.11','450.661','4.44034','4601:0 4605:0 '), +('4605','zd827','571','66','66','73','77','0','6680.31','-4847.44','452.584','2.08415','4604:0 4590:0 4606:0 '), +('4606','zd828','571','66','4375','73','77','0','6841.86','-4987.38','451.036','5.56582','4605:0 '), +('4607','cs829','571','2817','4557','74','78','0','5438.42','-1075.94','181.63','1.64666','4465:0 4608:0 4611:0 4613:0 '), +('4608','cs830','571','2817','4554','74','78','0','5414.79','-913.179','165.857','1.65453','4607:0 4609:0 '), +('4609','cs831','571','2817','4557','74','78','0','5529.65','-840.32','161.565','0.178767','4608:0 4610:0 4612:0 4624:0 '), +('4610','cs832','571','2817','4557','74','78','0','5646.92','-907.293','186.362','5.31056','4609:0 4611:0 '), +('4611','cs833','571','2817','4557','74','78','0','5531.5','-987.127','191.383','0.576966','4607:0 4610:0 '), +('4612','cs834','571','2817','4558','74','78','5','5605.93','-749.995','199.49','1.05213','4609:0 '), +('4613','cs835','571','2817','4554','74','78','0','5331.06','-914.795','170.103','1.60977','4607:0 4614:0 '), +('4614','cs836','571','2817','4554','74','78','0','5307.84','-750.937','162.904','2.08886','4613:0 4615:0 '), +('4615','cs837','571','2817','4557','74','78','0','5179.88','-711.115','164.986','1.15816','4614:0 4616:0 4618:0 '), +('4616','cs838','571','2817','2817','74','78','2','5082.9','-704.384','186.517','6.16508','4615:0 4617:0 '), +('4617','cs839','571','2817','4559','74','78','3','5062.59','-611.621','218.683','5.43466','4616:0 '), +('4618','cs840','571','2817','4557','74','78','0','5185.33','-555.375','158.496','1.08748','4615:0 4619:0 '), +('4619','cs841','571','2817','4557','74','78','0','5212.12','-335.385','163.451','1.20529','4618:0 4620:0 '), +('4620','cs842','571','2817','4557','74','78','0','5329.19','-218.838','166.128','0.232181','4619:0 4621:0 4629:0 '), +('4621','cs843','571','2817','4557','74','78','0','5520.69','-188.647','157.616','0.0751006','4620:0 4622:0 4629:0 '), +('4622','cs844','571','2817','4557','74','78','0','5672.16','-264.918','167.932','5.74096','4621:0 4623:0 4625:0 4720:0 '), +('4623','cs845','571','2817','4557','74','78','0','5583.23','-439.368','154.644','4.20158','4622:0 4624:0 '), +('4624','cs846','571','2817','4554','74','78','0','5498.08','-612.708','152.463','4.75686','4623:0 4609:0 '), +('4625','cs847','571','2817','4553','74','78','0','5679.51','-108.805','178.045','1.46212','4622:0 4626:0 4855:0 '), +('4626','cs848','571','2817','4553','74','78','0','5720.75','54.3693','171.054','1.38122','4625:0 4627:0 4628:0 4643:0 '), +('4627','cs849','571','2817','4553','74','78','0','5765.22','181.998','182.874','0.613098','4626:0 '), +('4628','cs850','571','2817','4553','74','78','0','5544.73','121.881','150.263','4.18823','4626:0 4629:0 '), +('4629','cs851','571','2817','4553','74','78','0','5414.2','-47.7746','149.795','3.91334','4628:0 4621:0 4620:0 4630:0 '), +('4630','cs852','571','2817','2817','74','78','0','5354.31','126.368','161.642','2.27029','4629:0 4631:0 4632:0 '), +('4631','cs853','571','2817','2817','74','78','0','5257.75','154.168','191.769','2.73995','4630:0 4147:0 '), +('4632','cs854','571','2817','2817','74','78','0','5423.58','292.611','156.85','1.48646','4630:0 4633:0 '), +('4633','cs855','571','2817','2817','74','78','0','5436.65','406.464','167.948','1.52965','4632:0 4634:0 4642:0 '), +('4634','cs856','571','2817','4555','74','78','0','5332.71','578.052','182.7','1.3333','4633:0 4635:0 '), +('4635','cs857','571','2817','4555','74','78','0','5471.03','710.798','171.741','0.739547','4634:0 4636:0 '), +('4636','cs858','571','2817','2817','74','78','0','5519.14','908.014','167.988','1.35608','4635:0 4637:0 4638:0 '), +('4637','cs859','571','2817','4556','74','78','1','5706.15','1006.05','174.48','3.67458','4636:0 '), +('4638','cs860','571','2817','2817','74','78','0','5682.36','825.06','156.036','5.32233','4636:0 4639:0 '), +('4639','cs861','571','2817','2817','74','78','0','5849.61','885.831','160.066','0.393957','4638:0 4640:0 '), +('4640','cs862','571','2817','4553','74','78','0','5854.17','706.025','164.715','4.73956','4639:0 4641:0 '), +('4641','cs863','571','2817','4553','74','78','0','5740.73','555.099','158.091','4.03271','4640:0 4642:0 '), +('4642','cs864','571','2817','4553','74','78','0','5615.21','410.65','156.222','3.51042','4641:0 4633:0 4643:0 '), +('4643','cs865','571','2817','4553','74','78','0','5676.64','245.015','166.802','4.93592','4642:0 4626:0 '), +('4644','sb866','571','3711','4483','74','78','0','4671.5','5526.35','34.4275','0.54946','3903:0 4645:0 '), +('4645','sb867','571','3711','3711','74','78','0','4892.01','5531.93','-71.5606','0.0389507','4644:0 4646:0 4674:0 '), +('4646','sb868','571','3711','4289','74','78','0','4952.39','5739.79','-75.5644','1.2249','4645:0 4647:0 '), +('4647','sb869','571','3711','4289','74','78','0','5130.17','5883.3','-75.7286','0.492126','4646:0 4648:0 '), +('4648','sb870','571','3711','4289','74','78','0','5325.13','5886.98','-68.7967','0.00281286','4647:0 4649:0 4675:0 '), +('4649','sb871','571','3711','4285','74','78','0','5507.19','5934.35','-51.6289','6.11243','4648:0 4650:0 '), +('4650','sb872','571','3711','3711','74','78','0','5713','5871.42','-60.4173','5.97499','4649:0 4651:0 4714:0 '), +('4651','sb873','571','3711','4289','74','78','0','5882.95','5730.44','-63.8448','5.56737','4650:0 4652:0 '), +('4652','sb874','571','3711','4289','74','78','0','6018.42','5571.93','-74.0181','5.18095','4651:0 4653:0 4703:0 '), +('4653','sb875','571','3711','4385','74','78','0','6082.5','5353.27','-92.7974','4.70422','4652:0 4654:0 4702:0 '), +('4654','sb876','571','3711','3711','74','78','0','6268.69','5300.48','-100.197','0.380579','4653:0 4655:0 '), +('4655','sb877','571','3711','3711','74','78','0','6450.25','5197.28','-64.1861','5.49824','4654:0 4656:0 '), +('4656','sb878','571','3711','4376','74','78','0','6507.21','4992.87','-60.1724','4.97202','4655:0 4657:0 4696:0 '), +('4657','sb879','571','3711','3711','74','78','0','6524.26','4794.34','-58.974','4.36726','4656:0 4658:0 '), +('4658','sb880','571','3711','4368','74','78','0','6443.53','4602.81','-66.2857','4.55104','4657:0 4659:0 4695:0 4697:0 '), +('4659','sb881','571','3711','4368','74','78','0','6405.01','4366.1','-47.8041','3.90309','4658:0 4660:0 4694:0 4695:0 '), +('4660','sb882','571','3711','3711','74','78','0','6226.59','4265.96','-45.1729','3.55594','4659:0 4661:0 4694:0 '), +('4661','sb883','571','3711','4283','74','78','0','6111.71','4134.4','-48.7024','4.20861','4660:0 4662:0 '), +('4662','sb884','571','3711','4283','74','78','0','6038.48','4009.63','-42.6825','4.13556','4661:0 4663:0 '), +('4663','sb885','571','3711','4283','74','78','0','5938.28','3889.47','-35.5353','3.47426','4662:0 4664:0 4692:0 '), +('4664','sb886','571','3711','4283','74','78','0','5785.88','3776.91','-41.0442','3.94','4663:0 4665:0 4692:0 4693:0 '), +('4665','sb887','571','3711','3711','74','78','0','5624.29','3606.52','-21.7442','3.13811','4664:0 4666:0 4693:0 '), +('4666','sb888','571','3711','4283','74','78','0','5488.8','3794.13','-58.7714','2.25061','4665:0 4667:0 '), +('4667','sb889','571','3711','4387','74','78','0','5337.73','3917.13','-70.8363','2.57655','4666:0 4668:0 4688:0 4689:0 '), +('4668','sb890','571','3711','3711','74','78','0','5220.59','4104.15','-78.9241','2.09588','4667:0 4669:0 4687:0 '), +('4669','sb891','571','3711','3711','74','78','0','5146.69','4294.33','-99.9765','1.80057','4668:0 4670:0 4686:0 '), +('4670','sb892','571','3711','3711','74','78','1','5110.31','4494.04','-98.6996','1.66941','4669:0 4671:0 '), +('4671','sb893','571','3711','3711','74','78','0','5098.19','4704.21','-133.607','2.04248','4670:0 4672:0 4681:0 '), +('4672','sb894','571','3711','3711','74','78','0','4975.85','4879.83','-101.866','1.73853','4671:0 4673:0 4682:0 '), +('4673','sb895','571','3711','3711','74','78','0','4881.56','5059.86','-91.1009','1.00104','4672:0 4674:0 '), +('4674','sb896','571','3711','3711','74','78','0','4917.13','5259.43','-92.7846','1.6058','4673:0 4645:0 4718:0 '), +('4675','sb897','571','3711','3711','74','78','0','5325.03','5694.77','-87.1296','4.35079','4648:0 4676:0 '), +('4676','sb898','571','3711','4288','74','78','0','5283.58','5529.21','-98.5324','4.62568','4675:0 4677:0 '), +('4677','sb899','571','3711','3711','74','78','0','5296.26','5368.21','-119.485','5.45035','4676:0 4678:0 4715:0 '), +('4678','sb900','571','3711','4293','74','78','0','5334.1','5156.57','-135.744','4.8503','4677:0 4679:0 '), +('4679','sb901','571','3711','4293','74','78','0','5316.24','4983.73','-135.985','4.21805','4678:0 4680:0 4719:0 '), +('4680','sb902','571','3711','4293','74','78','0','5263.3','4836.68','-132.956','4.22984','4679:0 4681:0 '), +('4681','sb903','571','3711','4293','74','78','0','5171.92','4761.28','-134.094','3.64315','4680:0 4671:0 '), +('4682','sb904','571','3711','3711','74','78','0','4906.61','4752.07','-80.9517','3.99658','4672:0 4683:0 '), +('4683','sb905','571','3711','4388','74','78','0','4784.59','4651.45','-67.3646','4.27774','4682:0 4684:0 '), +('4684','sb906','571','3711','4388','74','78','0','4776.89','4461.82','-65.79','0.115909','4683:0 4685:0 '), +('4685','sb907','571','3711','3711','74','78','0','4858.79','4312.13','-51.668','4.56127','4684:0 4686:0 '), +('4686','sb908','571','3711','4304','74','78','0','5008.15','4265.85','-85.0377','2.88757','4685:0 4669:0 '), +('4687','sb909','571','3711','4303','74','78','0','5141.4','4002.05','-61.6423','4.65002','4668:0 4688:0 '), +('4688','sb910','571','3711','3711','74','78','0','5236.32','3820.55','-29.2583','0.629582','4687:0 4667:0 '), +('4689','sb911','571','3711','4283','74','78','0','5426.13','4100.88','-86.7674','1.1935','4667:0 4690:0 '), +('4690','sb912','571','3711','4283','74','78','0','5555.95','4256','-102.563','0.234915','4689:0 4691:0 4710:0 '), +('4691','sb913','571','3711','4283','74','78','0','5713.23','4134.64','-89.7735','5.05333','4690:0 4692:0 '), +('4692','sb914','571','3711','4283','74','78','0','5757.78','3962.65','-74.4666','5.33842','4691:0 4663:0 4664:0 '), +('4693','sb915','571','3711','4297','74','78','0','5782.35','3632.67','-13.2643','3.22964','4664:0 4665:0 '), +('4694','sb916','571','3711','3711','74','78','0','6360.37','4223.67','-44.0521','1.79237','4659:0 4660:0 '), +('4695','sb917','571','3711','4368','74','78','0','6543.56','4456.6','-47.1567','2.3712','4659:0 4658:0 '), +('4696','sb918','571','3711','4376','74','78','0','6354.19','4873.76','-84.2341','5.19234','4656:0 4697:0 '), +('4697','sb919','571','3711','3711','74','78','0','6357.09','4685.34','-78.5111','5.19313','4696:0 4658:0 4698:0 '), +('4698','sb920','571','3711','3711','74','78','0','6208.52','4590.1','-92.1573','3.70988','4697:0 4699:0 '), +('4699','sb921','571','3711','3711','74','78','0','6025.9','4686.81','-95.5438','1.9176','4698:0 4700:0 '), +('4700','sb922','571','3711','3711','74','78','0','5994.9','4894.76','-99.8287','1.31128','4699:0 4701:0 '), +('4701','sb923','571','3711','4385','74','78','0','5981.34','5089.26','-100.986','1.44401','4700:0 4702:0 '), +('4702','sb924','571','3711','4391','74','78','0','5939.08','5277.6','-99.1834','0.521168','4701:0 4653:0 4703:0 4704:0 '), +('4703','sb925','571','3711','4391','74','78','0','5903.46','5420.52','-94.3184','1.13064','4702:0 4652:0 '), +('4704','sb926','571','3711','4293','74','78','0','5830.14','5100.16','-132.628','3.78921','4702:0 4705:0 '), +('4705','sb927','571','3711','4293','74','78','0','5680.53','5002.31','-134.988','3.89916','4704:0 4706:0 '), +('4706','sb928','571','3711','4293','74','78','0','5699.45','4825.48','-137.014','5.40319','4705:0 4707:0 '), +('4707','sb929','571','3711','4293','74','78','0','5769.89','4694.83','-130.521','4.6445','4706:0 4708:0 '), +('4708','sb930','571','3711','4293','74','78','0','5768.78','4519.94','-133.978','4.9029','4707:0 4709:0 '), +('4709','sb931','571','3711','4293','74','78','0','5638.21','4442.49','-138.205','3.53632','4708:0 4710:0 4711:0 '), +('4710','sb932','571','3711','4293','74','78','0','5520.96','4384.34','-134.132','5.02072','4709:0 4690:0 '), +('4711','sb933','571','3711','4293','74','78','1','5647.9','4579.12','-137.584','3.59247','4709:0 4712:0 '), +('4712','sb934','571','3711','4293','74','78','0','5559.82','4670.56','-135.523','1.45736','4711:0 4713:0 '), +('4713','sb935','571','3711','4383','74','78','5','5497.21','4748.71','-193.716','5.27047','4712:0 '), +('4714','sb936','571','3711','4284','74','78','1','5575.69','5751.97','-74.7038','0.766207','4650:0 4717:0 '), +('4715','sb937','571','3711','4285','74','78','0','5456.52','5353.98','-134.279','6.15248','4677:0 4716:0 '), +('4716','sb938','571','3711','3711','74','78','0','5648.23','5302.27','-121.201','1.53199','4715:0 4717:0 '), +('4717','sb939','571','3711','3711','74','78','0','5580.86','5590.13','-92.6294','1.61445','4716:0 4714:0 '), +('4718','sb940','571','3711','3711','74','78','0','5041.36','5069.92','-110.999','5.98282','4674:0 4719:0 '), +('4719','sb941','571','3711','4293','74','78','0','5202.57','5018.06','-134.206','5.92392','4718:0 4679:0 '), +('4720','sp942','571','67','67','76','80','0','5752.46','-354.53','205.676','5.2249','4622:0 4721:0 '), +('4721','sp943','571','67','4419','76','80','0','5903.83','-485.757','296.6','6.0849','4720:0 4722:0 '), +('4722','sp944','571','67','4419','76','80','0','6079.55','-564.659','370.689','5.9286','4721:0 4723:0 4735:0 4736:0 '), +('4723','sp945','571','67','4419','76','80','0','6182.6','-758.333','400.203','4.95','4722:0 4724:0 4734:0 '), +('4724','sp946','571','67','4419','76','80','0','6143.85','-967.832','398.945','4.41828','4723:0 4725:0 4726:0 '), +('4725','sp947','571','67','4418','76','80','1','6122.51','-1083.62','402.604','1.60263','4724:0 '), +('4726','sp948','571','67','4419','76','80','0','6273.81','-1034.37','412.299','5.98122','4724:0 4727:0 4737:0 '), +('4727','sp949','571','67','4462','76','80','0','6444.88','-1034.29','429.644','6.27654','4726:0 4728:0 '), +('4728','sp950','571','67','4462','76','80','0','6611.36','-1016.67','427.636','0.199126','4727:0 4729:0 4730:0 '), +('4729','sp951','571','67','4462','76','80','0','6639.97','-1111.4','427.1','3.18364','4728:0 '), +('4730','sp952','571','67','4462','76','80','0','6673.93','-1219.13','398.841','5.0403','4728:0 4731:0 4732:0 '), +('4731','sp953','571','67','4462','76','80','0','6592.21','-1275.82','392.991','5.05994','4730:0 '), +('4732','sp954','571','67','4462','76','80','0','6697.35','-1426.2','388.747','4.94449','4730:0 4733:0 '), +('4733','sp955','571','67','4462','76','80','0','6795.01','-1512.06','359.911','5.79979','4732:0 '), +('4734','sp956','571','67','4419','76','80','0','6324.13','-838.805','407.569','0.335772','4723:0 '), +('4735','sp957','571','67','4419','76','80','0','6232.08','-626.646','414.707','2.81842','4722:0 '), +('4736','sp958','571','67','4419','76','80','0','6146.68','-346.08','436.33','4.09626','4722:0 '), +('4737','sp959','571','67','4419','76','80','0','6257','-1198.57','428.281','5.0293','4726:0 4738:0 4739:0 '), +('4738','sp960','571','67','4460','76','80','0','6297.61','-1350.75','426.332','4.79525','4737:0 4745:0 '), +('4739','sp961','571','67','4460','76','80','0','6393.21','-1222.32','429.226','4.92092','4737:0 4740:0 '), +('4740','sp962','571','67','4419','76','80','0','6471.87','-1383.27','479.473','4.04521','4739:0 4741:0 '), +('4741','sp963','571','67','4421','76','80','0','6345.12','-1517.97','434.126','3.78838','4740:0 4742:0 '), +('4742','sp964','571','67','4421','76','80','0','6349.58','-1586.52','428.04','5.62228','4741:0 4743:0 4745:0 '), +('4743','sp965','571','67','4421','76','80','0','6455.08','-1685.64','435.366','5.95606','4742:0 4744:0 '), +('4744','sp966','571','67','4421','76','80','0','6462.41','-1739.78','484.912','2.47281','4743:0 '), +('4745','sp967','571','67','4421','76','80','0','6232.83','-1499.78','419.118','0.960144','4742:0 4738:0 '), +('4746','sp968','571','67','4484','76','80','1','8455.31','-325.265','906.979','2.46809','4747:0 '), +('4747','sp969','571','67','4484','76','80','0','8485.73','-279.992','850.711','1.97722','4746:0 4748:0 '), +('4748','sp970','571','67','4436','76','80','0','8483.73','-136.842','797.392','1.57038','4747:0 4749:0 4752:0 '), +('4749','sp971','571','67','4436','76','80','0','8481.05','57.8398','786.425','1.56174','4748:0 4750:0 '), +('4750','sp972','571','67','4436','76','80','0','8468.58','226.668','788.805','1.98979','4749:0 4751:0 '), +('4751','sp973','571','67','4436','76','80','0','8317.77','105.355','824.229','4.03339','4750:0 4752:0 '), +('4752','sp974','571','67','4436','76','80','0','8313.08','-91.3043','827.379','4.66407','4751:0 4748:0 4753:0 4765:0 '), +('4753','sp975','571','67','4435','76','80','0','8202.06','-202.29','871.634','4.01769','4752:0 4754:0 4756:0 '), +('4754','sp976','571','67','4435','76','80','0','8237.33','-247.278','952.88','3.24408','4753:0 4755:0 '), +('4755','sp977','571','67','4435','76','80','0','8092.31','-403.116','964.773','0.843118','4754:0 '), +('4756','sp978','571','67','4435','76','80','0','8040.6','-222.79','846.911','3.84806','4753:0 4757:0 '), +('4757','sp979','571','67','4435','76','80','0','7945.56','-368.7','884.037','5.26884','4756:0 4758:0 4759:0 4761:0 '), +('4758','sp980','571','67','4435','76','80','0','7953.58','-524.527','913.45','5.63719','4757:0 4783:0 '), +('4759','sp981','571','67','4435','76','80','0','8056.94','-415.927','981.568','4.73712','4757:0 4760:0 '), +('4760','sp982','571','67','4435','76','80','0','8116.43','-385.365','981.611','0.510895','4759:0 '), +('4761','sp983','571','67','4436','76','80','0','7847.85','-252.132','885.258','1.43374','4757:0 4762:0 4770:0 '), +('4762','sp984','571','67','4436','76','80','0','7851.3','-120.4','880.754','2.3126','4761:0 4763:0 '), +('4763','sp985','571','67','4486','76','80','0','7796.32','-44.5017','882.083','4.37664','4762:0 4764:0 '), +('4764','sp986','571','67','4486','76','80','0','7737.47','-4.97132','866.565','4.27297','4763:0 '), +('4765','sp987','571','67','4434','76','80','0','8078.57','-32.5271','869.499','2.49247','4752:0 4766:0 '), +('4766','sp988','571','67','4434','76','80','0','8168.23','99.4058','898.859','1.3183','4765:0 4767:0 '), +('4767','sp989','571','67','4434','76','80','0','7974.07','-6.90656','961.657','1.27039','4766:0 4768:0 '), +('4768','sp990','571','67','4434','76','80','0','7950.02','93.3063','1028.03','0.358544','4767:0 4769:0 '), +('4769','sp991','571','67','4434','76','80','0','7756.08','70.5513','1009.34','2.07228','4768:0 '), +('4770','sp992','571','67','4436','76','80','0','7731.17','-155.37','873.081','2.5828','4761:0 4771:0 '), +('4771','sp993','571','67','67','76','80','0','7531.71','-82.1077','837.076','3.3682','4770:0 4772:0 '), +('4772','sp994','571','67','4424','76','80','0','7365.61','-62.5754','778.928','3.05011','4771:0 4773:0 4777:0 '), +('4773','sp995','571','67','4424','76','80','0','7426.39','97.7694','770.976','6.19485','4772:0 4774:0 '), +('4774','sp996','571','67','4499','76','80','0','7491.52','288.222','774.525','2.91503','4773:0 4775:0 '), +('4775','sp997','571','67','4424','76','80','0','7352.12','248.002','774.191','5.46442','4774:0 4776:0 '), +('4776','sp998','571','67','4424','76','80','0','7358.47','144.777','783.476','4.25726','4775:0 4777:0 '), +('4777','sp999','571','67','4424','76','80','0','7260.89','6.23937','773.777','4.18657','4776:0 4772:0 4778:0 '), +('4778','sp1000','571','67','4424','76','80','0','7178.24','-109.916','772.616','4.20384','4777:0 4779:0 4782:0 '), +('4779','sp1001','571','67','4452','76','80','0','7066.53','-147.905','785.381','3.57396','4778:0 4780:0 '), +('4780','sp1002','571','67','4453','76','80','0','6959.39','-173.873','763.88','2.97313','4779:0 4781:0 '), +('4781','sp1003','571','67','4453','76','80','0','6782.97','-67.7557','738.941','2.11233','4780:0 '), +('4782','sp1004','571','67','4452','76','80','0','7090.4','-308.701','774.099','4.51251','4778:0 '), +('4783','sp1005','571','67','67','76','80','0','8123.18','-622.333','945.418','5.51782','4758:0 4784:0 '), +('4784','sp1006','571','67','4446','76','80','0','8249.28','-737.261','923.975','5.57986','4783:0 4785:0 '), +('4785','sp1007','571','67','4446','76','80','0','8264.89','-900.437','923.875','2.77599','4784:0 4786:0 4788:0 '), +('4786','sp1008','571','67','4485','76','80','0','8140.52','-817.38','956.118','4.95861','4785:0 4787:0 '), +('4787','sp1009','571','67','4485','76','80','0','8148.76','-940.656','956.955','0.816413','4786:0 '), +('4788','sp1010','571','67','4446','76','80','0','8178.94','-1015.84','926.187','5.46676','4785:0 4789:0 '), +('4789','sp1011','571','67','4446','76','80','0','8330.55','-1186.57','924.477','5.49032','4788:0 '), +('4790','sp1012','571','67','4441','76','80','4','7798.27','-2842.24','1219.51','2.4704','4791:0 4792:0 '), +('4791','sp1013','571','67','4441','76','80','5','7804.98','-2945.31','1256.79','1.43524','4790:0 '), +('4792','sp1014','571','67','4441','76','80','0','7866.14','-2800.43','1136.14','2.6746','4790:0 4793:0 4808:0 '), +('4793','sp1015','571','67','4444','76','80','0','7974.26','-2941.67','1137.82','0.286989','4792:0 4794:0 '), +('4794','sp1016','571','67','4444','76','80','0','8053.32','-2899.02','1136.35','5.48632','4793:0 4795:0 4799:0 '), +('4795','sp1017','571','67','67','76','80','0','8303.1','-2938.59','1067.71','2.65574','4794:0 4796:0 '), +('4796','sp1018','571','67','4488','76','80','0','8126.19','-2813.7','1044.76','1.21375','4795:0 4797:0 4798:0 '), +('4797','sp1019','571','67','4488','76','80','0','8235.54','-2730.69','1035.47','6.08244','4796:0 4798:0 '), +('4798','sp1020','571','67','4488','76','80','0','8268.8','-2840.55','1051.57','2.98168','4797:0 4796:0 '), +('4799','sp1021','571','67','4444','76','80','0','8178.4','-2765.96','1137.8','1.58369','4794:0 4800:0 '), +('4800','sp1022','571','67','4444','76','80','0','8192.81','-2569.46','1145.92','1.28524','4799:0 4801:0 4807:0 '), +('4801','sp1023','571','67','4446','76','80','0','8213.34','-2399.15','1136.06','1.39441','4800:0 4802:0 '), +('4802','sp1024','571','67','4446','76','80','0','8146.87','-2304.3','1157.9','3.14664','4801:0 4803:0 4804:0 '), +('4803','sp1025','571','67','4446','76','80','0','8041.79','-2401.8','1157.91','1.83659','4802:0 4804:0 4805:0 '), +('4804','sp1026','571','67','4446','76','80','0','7981.22','-2241.64','1157.87','2.17589','4803:0 4802:0 '), +('4805','sp1027','571','67','4446','76','80','0','7920.25','-2449.09','1137.97','1.81145','4803:0 4806:0 4809:0 '), +('4806','sp1028','571','67','4444','76','80','0','7924.11','-2571.76','1159.29','2.58976','4805:0 4807:0 '), +('4807','sp1029','571','67','4444','76','80','0','8046.59','-2635.79','1135.01','6.18452','4806:0 4800:0 4808:0 '), +('4808','sp1030','571','67','4463','76','80','0','7915.54','-2719.76','1135.12','3.80869','4807:0 4792:0 '), +('4809','sp1031','571','67','4446','76','80','0','7902.91','-2332.91','1153.05','3.63118','4805:0 4810:0 '), +('4810','sp1032','571','67','4442','76','80','0','7759.44','-2386.38','1077.61','2.84264','4809:0 4811:0 '), +('4811','sp1033','571','67','4442','76','80','0','7670.97','-2282.57','1010.94','2.45387','4810:0 4812:0 '), +('4812','sp1034','571','67','4442','76','80','0','7541.71','-2363.06','867.728','2.12164','4811:0 4813:0 '), +('4813','sp1035','571','67','4442','76','80','0','7465.67','-2485.65','760.441','0.00970542','4812:0 4814:0 4827:0 '), +('4814','sp1036','571','67','4438','76','80','0','7327.84','-2537.89','748.903','4.00974','4813:0 4815:0 4827:0 '), +('4815','sp1037','571','67','4438','76','80','0','7348.8','-2768.24','766.138','4.91922','4814:0 4816:0 '), +('4816','sp1038','571','67','4438','76','80','0','7348.82','-2958.97','838.738','4.70872','4815:0 4817:0 4826:0 '), +('4817','sp1039','571','67','4439','76','80','0','7229.61','-3136.68','837.486','4.08826','4816:0 4818:0 4826:0 '), +('4818','sp1040','571','67','4495','76','80','0','7202.51','-3334.74','843.826','4.6883','4817:0 4819:0 '), +('4819','sp1041','571','67','4439','76','80','0','7368.35','-3244.03','851.789','5.98892','4818:0 4820:0 '), +('4820','sp1042','571','67','4439','76','80','0','7555.15','-3237.78','842.017','0.160478','4819:0 4821:0 '), +('4821','sp1043','571','67','4440','76','80','0','7723.64','-3278.88','863.372','5.73209','4820:0 4822:0 '), +('4822','sp1044','571','67','4440','76','80','0','7843.39','-3412.02','865.974','6.25831','4821:0 4823:0 '), +('4823','sp1045','571','67','4440','76','80','0','8007.01','-3282.63','865.574','2.51589','4822:0 4824:0 '), +('4824','sp1046','571','67','4440','76','80','0','7854.16','-3160.4','864.749','3.02718','4823:0 4825:0 '), +('4825','sp1047','571','67','4440','76','80','0','7635.61','-3117.6','867.091','3.3971','4824:0 4826:0 '), +('4826','sp1048','571','67','4439','76','80','0','7437.01','-3083.87','837.452','2.68239','4825:0 4816:0 4817:0 '), +('4827','sp1049','571','67','4437','76','80','0','7293.97','-2399.5','753.5','6.25517','4814:0 4813:0 4828:0 '), +('4828','sp1050','571','67','4437','76','80','0','7210.78','-2258.23','756.519','2.1766','4827:0 4829:0 '), +('4829','sp1051','571','67','4437','76','80','0','7094.38','-2114.84','759.283','2.32896','4828:0 4830:0 '), +('4830','sp1052','571','67','4437','76','80','0','7071.57','-1957.32','769.29','1.71086','4829:0 4831:0 '), +('4831','sp1053','571','67','4422','76','80','0','7047.94','-1819.05','822.822','1.51843','4830:0 4832:0 '), +('4832','sp1054','571','67','4422','76','80','0','7016.16','-1682.12','819.647','1.56948','4831:0 4833:0 4834:0 4841:0 '), +('4833','sp1055','571','67','4422','76','80','0','6952.92','-1523.53','837.796','1.58755','4832:0 4834:0 4835:0 '), +('4834','sp1056','571','67','4422','76','80','0','6876.88','-1688.13','820.475','5.87424','4833:0 4832:0 '), +('4835','sp1057','571','67','4425','76','80','0','6933.96','-1355.59','831.143','1.62444','4833:0 4836:0 '), +('4836','sp1058','571','67','4425','76','80','0','6927.88','-1275.08','819.873','1.80979','4835:0 4837:0 '), +('4837','sp1059','571','67','4423','76','80','0','6891.54','-1120.83','801.801','2.04934','4836:0 4838:0 4840:0 '), +('4838','sp1060','571','67','4423','76','80','0','6874.8','-994.702','800.05','1.94645','4837:0 4839:0 '), +('4839','sp1061','571','67','4423','76','80','0','6713.98','-1032.73','766.307','5.25848','4838:0 4840:0 '), +('4840','sp1062','571','67','4423','76','80','0','6823.44','-1136.74','794.362','1.42024','4839:0 4837:0 '), +('4841','sp1063','571','67','4422','76','80','0','7059.17','-1565.01','830.127','1.02674','4832:0 4842:0 '), +('4842','sp1064','571','67','4432','76','80','1','7107.06','-1444.92','924.841','0.0175042','4841:0 4843:0 4854:0 '), +('4843','sp1065','571','67','4432','76','80','0','7224.43','-1369.92','917.332','1.04952','4842:0 4844:0 '), +('4844','sp1066','571','67','4432','76','80','0','7304.81','-1229.95','912.257','1.04952','4843:0 4845:0 '), +('4845','sp1067','571','67','4473','76','80','0','7277.74','-1094.09','938.898','4.08901','4844:0 4846:0 4847:0 '), +('4846','sp1068','571','67','4473','76','80','0','7159.16','-1203.13','929.255','3.39314','4845:0 '), +('4847','sp1069','571','67','4432','76','80','0','7276.68','-952.788','919.235','1.31654','4845:0 4848:0 '), +('4848','sp1070','571','67','4432','76','80','0','7442.11','-871.692','910.159','0.46203','4847:0 4849:0 '), +('4849','sp1071','571','67','4432','76','80','0','7617.15','-899.183','911.902','5.67158','4848:0 4850:0 '), +('4850','sp1072','571','67','4432','76','80','0','7731.32','-1040.44','920.428','5.0087','4849:0 4851:0 '), +('4851','sp1073','571','67','4432','76','80','0','7620.47','-1211.71','928.587','4.24765','4850:0 4852:0 '), +('4852','sp1074','571','67','4432','76','80','0','7523.4','-1365.25','935.317','3.55807','4851:0 4853:0 '), +('4853','sp1075','571','67','4432','76','80','0','7392.6','-1463.43','928.011','4.00967','4852:0 4854:0 '), +('4854','sp1076','571','67','4536','76','80','0','7307.54','-1573.81','942.399','2.34934','4853:0 4842:0 '), +('4855','cs1077','571','2817','2817','76','80','0','5850.58','-134.972','227.342','6.13811','4625:0 4856:0 '), +('4856','ic1078','571','210','4501','77','80','0','5971.83','-162.754','303.817','0.106256','4855:0 4857:0 '), +('4857','ic1079','571','210','4501','77','80','1','6141.12','-52.066','381.591','0.738502','4856:0 4858:0 '), +('4858','ic1080','571','210','4501','77','80','0','6194.51','34.1853','380.232','0.656035','4857:0 4859:0 4861:0 '), +('4859','ic1081','571','210','4504','77','80','0','6172.36','156.629','374.276','0.907363','4858:0 4860:0 '), +('4860','ic1082','571','210','4504','77','80','0','6379.33','179.484','392.956','0.48639','4859:0 4861:0 4862:0 '), +('4861','ic1083','571','210','4501','77','80','0','6302.14','95.8391','390.823','3.94214','4860:0 4858:0 '), +('4862','ic1084','571','210','4505','77','80','0','6510.94','285.541','400.181','0.224064','4860:0 4863:0 '), +('4863','ic1085','571','210','4505','77','80','0','6561.12','378.976','420.028','1.01575','4862:0 4864:0 4865:0 '), +('4864','ic1086','571','210','4506','77','80','0','6586.56','556.561','402.946','1.2011','4863:0 4865:0 4866:0 '), +('4865','ic1087','571','210','4506','77','80','0','6708.22','403.424','412.114','3.63504','4864:0 4863:0 4870:0 '), +('4866','ic1088','571','210','4506','77','80','0','6723.06','690.867','407.835','0.367783','4864:0 4867:0 4897:0 '), +('4867','ic1089','571','210','4506','77','80','0','6907.33','730.085','420.328','0.313591','4866:0 4868:0 4892:0 '), +('4868','ic1090','571','210','4506','77','80','0','6968.03','622.605','462.269','4.11884','4867:0 4869:0 4871:0 '), +('4869','ic1091','571','210','4506','77','80','0','6892.8','465.906','471.697','4.29869','4868:0 '), +('4870','ic1092','571','210','4506','77','80','0','6802.58','551.703','425.103','0.960751','4865:0 '), +('4871','ic1093','571','210','4506','77','80','0','7175.08','588.447','498.301','5.6747','4868:0 4872:0 4873:0 '), +('4872','ic1094','571','210','4506','77','80','0','7078.72','503.754','527.049','0.380329','4871:0 '), +('4873','ic1095','571','210','4593','77','80','0','7262.77','707.458','487.068','0.969396','4871:0 4874:0 '), +('4874','ic1096','571','210','210','77','80','0','7380.79','847.639','456.611','1.04794','4873:0 4875:0 '), +('4875','ic1097','571','210','4533','77','80','0','7556.82','768.81','478.284','6.18915','4874:0 4876:0 4877:0 '), +('4876','ic1098','571','210','4533','77','80','0','7584.83','947.602','480.255','1.08485','4875:0 4877:0 '), +('4877','ic1099','571','210','4533','77','80','0','7712.14','808.936','478.443','2.68156','4876:0 4875:0 4878:0 '), +('4878','ic1100','571','210','4533','77','80','0','7860.07','894.491','452.788','0.0928901','4877:0 4879:0 4888:0 4891:0 '), +('4879','ic1101','571','210','4533','77','80','0','8034.56','793.581','473.244','0.0151296','4878:0 4880:0 '), +('4880','ic1102','571','210','4658','77','80','0','8220.62','796.211','504.283','0.00650024','4879:0 4881:0 '), +('4881','ic1103','571','210','4658','77','80','0','8374.24','796.938','547.919','6.10983','4880:0 4882:0 4887:0 '), +('4882','ic1104','571','210','4669','77','80','0','8423.03','930.654','544.674','1.16574','4881:0 4883:0 '), +('4883','ic1105','571','210','4658','77','80','1','8522.2','1039.69','548.104','0.757338','4882:0 4884:0 '), +('4884','ic1106','571','210','4658','77','80','0','8609.95','905.31','545.094','5.03776','4883:0 4885:0 '), +('4885','ic1107','571','210','4658','77','80','3','8585.78','716.794','547.469','4.14555','4884:0 4886:0 '), +('4886','ic1108','571','210','4658','77','80','0','8489.61','581.224','559.708','4.17226','4885:0 4887:0 '), +('4887','ic1109','571','210','4658','77','80','5','8440.59','706.692','547.293','2.35563','4886:0 4881:0 '), +('4888','ic1110','571','210','4533','77','80','0','7765.83','995.585','447.266','2.83001','4878:0 4889:0 '), +('4889','ic1111','571','210','4533','77','80','0','7711.5','1167.13','440.596','0.808391','4888:0 4890:0 '), +('4890','ic1112','571','210','4533','77','80','0','7859.21','1231.56','441.972','0.590053','4889:0 4891:0 4921:0 '), +('4891','ic1113','571','210','4533','77','80','0','7895.54','1042.03','450.717','4.64349','4890:0 4878:0 '), +('4892','ic1114','571','210','4508','77','80','0','6955.49','874.331','402.675','1.29769','4867:0 4893:0 '), +('4893','ic1115','571','210','4508','77','80','0','7020.59','1065.06','402.857','1.32832','4892:0 4894:0 '), +('4894','ic1116','571','210','4508','77','80','0','6957.22','1339.68','398.602','1.97863','4893:0 4895:0 '), +('4895','ic1117','571','210','4508','77','80','0','6833.49','1460.28','390.712','1.81761','4894:0 4896:0 '), +('4896','ic1118','571','210','210','77','80','0','6826.51','1619.37','389.033','4.69297','4895:0 '), +('4897','ic1119','571','210','4507','77','80','0','6666.86','830.852','365.983','2.48285','4866:0 4898:0 '), +('4898','ic1120','571','210','4507','77','80','0','6599.05','917.605','308.63','2.05873','4897:0 4899:0 '), +('4899','ic1121','571','210','4507','77','80','0','6597.42','1025.81','280.105','1.11625','4898:0 4900:0 4904:0 '), +('4900','ic1122','571','210','4507','77','80','0','6559.27','1194.84','276.689','2.19853','4899:0 4901:0 '), +('4901','ic1123','571','210','4507','77','80','0','6688.4','1234.6','276.432','6.14281','4900:0 4902:0 '), +('4902','ic1124','571','210','4507','77','80','0','6863.13','1227.79','282.996','5.66451','4901:0 4903:0 4905:0 '), +('4903','ic1125','571','210','4507','77','80','0','6896.72','1042.62','305.673','4.53274','4902:0 4904:0 4905:0 '), +('4904','ic1126','571','210','4507','77','80','0','6726.14','1037','288.797','3.31459','4903:0 4899:0 '), +('4905','ic1127','571','210','4508','77','80','0','7001.55','1212.52','307.249','0.331647','4903:0 4902:0 4906:0 4907:0 '), +('4906','ic1128','571','210','4537','77','80','0','7135.37','1189.66','298.703','5.1862','4905:0 4915:0 '), +('4907','ic1129','571','210','4508','77','80','0','7048.99','1354.24','304.707','4.93802','4905:0 4908:0 '), +('4908','ic1130','571','210','4537','77','80','0','7186.05','1411.54','315.97','0.428269','4907:0 4909:0 5003:0 '), +('4909','ic1131','571','210','4537','77','80','0','7349.91','1448.05','320.323','0.242915','4908:0 4910:0 5003:0 '), +('4910','ic1132','571','210','4537','77','80','0','7474.88','1490.17','330.932','0.611267','4909:0 4911:0 '), +('4911','ic1133','571','210','4537','77','80','0','7573.73','1609.11','345.106','1.19325','4910:0 4912:0 4916:0 '), +('4912','ic1134','571','210','4537','77','80','0','7721.75','1522.15','349.093','5.66688','4911:0 4913:0 4920:0 '), +('4913','ic1135','571','210','4537','77','80','0','7720.3','1336.02','354.115','4.43774','4912:0 4914:0 4922:0 '), +('4914','ic1136','571','210','4537','77','80','0','7534.59','1265.76','334.056','2.73578','4913:0 4915:0 '), +('4915','ic1137','571','210','4537','77','80','0','7314','1220.63','315.76','3.35624','4914:0 4906:0 '), +('4916','ic1138','571','210','4509','77','80','0','7577.1','1786.78','359.583','1.47521','4911:0 4917:0 '), +('4917','ic1139','571','210','4509','77','80','0','7669.29','1945.97','367.174','1.15084','4916:0 4918:0 4927:0 '), +('4918','ic1140','571','210','4509','77','80','0','7873.9','1929.78','365.964','6.20016','4917:0 4919:0 4923:0 '), +('4919','ic1141','571','210','4509','77','80','0','7887.76','1725.17','368.82','4.09059','4918:0 4920:0 '), +('4920','ic1142','571','210','4509','77','80','0','7765','1638.88','350.579','4.75974','4919:0 4912:0 '), +('4921','ic1143','571','210','210','77','80','0','7920.44','1374.41','452.53','1.61423','4890:0 4922:0 '), +('4922','ic1144','571','210','4537','77','80','0','7822.59','1365.97','388.481','3.40023','4921:0 4913:0 '), +('4923','ic1145','571','210','4510','77','80','0','7809.24','2060.92','392.033','1.58203','4918:0 4924:0 '), +('4924','ic1146','571','210','4540','77','80','0','7858.11','2248.57','376.618','1.4603','4923:0 4925:0 '), +('4925','ic1147','571','210','4540','77','80','0','7823.2','2412.9','388.323','1.80744','4924:0 4926:0 '), +('4926','ic1148','571','210','4540','77','80','0','7793.72','2597.47','402.105','1.67392','4925:0 4938:0 '), +('4927','ic1149','571','210','4510','77','80','0','7699.75','2069.29','391.653','1.65822','4917:0 4928:0 '), +('4928','ic1150','571','210','4540','77','80','0','7652.21','2249.08','368.676','2.35408','4927:0 4929:0 '), +('4929','ic1151','571','210','4540','77','80','0','7521.07','2342.93','375.359','2.52294','4928:0 4930:0 '), +('4930','ic1152','571','210','4523','77','80','0','7369.4','2442.13','391.045','2.44441','4929:0 4931:0 4939:0 '), +('4931','ic1153','571','210','4523','77','80','0','7198.45','2497.03','404.398','2.97341','4930:0 4932:0 4939:0 '), +('4932','ic1154','571','210','4523','77','80','0','7035.47','2497.06','410.594','3.37789','4931:0 4933:0 '), +('4933','ic1155','571','210','4523','77','80','0','7014.72','2665.38','400.29','1.87621','4932:0 4934:0 '), +('4934','ic1156','571','210','4523','77','80','0','7013.48','2866.37','424.885','1.59347','4933:0 4935:0 '), +('4935','ic1157','571','210','4523','77','80','0','7165.04','2990.37','441.491','6.14878','4934:0 4936:0 '), +('4936','ic1158','571','210','4523','77','80','0','7332.14','2917.95','423.697','5.78906','4935:0 4937:0 4939:0 4940:0 '), +('4937','ic1159','571','210','4540','77','80','0','7502.18','2817.76','419.764','5.68225','4936:0 4938:0 4939:0 '), +('4938','ic1160','571','210','4516','77','80','0','7710.23','2726.49','412.219','5.21495','4937:0 4926:0 5009:0 '), +('4939','ic1161','571','210','4523','77','80','0','7307.93','2711.18','396.859','4.63376','4937:0 4930:0 4931:0 4936:0 '), +('4940','ic1162','571','210','210','77','80','0','7358.64','3080.68','476.932','1.77884','4936:0 4941:0 '), +('4941','ic1163','571','210','210','77','80','0','7199','3207.31','545.531','1.97125','4940:0 4942:0 '), +('4942','ic1164','571','210','210','77','80','0','7139.56','3362.03','669.633','2.10878','4941:0 4943:0 '), +('4943','ic1165','571','210','4531','77','80','0','6985.22','3466.47','708.57','2.8337','4942:0 4944:0 4974:0 '), +('4944','ic1166','571','210','4531','77','80','0','6890.27','3530.05','708.606','5.39253','4943:0 4945:0 '), +('4945','ic1167','571','210','4531','77','80','0','6830.55','3590.9','740.056','5.22131','4944:0 4946:0 '), +('4946','ic1168','571','210','4496','77','80','0','6868.76','3731.57','755.294','4.54431','4945:0 4947:0 '), +('4947','ic1169','571','210','4496','77','80','0','7002.18','3880.5','610.484','1.63677','4946:0 4948:0 4949:0 4951:0 '), +('4948','ic1170','571','210','4496','77','80','0','6831.56','3987.86','615.968','2.45046','4947:0 4949:0 '), +('4949','ic1171','571','210','4496','77','80','0','6897.56','3919.65','615.975','4.57026','4948:0 4947:0 4950:0 '), +('4950','ic1172','571','210','4528','77','80','0','6814.18','3788.63','621.07','1.05954','4949:0 '), +('4951','ic1173','571','210','4496','77','80','0','7138.52','3955.81','581.785','5.65333','4947:0 4952:0 5020:0 5022:0 '), +('4952','ic1174','571','210','4498','77','80','0','7284.9','3836.51','604.369','5.59993','4951:0 4953:0 '), +('4953','ic1175','571','210','4498','77','80','0','7410.57','3732.09','627.678','5.65962','4952:0 4954:0 '), +('4954','ic1176','571','210','4526','77','80','0','7512.15','3660.34','623.332','6.01697','4953:0 4955:0 4973:0 '), +('4955','ic1177','571','210','4526','77','80','0','7593.36','3544.42','655.937','5.07214','4954:0 4956:0 '), +('4956','ic1178','571','210','4526','77','80','0','7671','3432.93','656.192','2.35151','4955:0 4957:0 '), +('4957','ic1179','571','210','4526','77','80','0','7795.77','3500.91','658.351','0.992774','4956:0 4958:0 4959:0 4971:0 '), +('4958','ic1180','571','210','4526','77','80','0','7883.18','3469.31','671.838','6.04917','4957:0 '), +('4959','ic1181','571','210','4526','77','80','0','7919.33','3582.64','631.477','0.58594','4957:0 4960:0 '), +('4960','ic1182','571','210','4524','77','80','0','8041.43','3526.53','654.716','6.08922','4959:0 4961:0 4968:0 '), +('4961','ic1183','571','210','4524','77','80','0','8187.82','3610.43','658.019','0.290628','4960:0 4962:0 '), +('4962','ic1184','571','210','4524','77','80','0','8317.43','3535.48','657.912','4.87265','4961:0 4963:0 '), +('4963','ic1185','571','210','4524','77','80','0','8248.7','3338.48','654.291','5.09882','4962:0 4964:0 4968:0 '), +('4964','ic1186','571','210','4496','77','80','0','8311.26','3170.17','618.951','5.16165','4963:0 4965:0 '), +('4965','ic1187','571','210','4497','77','80','0','8430.96','3102.38','588.141','4.91819','4964:0 4966:0 '), +('4966','ic1188','571','210','4492','77','80','0','8426.9','2947.18','602.293','4.77996','4965:0 4967:0 '), +('4967','ic1189','571','210','4492','77','80','0','8429.04','2879.47','606.259','1.76717','4966:0 '), +('4968','ic1190','571','210','4524','77','80','0','8105.02','3418.73','672.247','3.83984','4960:0 4963:0 4969:0 '), +('4969','ic1191','571','210','4525','77','80','0','7990.63','3300.15','676.112','3.81628','4968:0 4970:0 '), +('4970','ic1192','571','210','4525','77','80','0','7944.78','3251.88','632.818','3.79665','4969:0 '), +('4971','ic1193','571','210','4526','77','80','0','7848.54','3632.5','653.393','4.16107','4957:0 4972:0 '), +('4972','ic1194','571','210','4526','77','80','0','7717.96','3673.53','653.297','3.444','4971:0 4973:0 '), +('4973','ic1195','571','210','4526','77','80','0','7617.43','3670.12','634.89','3.02774','4972:0 4954:0 '), +('4974','ic1196','571','210','4531','77','80','0','6853.09','3478.09','692.779','3.04894','4943:0 4975:0 4976:0 4977:0 '), +('4975','ic1197','571','210','4531','77','80','0','6714.63','3539.45','668.964','2.71907','4974:0 4977:0 '), +('4976','ic1198','571','210','4531','77','80','0','6719.14','3429.9','682.197','3.43144','4974:0 4977:0 5024:0 '), +('4977','ic1199','571','210','4531','77','80','0','6774.33','3483.53','672.159','3.05053','4974:0 4976:0 4975:0 4978:0 '), +('4978','ic1200','571','210','4531','77','80','0','6549.04','3449.66','598.005','3.34505','4977:0 4979:0 '), +('4979','ic1201','571','210','4531','77','80','0','6461.08','3286.15','631.506','5.01716','4978:0 4980:0 '), +('4980','ic1202','571','210','4531','77','80','0','6452.51','3119.45','657.481','1.45066','4979:0 4981:0 '), +('4981','ic1203','571','210','4518','77','80','0','6526.9','3000.14','651.117','4.87342','4980:0 4982:0 '), +('4982','ic1204','571','210','4518','77','80','0','6547.7','2810.28','651.117','4.72026','4981:0 4983:0 '), +('4983','ic1205','571','210','4518','77','80','0','6524.74','2581.76','651.117','4.51292','4982:0 4984:0 '), +('4984','ic1206','571','210','4518','77','80','0','6630.74','2328.4','651.585','5.06506','4983:0 4985:0 '), +('4985','ic1207','571','210','4518','77','80','0','6619.16','2134.02','651.118','4.86635','4984:0 4986:0 '), +('4986','ic1208','571','210','4520','77','80','0','6609.61','1940.41','652.242','3.77073','4985:0 4987:0 4990:0 '), +('4987','ic1209','571','210','4520','77','80','0','6436.2','1960.49','631.376','3.19739','4986:0 4988:0 '), +('4988','ic1210','571','210','4520','77','80','0','6238.67','1930.65','631.948','3.56731','4987:0 4989:0 '), +('4989','ic1211','571','210','4520','77','80','0','6075.08','1919.83','632.648','3.19032','4988:0 '), +('4990','ic1212','571','210','4520','77','80','0','6546.79','1812.75','629.551','4.65274','4986:0 4991:0 4992:0 '), +('4991','ic1213','571','210','4520','77','80','0','6556.05','1624.57','633.228','4.76113','4990:0 4992:0 '), +('4992','ic1214','571','210','4520','77','80','0','6590.03','1684.23','628.876','3.70395','4991:0 4990:0 4993:0 '), +('4993','ic1215','571','210','4513','77','80','0','6728.5','1651.46','572.96','1.13099','4992:0 4994:0 '), +('4994','ic1216','571','210','4513','77','80','0','6809.4','1726.92','556.264','3.80763','4993:0 4995:0 4996:0 '), +('4995','ic1217','571','210','4513','77','80','0','6812.51','1821.36','578.354','4.77131','4994:0 '), +('4996','ic1218','571','210','4513','77','80','0','6952.4','1697.92','518.536','2.9209','4994:0 4997:0 '), +('4997','ic1219','571','210','4513','77','80','0','7031.52','1779.42','506.019','1.12469','4996:0 4998:0 5000:0 '), +('4998','ic1220','571','210','4514','77','80','0','7025.43','1902.07','528.077','5.0187','4997:0 4999:0 '), +('4999','ic1221','571','210','4514','77','80','0','6960.52','2014.43','520.864','4.66919','4998:0 '), +('5000','ic1222','571','210','4513','77','80','0','7201.98','1696.05','469.261','2.30668','4997:0 5001:0 5004:0 5005:0 '), +('5001','ic1223','571','210','210','77','80','0','7348.32','1642.76','429.984','1.78832','5000:0 5002:0 '), +('5002','ic1224','571','210','210','77','80','0','7214.66','1585.03','379.979','5.44591','5001:0 5003:0 '), +('5003','ic1225','571','210','4537','77','80','0','7281.16','1492.2','326.379','1.64772','5002:0 4909:0 4908:0 5004:0 '), +('5004','ic1226','571','210','210','77','80','0','7331.27','1765.54','456.844','3.80914','5003:0 5000:0 '), +('5005','ic1227','571','210','4513','77','80','0','7136.03','1876.5','532.96','4.77911','5000:0 5006:0 '), +('5006','ic1228','571','210','4513','77','80','0','7224.2','1973.83','570.931','1.27072','5005:0 5007:0 5008:0 '), +('5007','ic1229','571','210','4513','77','80','1','7242.67','2168.33','565.646','4.69493','5006:0 '), +('5008','ic1230','571','210','4513','77','80','0','7100.09','2122.12','621.203','4.3801','5006:0 '), +('5009','ic1231','571','210','4540','77','80','0','7682.15','2851.03','469.055','2.77003','4938:0 5010:0 '), +('5010','ic1232','571','210','4517','77','80','0','7685.31','3002.19','541.332','0.921987','5009:0 5011:0 '), +('5011','ic1233','571','210','4517','77','80','0','7702.82','3078.43','557.351','5.63045','5010:0 5012:0 5019:0 '), +('5012','ic1234','571','210','4517','77','80','0','7843.82','2935.68','517.933','0.541764','5011:0 5013:0 '), +('5013','ic1235','571','210','4517','77','80','0','7934.47','2994.97','541.057','3.56948','5012:0 5014:0 '), +('5014','ic1236','571','210','4517','77','80','0','8021.28','2971.58','563.405','2.3317','5013:0 5015:0 '), +('5015','ic1237','571','210','4517','77','80','0','8106.95','2852.61','559.292','5.73169','5014:0 5016:0 5025:0 '), +('5016','ic1238','571','210','4477','77','80','0','8243','2776.54','640.775','6.08434','5015:0 5017:0 '), +('5017','ic1239','571','210','4477','77','80','0','8383.84','2730.79','655.094','5.79375','5016:0 5018:0 '), +('5018','ic1240','571','210','4477','77','80','1','8508.77','2673.5','652.354','5.83302','5017:0 '), +('5019','ic1241','571','210','4517','77','80','0','7575.23','3170.11','577.394','5.58719','5011:0 '), +('5020','ic1242','571','210','4496','77','80','0','6971.39','3981.75','552.612','5.94612','4951:0 5021:0 '), +('5021','ic1243','571','210','4496','77','80','0','6934.59','4194.33','535.202','1.79686','5020:0 '), +('5022','ic1244','571','210','4496','77','80','0','7162.84','4153.53','633.939','2.91213','4951:0 5023:0 '), +('5023','ic1245','571','210','4496','77','80','0','7019.95','4225.08','665.892','2.06625','5022:0 '), +('5024','ic1246','571','210','4531','77','80','0','6545.08','3321.6','665.004','4.0596','4976:0 '), +('5025','ic1247','571','210','210','77','80','0','8061.74','2755.35','542.008','4.48453','5015:0 5026:0 '), +('5026','ic1248','571','210','210','77','80','0','8077.07','2597.13','519.549','4.54375','5025:0 5027:0 '), +('5027','ic1249','571','210','4510','77','80','0','8103.98','2410.6','490.587','4.97494','5026:0 5028:0 '), +('5028','ic1250','571','210','4510','77','80','0','8060.18','2339.25','484.62','3.25413','5027:0 5029:0 '), +('5029','ic1251','571','210','4510','77','80','0','7982.84','2214.78','500.312','4.5092','5028:0 5030:0 5031:0 '), +('5030','ic1252','571','210','4510','77','80','0','8163.01','2189.09','499.737','3.33032','5029:0 '), +('5031','ic1253','571','210','4510','77','80','0','7986.6','2067','499.729','3.00752','5029:0 5032:0 5035:0 '), +('5032','ic1254','571','210','4510','77','80','0','8120.19','1962.29','501.671','5.88521','5031:0 5033:0 '), +('5033','ic1255','571','210','4510','77','80','0','8204.78','1874.81','514.771','1.36602','5032:0 5034:0 '), +('5034','ic1256','571','210','4510','77','80','0','8179.12','2049.72','550.596','2.65329','5033:0 '), +('5035','ic1257','571','210','4510','77','80','0','7751.59','2060.95','499.864','3.22507','5031:0 5036:0 '), +('5036','ic1258','571','210','4510','77','80','0','7549.16','2067.17','500.312','3.28947','5035:0 5037:0 5038:0 '), +('5037','ic1259','571','210','4510','77','80','0','7438.58','2146.13','500.312','2.65644','5036:0 '), +('5038','ic1260','571','210','4510','77','80','0','7492.17','1991.36','500.301','0.996881','5036:0 '); diff --git a/sql/Bots/updates/world/2023_06_16_00_creature_template_npcbot_wander_nodes.sql b/sql/Bots/updates/world/2023_06_16_00_creature_template_npcbot_wander_nodes.sql new file mode 100644 index 000000000..10d6ae33b --- /dev/null +++ b/sql/Bots/updates/world/2023_06_16_00_creature_template_npcbot_wander_nodes.sql @@ -0,0 +1,9 @@ +-- +UPDATE `creature_template_npcbot_wander_nodes` SET `flags`='4' WHERE (`id`='28'); -- Razor Hill: make Horde-only +-- Durotar canyon (Drygulch Ravine) +UPDATE `creature_template_npcbot_wander_nodes` SET `flags`='0' WHERE `id` IN ('66','1372','1373','1374','1375','1376','1377','1378','1380','1381','1382','1383','1384','1385','1401'); +UPDATE `creature_template_npcbot_wander_nodes` SET `flags`='1' WHERE (`id`='419'); -- RefugePointeExitS: allow neutral spawns +UPDATE `creature_template_npcbot_wander_nodes` SET `flags`='1' WHERE (`id`='505'); -- LochS: allow neutral spawns +UPDATE `creature_template_npcbot_wander_nodes` SET `flags`='1' WHERE (`id`='1071'); -- AshenvaleC_hub11: allow neutral spawns +UPDATE `creature_template_npcbot_wander_nodes` SET `flags`='1' WHERE (`id`='1379'); -- DurotarCanyon1: allow neutral spawns +UPDATE `creature_template_npcbot_wander_nodes` SET `flags`='1' WHERE (`id`='1739'); -- DesolaceN34: allow neutral spawns diff --git a/sql/Bots/updates/world/2023_06_18_00_command.sql b/sql/Bots/updates/world/2023_06_18_00_command.sql new file mode 100644 index 000000000..6f82bafde --- /dev/null +++ b/sql/Bots/updates/world/2023_06_18_00_command.sql @@ -0,0 +1,32 @@ +-- +INSERT IGNORE INTO `command` (`name`) VALUES +('npcbot command follow only'), +('npcbot command nocast'), +('npcbot command nolongcast'), +('npcbot debug guids'), +('npcbot debug model'), +('npcbot debug mount'), +('npcbot debug names'), +('npcbot debug raid'), +('npcbot debug spellvisual'), +('npcbot debug states'), +('npcbot debug wbequips'), +('npcbot debug spells'), +('npcbot gs'), +('npcbot useonbot item'), +('npcbot useonbot spell'), +('npcbot wp add'), +('npcbot wp go'), +('npcbot wp info'), +('npcbot wp links'), +('npcbot wp list'), +('npcbot wp list all'), +('npcbot wp move'), +('npcbot wp setflags'), +('npcbot wp setflags z'), +('npcbot wp setlevels'), +('npcbot wp setlevels z'), +('npcbot wp setlinks'), +('npcbot wp del'), +('npcbot wp setname'), +('npcbot wp spawnall'); diff --git a/sql/Bots/updates/world/2023_06_18_01_creature_template_npcbot_disabled_items.sql b/sql/Bots/updates/world/2023_06_18_01_creature_template_npcbot_disabled_items.sql new file mode 100644 index 000000000..84347a56e --- /dev/null +++ b/sql/Bots/updates/world/2023_06_18_01_creature_template_npcbot_disabled_items.sql @@ -0,0 +1,6 @@ +-- +DROP TABLE IF EXISTS `creature_template_npcbot_disabled_items`; +CREATE TABLE `creature_template_npcbot_disabled_items` ( + `id` int(10) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; diff --git a/sql/Bots/updates/world/2023_06_19_00_creature_template_npcbot_disabled_items.sql b/sql/Bots/updates/world/2023_06_19_00_creature_template_npcbot_disabled_items.sql new file mode 100644 index 000000000..5c4f4dcef --- /dev/null +++ b/sql/Bots/updates/world/2023_06_19_00_creature_template_npcbot_disabled_items.sql @@ -0,0 +1,53 @@ +-- +INSERT IGNORE INTO `creature_template_npcbot_disabled_items` (`id`) VALUES +('77'), +('1046'), +('1047'), +('1170'), +('1174'), +('1354'), +('1719'), +('2811'), +('2812'), +('2814'), +('3068'), +('3271'), +('3333'), +('3436'), +('3883'), +('3885'), +('3886'), +('3887'), +('3888'), +('3933'), +('3935'), +('4193'), +('4616'), +('4657'), +('4664'), +('4667'), +('4670'), +('4673'), +('4902'), +('4934'), +('4950'), +('4955'), +('4956'), +('5040'), +('5294'), +('5295'), +('5296'), +('5297'), +('5298'), +('5607'), +('12755'), +('13242'), +('14363'), +('14597'), +('14609'), +('14691'), +('14696'), +('15888'), +('15889'), +('22391'), +('23553'); diff --git a/sql/Bots/updates/world/2023_06_19_01_creature_template.sql b/sql/Bots/updates/world/2023_06_19_01_creature_template.sql new file mode 100644 index 000000000..c174021ef --- /dev/null +++ b/sql/Bots/updates/world/2023_06_19_01_creature_template.sql @@ -0,0 +1,2 @@ +-- +UPDATE `creature_template` SET `speed_walk` = 1.0, `speed_run` = 1.0 WHERE `entry` > 70000 AND (`flags_extra`&0x8E000000) = 0x8E000000 AND `speed_run` > 1.0; diff --git a/sql/Bots/updates/world/2023_06_22_00_command.sql b/sql/Bots/updates/world/2023_06_22_00_command.sql new file mode 100644 index 000000000..202c3e680 --- /dev/null +++ b/sql/Bots/updates/world/2023_06_22_00_command.sql @@ -0,0 +1,2 @@ +-- +DELETE FROM `command` WHERE `name` LIKE 'npcbot debug %'; diff --git a/sql/Bots/updates/world/2023_06_24_00_creature_template_npcbot_wander_nodes.sql b/sql/Bots/updates/world/2023_06_24_00_creature_template_npcbot_wander_nodes.sql new file mode 100644 index 000000000..263d7e5ed --- /dev/null +++ b/sql/Bots/updates/world/2023_06_24_00_creature_template_npcbot_wander_nodes.sql @@ -0,0 +1,55 @@ +-- +SET @WP_START = 5039; +SET @WP_END = 5087; + +DELETE FROM `creature_template_npcbot_wander_nodes` WHERE `id` BETWEEN @WP_START AND @WP_END; +INSERT INTO `creature_template_npcbot_wander_nodes`(`id`,`name`,`mapid`,`zoneid`,`areaid`,`minlevel`,`maxlevel`,`flags`,`x`,`y`,`z`,`o`,`links`) VALUES +('5039', 'AlteracValleySpawnA', '30', '2597', '2597', '50', '80', '3', '801.027', '-493.883', '99.6917', '3.08066', '5040:0 '), +('5040', 'AlteracValleyStartA1', '30', '2597', '3299', '50', '80', '2', '717.929', '-475.351', '75.1276', '2.10478', '5041:0 5079:0 '), +('5041', 'AlteracValleyStartA2', '30', '2597', '3299', '50', '80', '34', '664.972', '-400.377', '68.7491', '2.43269', '5042:0 5077:0 '), +('5042', 'AlteracValleyStartA3', '30', '2597', '3299', '50', '80', '2', '610.827', '-388.879', '66.5562', '3.30843', '5043:0 '), +('5043', 'AlteracValleyStartA4', '30', '2597', '2597', '50', '80', '2', '463.128', '-438.201', '33.0107', '2.75865', '5044:0 '), +('5044', 'AlteracValleyHubA1', '30', '2597', '2597', '50', '80', '0', '401.785', '-393.695', '-1.05733', '2.60355', '5045:0 5084:0 '), +('5045', 'AlteracValleyBunker2', '30', '2597', '3304', '50', '80', '0', '221.727', '-412.006', '41.1425', '2.14206', '5046:0 5047:0 5044:0 '), +('5046', 'AlteracValleyBunker2Inside', '30', '2597', '3304', '50', '80', '146', '204.453', '-359.47', '56.3831', '2.20687', '5045:0 '), +('5047', 'AlteracValleyGYA1', '30', '2597', '3302', '50', '80', '128', '76.8322', '-399.172', '45.7006', '1.69996', '5045:0 5048:0 5049:0 5050:0 '), +('5048', 'AlteracValleyBelinda', '30', '2597', '2958', '50', '80', '0', '-36.1268', '-290.741', '15.0786', '3.0354', '5047:0 '), +('5049', 'AlteracValleyCN', '30', '2597', '2597', '50', '80', '0', '-133.7', '-368.866', '8.47712', '6.00431', '5047:0 5052:0 5087:0 '), +('5050', 'AlteracValleyBunker1Inside', '30', '2597', '3305', '50', '80', '146', '-151.384', '-442.049', '40.3942', '1.08956', '5047:0 '), +('5051', 'AlteracValleyGYC', '30', '2597', '3301', '50', '80', '128', '-200.494', '-109.396', '78.5207', '4.95373', '5087:0 5086:0 '), +('5052', 'AlteracValleyCS1', '30', '2597', '3057', '50', '80', '0', '-262.759', '-292.49', '6.80088', '5.73925', '5049:0 5053:0 '), +('5053', 'AlteracValleyCS2', '30', '2597', '3057', '50', '80', '0', '-473.131', '-273.873', '24.7318', '3.74628', '5052:0 5054:0 5086:0 '), +('5054', 'AlteracValleyCS3', '30', '2597', '2597', '50', '80', '32', '-525.235', '-353.849', '37.0353', '4.37461', '5055:0 5053:0 '), +('5055', 'AlteracValleyCS4', '30', '2597', '2597', '50', '80', '0', '-601.467', '-320.914', '51.7167', '6.08664', '5054:0 5056:0 '), +('5056', 'AlteracValleyGYH1', '30', '2597', '2597', '50', '80', '128', '-615.967', '-397.593', '60.6924', '0.333583', '5055:0 5057:0 5058:0 5059:0 '), +('5057', 'AlteracValleyTower1Inside', '30', '2597', '2977', '50', '80', '148', '-569.205', '-264.746', '75.0088', '2.58769', '5056:0 '), +('5058', 'AlteracValleyGalvangar', '30', '2597', '2977', '50', '80', '0', '-536.858', '-168.875', '57.0098', '2.75262', '5056:0 '), +('5059', 'AlteracValleyTower2', '30', '2597', '2962', '50', '80', '0', '-715.985', '-350.671', '66.8321', '2.63482', '5056:0 5060:0 5061:0 '), +('5060', 'AlteracValleyTower2Inside', '30', '2597', '2962', '50', '80', '148', '-767.615', '-361.296', '90.8949', '4.27041', '5059:0 '), +('5061', 'AlteracValleyGYH2', '30', '2597', '2597', '50', '80', '128', '-1083.95', '-341.705', '55.3047', '0.111719', '5059:0 5064:0 5082:0 '), +('5062', 'AlteracValleyStartH1', '30', '2597', '2597', '50', '80', '4', '-1144.38', '-442.064', '56.7427', '0.904973', '5061:0 '), +('5063', 'AlteracValleySpawnH', '30', '2597', '2597', '50', '80', '5', '-1386.53', '-549.064', '55.0284', '0.628112', '5062:0 '), +('5064', 'AlteracValleyBaseH1', '30', '2597', '2961', '50', '80', '0', '-1241.61', '-363.466', '59.6773', '2.10074', '5061:0 5065:0 '), +('5065', 'AlteracValleyBaseH2', '30', '2597', '2961', '50', '80', '0', '-1210.4', '-252.908', '72.7007', '3.07856', '5064:0 5066:0 '), +('5066', 'AlteracValleyBaseH3', '30', '2597', '2978', '50', '80', '0', '-1286.08', '-289.551', '89.091', '3.10996', '5065:0 5067:0 '), +('5067', 'AlteracValleyGYH3', '30', '2597', '2978', '50', '80', '128', '-1404.13', '-312.526', '89.4075', '1.4783', '5066:0 5068:0 5069:0 5070:0 '), +('5068', 'AlteracValleyTower3Inside', '30', '2597', '2978', '50', '80', '148', '-1301.34', '-267.703', '114.151', '0.219693', '5067:0 '), +('5069', 'AlteracValleyTower4Inside', '30', '2597', '2978', '50', '80', '148', '-1304.35', '-313.854', '113.867', '5.07738', '5067:0 '), +('5070', 'AlteracValleyDrekthar', '30', '2597', '4407', '50', '80', '276', '-1367.71', '-227.007', '98.4255', '2.09366', '5067:0 '), +('5071', 'AlteracValleyGYA2', '30', '2597', '3303', '50', '80', '128', '664.375', '-295.432', '30.2906', '1.6334', '5072:0 5080:0 5084:0 '), +('5072', 'AlteracValleyBaseA1', '30', '2597', '2959', '50', '80', '0', '628.165', '-209.132', '39.032', '1.71587', '5073:0 5071:0 '), +('5073', 'AlteracValleyGYA3', '30', '2597', '2959', '50', '80', '128', '635.165', '-28.286', '46.6157', '4.25874', '5072:0 5074:0 5075:0 5076:0 '), +('5074', 'AlteracValleyBunker3Inside', '30', '2597', '2959', '50', '80', '146', '672.521', '-142.475', '63.6551', '4.09365', '5073:0 '), +('5075', 'AlteracValleyBunker4Inside', '30', '2597', '2959', '50', '80', '146', '555.611', '-77.7777', '51.9336', '1.81992', '5073:0 '), +('5076', 'AlteracValleyVanndar', '30', '2597', '4408', '50', '80', '274', '712.208', '-13.5129', '50.1354', '0.288398', '5073:0 '), +('5077', 'AlteracValleyShortcutA1', '30', '2597', '3303', '50', '80', '34', '668.321', '-376.13', '30.4499', '1.65697', '5071:0 '), +('5078', 'AlteracValleyShortcutH1', '30', '2597', '2597', '50', '80', '36', '-550.621', '-356.862', '50.2404', '5.33475', '5054:0 '), +('5079', 'AlteracValleyMineA1', '30', '2597', '2957', '50', '80', '34', '774.881', '-363.728', '78.8805', '1.75712', '5080:0 '), +('5080', 'AlteracValleyMineA2', '30', '2597', '2957', '50', '80', '32', '776.68', '-313.432', '54.4907', '0.0135369', '5081:0 5071:0 '), +('5081', 'AlteracValleyMineA3', '30', '2597', '2957', '50', '80', '512', '870.508', '-436.928', '51.1438', '5.59377', '5080:0 '), +('5082', 'AlteracValleyMineH1', '30', '2597', '2963', '50', '80', '0', '-964.106', '-216.1', '69.4372', '0.822477', '5061:0 5083:0 '), +('5083', 'AlteracValleyMineH2', '30', '2597', '2963', '50', '80', '512', '-857.468', '-102.82', '65.0282', '0.681106', '5082:0 '), +('5084', 'AlteracValleyAuxA1', '30', '2597', '2597', '50', '80', '0', '517.454', '-327.563', '-1.03301', '0.235552', '5044:0 5071:0 '), +('5085', 'AlteracValleyIcebloodGY', '30', '2597', '3300', '50', '80', '0', '-531.167', '-405.06', '49.5441', '2.83719', '5056:0 5078:0 '), +('5086', 'AlteracValleyGYCS', '30', '2597', '3057', '50', '80', '0', '-296.163', '-118.723', '18.4795', '0.58704', '5051:0 5053:0 '), +('5087', 'AlteracValleyGYCN', '30', '2597', '2597', '50', '80', '0', '-154.952', '-231.949', '10.0454', '1.75925', '5049:0 5051:0 '); diff --git a/sql/Bots/updates/world/2023_06_26_00_creature_template_npcbot_wander_nodes.sql b/sql/Bots/updates/world/2023_06_26_00_creature_template_npcbot_wander_nodes.sql new file mode 100644 index 000000000..172d35860 --- /dev/null +++ b/sql/Bots/updates/world/2023_06_26_00_creature_template_npcbot_wander_nodes.sql @@ -0,0 +1,5 @@ +-- +DELETE FROM `creature_template_npcbot_wander_nodes` WHERE `id` IN (2396,2406); +INSERT INTO `creature_template_npcbot_wander_nodes`(`id`,`name`,`mapid`,`zoneid`,`areaid`,`minlevel`,`maxlevel`,`flags`,`x`,`y`,`z`,`o`,`links`) VALUES +('2396','ArathiBasinSpawnA','529','3358','3417','20','80','3','1289.51','1286.41','-14.466','3.89804','2397:0 '), +('2406','ArathiBasinSpawnH','529','3358','3418','20','80','5','704.074','704.457','-16.4305','0.79572','2407:0 '); diff --git a/sql/Bots/updates/world/2023_07_01_00_creature_template_npcbot_disabled_items.sql b/sql/Bots/updates/world/2023_07_01_00_creature_template_npcbot_disabled_items.sql new file mode 100644 index 000000000..d7a4b3996 --- /dev/null +++ b/sql/Bots/updates/world/2023_07_01_00_creature_template_npcbot_disabled_items.sql @@ -0,0 +1,11 @@ +-- Test items: 'LK ARENA ...', 'LK Arena ...', 'LK Honor ...' (SELECT entry,name FROM item_template WHERE name LIKE 'LK %';) +INSERT IGNORE INTO `creature_template_npcbot_disabled_items` (`id`) VALUES +('42000'), +('42019'), +('41995'), +('42007'), +('42013'), +('40650'), +('41900'), +('41911'), +('42083'); diff --git a/sql/Bots/updates/world/2023_08_13_00_creature_template_npcbot_appearance.sql b/sql/Bots/updates/world/2023_08_13_00_creature_template_npcbot_appearance.sql new file mode 100644 index 000000000..b50d728b3 --- /dev/null +++ b/sql/Bots/updates/world/2023_08_13_00_creature_template_npcbot_appearance.sql @@ -0,0 +1,2 @@ +-- +ALTER TABLE `creature_template_npcbot_appearance` MODIFY COLUMN `name*` char(100) DEFAULT 'unk' COMMENT 'unused', CHARSET=utf8mb4; diff --git a/sql/Bots/updates/world/2024_03_12_00_npc_text.sql b/sql/Bots/updates/world/2024_03_12_00_npc_text.sql new file mode 100644 index 000000000..beac65429 --- /dev/null +++ b/sql/Bots/updates/world/2024_03_12_00_npc_text.sql @@ -0,0 +1,7 @@ +-- +SET @LOCALIZED_STRINGS_START = 70673; +SET @LOCALIZED_STRINGS_END = 70673; + +DELETE FROM `npc_text` WHERE ID BETWEEN @LOCALIZED_STRINGS_START and @LOCALIZED_STRINGS_END; +INSERT INTO `npc_text` (`ID`,`text0_0`,`VerifiedBuild`) VALUES +(70673,'Heal target health threshold','-1'); diff --git a/sql/Bots/updates/world/2024_03_18_00_npc_text.sql b/sql/Bots/updates/world/2024_03_18_00_npc_text.sql new file mode 100644 index 000000000..ad440f831 --- /dev/null +++ b/sql/Bots/updates/world/2024_03_18_00_npc_text.sql @@ -0,0 +1,2 @@ +-- +UPDATE `npc_text` SET `text0_0`='You exceed max npcbots for your level (%u)' WHERE (`ID`='70532'); diff --git a/sql/Bots/updates/world/2024_03_19_00_npc_text.sql b/sql/Bots/updates/world/2024_03_19_00_npc_text.sql new file mode 100644 index 000000000..c0904d4f7 --- /dev/null +++ b/sql/Bots/updates/world/2024_03_19_00_npc_text.sql @@ -0,0 +1,17 @@ +-- +SET @LOCALIZED_STRINGS_START = 70674; +SET @LOCALIZED_STRINGS_END = 70684; + +DELETE FROM `npc_text` WHERE ID BETWEEN @LOCALIZED_STRINGS_START and @LOCALIZED_STRINGS_END; +INSERT INTO `npc_text` (`ID`,`text0_0`,`VerifiedBuild`) VALUES +(70674,'I need a portal','-1'), +(70675,'Stormwind','-1'), +(70676,'Ironforge','-1'), +(70677,'Darnassus','-1'), +(70678,'Exodar','-1'), +(70679,'Orgrimmar','-1'), +(70680,'Undercity','-1'), +(70681,'Thunder Bluff','-1'), +(70682,'Silvermoon','-1'), +(70683,'Shattrath','-1'), +(70684,'Dalaran','-1'); diff --git a/sql/Bots/updates/world/2024_05_29_00_command.sql b/sql/Bots/updates/world/2024_05_29_00_command.sql new file mode 100644 index 000000000..b0d6c334f --- /dev/null +++ b/sql/Bots/updates/world/2024_05_29_00_command.sql @@ -0,0 +1,3 @@ +-- +INSERT IGNORE INTO `command` (`name`) VALUES +('npcbot log clear'); diff --git a/sql/Bots/updates/world/2024_08_14_00_npc_text.sql b/sql/Bots/updates/world/2024_08_14_00_npc_text.sql new file mode 100644 index 000000000..31b1faf7f --- /dev/null +++ b/sql/Bots/updates/world/2024_08_14_00_npc_text.sql @@ -0,0 +1,7 @@ +-- +SET @LOCALIZED_STRINGS_START = 70685; +SET @LOCALIZED_STRINGS_END = 70685; + +DELETE FROM `npc_text` WHERE ID BETWEEN @LOCALIZED_STRINGS_START and @LOCALIZED_STRINGS_END; +INSERT INTO `npc_text` (`ID`,`text0_0`,`VerifiedBuild`) VALUES +(@LOCALIZED_STRINGS_START,'You exceed max npcbots for your account (%u >= %u)','-1'); diff --git a/sql/Bots/updates/world/2024_08_15_00_command.sql b/sql/Bots/updates/world/2024_08_15_00_command.sql new file mode 100644 index 000000000..3d7c3460e --- /dev/null +++ b/sql/Bots/updates/world/2024_08_15_00_command.sql @@ -0,0 +1,3 @@ +-- +INSERT IGNORE INTO `command` (`name`) VALUES +('npcbot free'); diff --git a/sql/Bots/updates/world/2024_08_31_00_command.sql b/sql/Bots/updates/world/2024_08_31_00_command.sql new file mode 100644 index 000000000..4a19d83e5 --- /dev/null +++ b/sql/Bots/updates/world/2024_08_31_00_command.sql @@ -0,0 +1,3 @@ +-- +INSERT IGNORE INTO `command` (`name`) VALUES +('npcbot order pull'); diff --git a/src/common/Utilities/EventProcessor.h b/src/common/Utilities/EventProcessor.h index 5cb510868..7d55d09eb 100644 --- a/src/common/Utilities/EventProcessor.h +++ b/src/common/Utilities/EventProcessor.h @@ -56,6 +56,7 @@ class TC_COMMON_API BasicEvent // Aborts the event at the next update tick void ScheduleAbort(); + bool IsActive() const { return m_abortState == AbortState::STATE_RUNNING; } private: void SetAborted(); diff --git a/src/server/database/Database/Implementation/CharacterDatabase.cpp b/src/server/database/Database/Implementation/CharacterDatabase.cpp index 35c9d4d55..90402d2b1 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.cpp +++ b/src/server/database/Database/Implementation/CharacterDatabase.cpp @@ -594,6 +594,35 @@ void CharacterDatabaseConnection::DoPrepareStatements() // DeserterTracker PrepareStatement(CHAR_INS_DESERTER_TRACK, "INSERT INTO battleground_deserters (guid, type, datetime) VALUES (?, ?, NOW())", CONNECTION_ASYNC); + + // NPCBots + PrepareStatement(CHAR_UPD_NPCBOT_OWNER, "UPDATE characters_npcbot SET owner = ?, hire_time = FROM_UNIXTIME(?) WHERE entry = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_UPD_NPCBOT_OWNER_ALL, "UPDATE characters_npcbot SET owner = ?, hire_time = FROM_UNIXTIME(?) WHERE owner = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_UPD_NPCBOT_ROLES, "UPDATE characters_npcbot SET roles = ? WHERE entry = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_SEL_NPCBOT_EQUIP_BY_ITEM_INSTANCE, "SELECT creatorGuid, giftCreatorGuid, count, duration, charges, flags, enchantments, randomPropertyId, durability, playedTime, text, guid, itemEntry, owner_guid " + "FROM item_instance WHERE guid IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_SYNCH); + PrepareStatement(CHAR_UPD_NPCBOT_EQUIP, "UPDATE characters_npcbot SET equipMhEx = ?, equipOhEx = ?, equipRhEx = ?, " + "equipHead = ?, equipShoulders = ?, equipChest = ?, equipWaist = ?, equipLegs = ?, equipFeet = ?, equipWrist = ?, equipHands = ?, equipBack = ?, equipBody = ?, equipFinger1 = ?, equipFinger2 = ?, equipTrinket1 = ?, equipTrinket2 = ?, equipNeck = ? WHERE entry = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_UPD_NPCBOT_EQUIP_RESET_ALL, "UPDATE characters_npcbot SET equipMhEx = 0, equipOhEx = 0, equipRhEx = 0, " + "equipHead = 0, equipShoulders = 0, equipChest = 0, equipWaist = 0, equipLegs = 0, equipFeet = 0, equipWrist = 0, equipHands = 0, equipBack = 0, equipBody = 0, equipFinger1 = 0, equipFinger2 = 0, equipTrinket1 = 0, equipTrinket2 = 0, equipNeck = 0 WHERE owner = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_DEL_NPCBOT, "DELETE FROM characters_npcbot WHERE entry = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_INS_NPCBOT, "INSERT INTO characters_npcbot (entry, roles, spec, faction) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC); + PrepareStatement(CHAR_UPD_NPCBOT_FACTION, "UPDATE characters_npcbot SET faction = ? WHERE entry = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_UPD_NPCBOT_SPEC, "UPDATE characters_npcbot SET spec = ? WHERE entry = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_UPD_NPCBOT_DISABLED_SPELLS, "UPDATE characters_npcbot SET spells_disabled = ? WHERE entry = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_REP_NPCBOT_STATS, "REPLACE INTO characters_npcbot_stats " + "(entry, maxhealth, maxpower, strength, agility, stamina, intellect, spirit, armor, defense, resHoly, resFire, resNature, resFrost, resShadow, resArcane, blockPct, dodgePct, parryPct, critPct, attackPower, spellPower, spellPen, hastePct, hitBonusPct, expertise, armorPenPct) VALUES " + "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC); + PrepareStatement(CHAR_REP_NPCBOT_TRANSMOG, "REPLACE INTO characters_npcbot_transmog (entry, slot, item_id, fake_id) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC); + PrepareStatement(CHAR_DEL_NPCBOT_TRANSMOG, "DELETE FROM characters_npcbot_transmog WHERE entry = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_DEL_NPCBOT_TRANSMOG_ALL, "DELETE FROM characters_npcbot_transmog WHERE entry IN (SELECT entry FROM characters_npcbot WHERE owner = ?)", CONNECTION_ASYNC); + PrepareStatement(CHAR_INS_NPCBOT_GROUP_MEMBER, "INSERT INTO characters_npcbot_group_member (guid, entry, memberFlags, subgroup, roles) VALUES(?, ?, ?, ?, ?)", CONNECTION_ASYNC); + PrepareStatement(CHAR_DEL_NPCBOT_GROUP_MEMBER, "DELETE FROM characters_npcbot_group_member WHERE entry = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_DEL_NPCBOT_GROUP_MEMBER_ALL, "DELETE FROM characters_npcbot_group_member WHERE guid = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_UPD_NPCBOT_GROUP_MEMBER_FLAG, "UPDATE characters_npcbot_group_member SET memberFlags = ? WHERE entry = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_INS_NPCBOT_LOG, "INSERT INTO characters_npcbot_logs (entry, owner, mapid, inmap, inworld, type, param1, param2, param3, param4, param5) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC); + PrepareStatement(CHAR_SEL_NPCBOT_ACC_BOT_COUNT, "SELECT COUNT(entry) FROM characters_npcbot WHERE owner IN (SELECT guid FROM characters WHERE account = ?);", CONNECTION_SYNCH); + // End NPCBots } CharacterDatabaseConnection::CharacterDatabaseConnection(MySQLConnectionInfo& connInfo) : MySQLConnection(connInfo) diff --git a/src/server/database/Database/Implementation/CharacterDatabase.h b/src/server/database/Database/Implementation/CharacterDatabase.h index 7404a615c..944122d38 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.h +++ b/src/server/database/Database/Implementation/CharacterDatabase.h @@ -510,6 +510,30 @@ enum CharacterDatabaseStatements : uint32 CHAR_INS_DESERTER_TRACK, + // NPCBot + CHAR_UPD_NPCBOT_OWNER, + CHAR_UPD_NPCBOT_OWNER_ALL, + CHAR_UPD_NPCBOT_ROLES, + CHAR_SEL_NPCBOT_EQUIP_BY_ITEM_INSTANCE, + CHAR_UPD_NPCBOT_EQUIP, + CHAR_UPD_NPCBOT_EQUIP_RESET_ALL, + CHAR_DEL_NPCBOT, + CHAR_INS_NPCBOT, + CHAR_UPD_NPCBOT_FACTION, + CHAR_UPD_NPCBOT_SPEC, + CHAR_UPD_NPCBOT_DISABLED_SPELLS, + CHAR_REP_NPCBOT_STATS, + CHAR_REP_NPCBOT_TRANSMOG, + CHAR_DEL_NPCBOT_TRANSMOG, + CHAR_DEL_NPCBOT_TRANSMOG_ALL, + CHAR_INS_NPCBOT_GROUP_MEMBER, + CHAR_DEL_NPCBOT_GROUP_MEMBER, + CHAR_DEL_NPCBOT_GROUP_MEMBER_ALL, + CHAR_UPD_NPCBOT_GROUP_MEMBER_FLAG, + CHAR_INS_NPCBOT_LOG, + CHAR_SEL_NPCBOT_ACC_BOT_COUNT, + // End NPCBot + MAX_CHARACTERDATABASE_STATEMENTS }; diff --git a/src/server/game/AI/CoreAI/UnitAI.cpp b/src/server/game/AI/CoreAI/UnitAI.cpp index 35d2e2568..71f86f6f1 100644 --- a/src/server/game/AI/CoreAI/UnitAI.cpp +++ b/src/server/game/AI/CoreAI/UnitAI.cpp @@ -352,6 +352,9 @@ bool DefaultTargetSelector::operator()(Unit const* target) const return false; if (_playerOnly && (target->GetTypeId() != TYPEID_PLAYER)) + //npcbot: allow to target bots + //if (!(target->GetTypeId() == TYPEID_UNIT && target->ToCreature()->IsNPCBot())) + //end npcbot return false; if (_dist > 0.0f && !_me->IsWithinCombatRange(target, _dist)) diff --git a/src/server/game/AI/NpcBots/bot_Events.h b/src/server/game/AI/NpcBots/bot_Events.h new file mode 100644 index 000000000..961d7b213 --- /dev/null +++ b/src/server/game/AI/NpcBots/bot_Events.h @@ -0,0 +1,74 @@ +#ifndef _BOT_EVENTS_H +#define _BOT_EVENTS_H + +#include "EventProcessor.h" + +/* +Name: bot_Events +%Complete: ??? +Comment: Custom event types for NPCBot system by Trickerer (onlysuffering@gmail.com) +Category: creature_cripts/custom/bots/events + +Notes: +All events must be executed through botAI +*/ + +//Teleport home: near or far, only used for free bots +class TeleportHomeEvent : public BasicEvent +{ + friend class bot_ai; + protected: + TeleportHomeEvent(bot_ai* ai, bool reset) : _ai(ai), _reset(reset) {} + ~TeleportHomeEvent() {} + + bool Execute(uint64 /*e_time*/, uint32 /*p_time*/) + { + _ai->TeleportHome(_reset); + return true; + } + + private: + bot_ai* _ai; + bool _reset; +}; +//Delayed teleport finish: adds bot back to world on new location +class TeleportFinishEvent : public BasicEvent +{ + friend class bot_ai; + friend class BotMgr; + protected: + TeleportFinishEvent(bot_ai* ai, bool reset) : _ai(ai), _reset(reset) {} + ~TeleportFinishEvent() {} + + //Execute is always called while creature is out of world so ai is never deleted + bool Execute(uint64 /*e_time*/, uint32 /*p_time*/) + { + _ai->FinishTeleport(_reset); + return true; + } + + private: + bot_ai* _ai; + bool _reset; +}; +//Await state removal +class AwaitStateRemovalEvent : public BasicEvent +{ + friend class bot_ai; + protected: + AwaitStateRemovalEvent(bot_ai* ai, uint8 state) : _ai(ai), _state(state) {} + ~AwaitStateRemovalEvent() = default; + + //Execute is always called while creature is out of world so ai is never deleted + bool Execute(uint64 /*e_time*/, uint32 /*p_time*/) + { + _ai->EventRemoveBotAwaitState(_state); + return true; + } + + private: + bot_ai* _ai; + const uint8 _state; +}; + +#endif diff --git a/src/server/game/AI/NpcBots/bot_GridNotifiers.h b/src/server/game/AI/NpcBots/bot_GridNotifiers.h new file mode 100644 index 000000000..acfd3ae39 --- /dev/null +++ b/src/server/game/AI/NpcBots/bot_GridNotifiers.h @@ -0,0 +1,1410 @@ +#ifndef _BOT_GRIDNOTIFIERS_H +#define _BOT_GRIDNOTIFIERS_H + +#include "bot_ai.h" +#include "botspell.h" +#include "Corpse.h" +#include "Creature.h" +#include "DBCStores.h" +#include "DynamicObject.h" +#include "GameObject.h" +#include "Group.h" +#include "ObjectAccessor.h" +#include "Player.h" +#include "Spell.h" +#include "SpellAuras.h" +#include "SpellMgr.h" +#include "Vehicle.h" +/* +Name: bot_GridNotifiers +%Complete: 99+ +Comment: Custom grid notifiers for Bot system by Trickerer (onlysuffering@gmail.com) +Category: creature_cripts/custom/bots/grids +*/ + +extern bool _botPvP; + +template +struct Unit2LastSearcher +{ + Unit* &i_result1; + Unit* &i_result2; + Check& i_check; + + Unit2LastSearcher(Unit* &result1, Unit* &result2, Check& check) + : i_result1(result1), i_result2(result2), i_check(check) { } + + void Visit(CreatureMapType &m) + { + for (CreatureMapType::iterator itr = m.begin(); itr != m.end(); ++itr) + { + switch ((uint32)i_check(itr->GetSource())) + { + case 1: i_result1 = itr->GetSource(); break; + case 2: i_result2 = itr->GetSource(); break; + default: break; + } + } + } + + void Visit(PlayerMapType &m) + { + for (PlayerMapType::iterator itr = m.begin(); itr != m.end(); ++itr) + { + switch ((uint32)i_check(itr->GetSource())) + { + case 1: i_result1 = itr->GetSource(); break; + case 2: i_result2 = itr->GetSource(); break; + default: break; + } + } + } + + template void Visit(GridRefManager &) { } +}; + +class ImmunityShieldDispelTargetCheck +{ + public: + explicit ImmunityShieldDispelTargetCheck(Unit const* unit, float dist, bot_ai const* m_ai) : + me(unit), range(dist), ai(m_ai) { } + bool operator()(Unit const* u) + { + if (!_botPvP && me->IsPvP() && u->IsControlledByPlayer()) + return false; + if (!u->IsInCombat()) + return false; + if (!u->InSamePhase(me)) + return false; + if (!me->IsWithinDistInMap(u, range)) + return false; + if (!u->isTargetableForAttack()) + return false; + //if (ai->InDuel(u)) + // return false; + if (!ai->IsInBotParty(u->GetVictim())) + return false; + if (!u->HasAuraWithMechanic(1<IsWithinLOSInMap(me, LINEOFSIGHT_ALL_CHECKS, VMAP::ModelIgnoreFlags::M2)) + return false; + + return true; + } + private: + Unit const* me; + float range; + bot_ai const* ai; + ImmunityShieldDispelTargetCheck(ImmunityShieldDispelTargetCheck const&); +}; + +class NearestHostileUnitCheck +{ + enum : uint32 { + INVALID = 0, + VALID_PRIMARY = 1, + VALID_SECONDARY = 2 + }; + + public: + NearestHostileUnitCheck(NearestHostileUnitCheck const&) = delete; + explicit NearestHostileUnitCheck(Unit const* unit, float dist, bool magic, bot_ai const* m_ai, bool targetCCed, bool withSecondary) : + me(unit), m_range(dist), byspell(magic), ai(m_ai), AttackCCed(targetCCed), checkSecondary(withSecondary) + { free = ai->IAmFree(); berserk = free && (ai->IsWanderer() || unit->GetFaction() == 14); } + explicit NearestHostileUnitCheck(Unit const* unit, float dist, bool magic, bot_ai const* m_ai) : + NearestHostileUnitCheck(unit, dist, magic, m_ai, true, false) + {} + uint32 operator()(Unit const* u) + { + if (u == me) + return INVALID; + if (!me->IsWithinDistInMap(u, m_range, !berserk)) + return INVALID; + if (berserk && std::fabs(me->GetPositionZ() - u->GetPositionZ()) > (m_range * 0.25f + 5.0f)) + return INVALID; + if (me->HasUnitState(UNIT_STATE_ROOT) && (ai->HasRole(BOT_ROLE_RANGED) == me->IsWithinDistInMap(u, 8.f))) + return INVALID; + if (!berserk && !u->IsInCombat()) + return INVALID; + //if (ai->InDuel(u)) + // return false; + if (!AttackCCed && (u->HasUnitState(UNIT_STATE_CONFUSED | UNIT_STATE_STUNNED | UNIT_STATE_FLEEING | UNIT_STATE_DISTRACTED | UNIT_STATE_CONFUSED_MOVE | UNIT_STATE_FLEEING_MOVE))) + return INVALID;//do not allow CCed units if checked + //if (u->HasUnitState(UNIT_STATE_CASTING) && (u->GetTypeId() == TYPEID_PLAYER || u->IsPet())) + // for (uint8 i = 0; i != CURRENT_MAX_SPELL; ++i) + // if (Spell* spell = u->GetCurrentSpell(i)) + // if (ai->IsInBotParty(spell->m_targets.GetUnitTarget())) + // return true; + if (!berserk && !ai->IsInBotParty(u->GetVictim())) + return INVALID; + + if (free && !berserk && u->IsControlledByPlayer() && !u->IsInCombat()) + return INVALID; + + if (!u->IsWithinLOSInMap(me, LINEOFSIGHT_ALL_CHECKS)) + return INVALID; + + uint32 res = VALID_PRIMARY; + if (!ai->CanBotAttack(u, byspell)) + { + if (checkSecondary && ai->CanBotAttack(u, byspell, checkSecondary)) + res = VALID_SECONDARY; + else + return INVALID; + } + + m_range = me->GetDistance(u); // use found unit range as new range limit for next check + return res; + } + private: + Unit const* me; + float m_range; + bool byspell; + bot_ai const* ai; + bool AttackCCed; + bool checkSecondary; + bool free; + bool berserk; +}; + +class NearbyHostileVehicleTargetCheck +{ + public: + explicit NearbyHostileVehicleTargetCheck(Unit const* unit, float dist, bot_ai const* m_ai) : veh(unit), m_range(dist), ai(m_ai) { } + bool operator()(Unit const* u) + { + if (u == veh) + return false; + if (!veh->IsWithinDistInMap(u, m_range)) + return false; + if (!u->IsInCombat()) + return false; + if (!_botPvP && veh->IsPvP() && u->IsControlledByPlayer()) + return false; + //if (!veh->IsValidAttackTarget(u)) + // return false; + //if (!u->isTargetableForAttack(false)) + // return false; + //if (ai->IsInBotParty(u)) + // return false; + if (!ai->IsInBotParty(u->GetVictim())) + return false; + if (u->GetReactionTo(veh) >= REP_NEUTRAL) + return false; + //if (!u->IsWithinLOSInMap(veh)) + // return false; + + return true; + } + private: + Unit const* veh; + float m_range; + bot_ai const* ai; + NearbyHostileVehicleTargetCheck(NearbyHostileVehicleTargetCheck const&); +}; + +class HostileDispelTargetCheck +{ + public: + explicit HostileDispelTargetCheck(Unit const* unit, float dist = 30, bool stealable = false, bot_ai const* m_ai = nullptr) : + me(unit), m_range(dist), checksteal(stealable), ai(m_ai) { } + bool operator()(Unit const* u) const + { + if (!_botPvP && me->IsPvP() && u->IsControlledByPlayer()) + return false; + if (u->IsInCombat() && + u->InSamePhase(me) && + u->IsWithinDistInMap(me, m_range) && + u->isTargetableForAttack() && + //!ai->InDuel(u) && + (!ai->IsInBotParty(u) || ai->IsInHeroicOrRaid()) && + (ai->IsInBotParty(u->GetVictim()) || me->GetVictim() == u)) + { + if (!checksteal && u->GetEntry() == 25744 && !u->GetOwnedAuras().empty()) // Sunwell - Dark Fiend + return true; + + if (checksteal) + { + if (u->IsImmunedToSpell(sSpellMgr->GetSpellInfo(30449), me)) + return false; //immune to steal + } + else if (ai->GetBotClass() == BOT_CLASS_SHAMAN) + { + if (u->IsImmunedToSpell(sSpellMgr->GetSpellInfo(370), me)) + return false; //immune to purge + } + //else if (ai->GetBotClass() == BOT_CLASS_PRIEST) + //{ + // if (me->GetLevel() < 70 && u->IsImmunedToSpell(sSpellMgr->GetSpellInfo(527), me)) + // return false; //immune to dispel magic + //} + + Unit::AuraApplicationMap const &AurApps = u->GetAppliedAuras(); + SpellInfo const* Info; + uint32 id; + for (Unit::AuraApplicationMap::const_iterator itr = AurApps.begin(); itr != AurApps.end(); ++itr) + { + Info = itr->second->GetBase()->GetSpellInfo(); + if (itr->second->IsPositive() && Info->Dispel == DISPEL_MAGIC && + !(Info->Attributes & (SPELL_ATTR0_PASSIVE | SPELL_ATTR0_HIDDEN_CLIENTSIDE)) && + !(checksteal && (Info->AttributesEx4 & SPELL_ATTR4_NOT_STEALABLE))) + { + id = Info->Id; + if (id != 20050 && id != 20052 && id != 20053 && //Vengeance + id != 50447 && id != 50448 && id != 50449) //Bloody Vengeance + return true; + } + } + + //Unit::AuraMap const &Auras = u->GetOwnedAuras(); + //SpellInfo const* Info; + //uint32 id; + //for (Unit::AuraMap::const_iterator itr = Auras.begin(); itr != Auras.end(); ++itr) + //{ + // Aura* aura = itr->second; + // Info = aura->GetSpellInfo(); + // if (Info->Dispel != DISPEL_MAGIC) continue; + // id = Info->Id; + // if (id == 20050 || id == 20052 || id == 20053 || //Vengeance + // id == 50447 || id == 50448 || id == 50449) //Bloody Vengeance + // continue; + // if (Info->Attributes & (SPELL_ATTR0_PASSIVE | SPELL_ATTR0_HIDDEN_CLIENTSIDE)) continue; + // //if (Info->AttributesEx & SPELL_ATTR1_DONT_DISPLAY_IN_AURA_BAR) continue; + // if (checksteal && (Info->AttributesEx4 & SPELL_ATTR4_NOT_STEALABLE)) continue; + // AuraApplication const* aurApp = aura->GetApplicationOfTarget(u->GetGUID()); + // if (aurApp && aurApp->IsPositive()) + // return true; + //} + } + return false; + } + private: + Unit const* me; + float m_range; + bool checksteal; + bot_ai const* ai; + HostileDispelTargetCheck(HostileDispelTargetCheck const&); +}; + +class AffectedTargetCheck +{ + public: + explicit AffectedTargetCheck(ObjectGuid casterguid, float dist, uint32 spellId, Player const* groupCheck = 0, uint8 hostileCheckType = 0) : + caster(casterguid), m_range(dist), spell(spellId), checker(groupCheck), needhostile(hostileCheckType) { } + bool operator()(Unit const* u) const + { + if (!u->IsAlive()) + return false; + if (caster && u->HasUnitFlag(UNIT_FLAG_UNINTERACTIBLE)) + return false; + if (!checker->IsWithinDistInMap(u, m_range)) + return false; + if (needhostile == 0 && !u->IsHostileTo(checker)) return false; + //else if (needhostile == 1 && !(gr && gr->IsMember(u->GetGUID()) && u->GetTypeId() == TYPEID_PLAYER)) return false; + //else if (needhostile == 2 && !(gr && gr->IsMember(u->GetGUID()))) return false; + else if (needhostile == 3 && !u->IsFriendlyTo(checker)) return false; + else if (needhostile == 4 && !(u->GetTypeId() == TYPEID_PLAYER && u->IsFriendlyTo(checker))) return false; + + //if (u->HasAura(spell, caster) + // return true; + + Unit::AuraApplicationMap const &Auras = u->GetAppliedAuras(); + for (Unit::AuraApplicationMap::const_iterator itr = Auras.begin(); itr != Auras.end(); ++itr) + { + AuraApplication const* auraApp = itr->second; + if (itr->first == spell) + if (caster == 0 || auraApp->GetBase()->GetCasterGUID() == caster) + return true; + } + return false; + } + private: + ObjectGuid const caster; + float m_range; + uint32 const spell; + Player const* checker; + uint8 needhostile; + AffectedTargetCheck(AffectedTargetCheck const&); +}; + +class PolyUnitCheck +{ + public: + explicit PolyUnitCheck(Unit const* unit, float dist) : me(unit), m_range(dist) {} + bool operator()(Unit const* u) const + { + if (!_botPvP && me->IsPvP() && u->IsControlledByPlayer()) + return false; + if (!me->GetVictim() || u == me->GetVictim()) + return false; + if (!me->IsWithinDistInMap(u, m_range)) + return false; + if (!u->IsInCombat() || !u->IsAlive() || !u->GetVictim()) + return false; + if (u->GetCreatureType() != CREATURE_TYPE_HUMANOID && + u->GetCreatureType() != CREATURE_TYPE_BEAST) + return false; + if (me->GetDistance(u) < 6 || me->GetVictim()->GetDistance(u) < 5 || + (me->ToCreature()->GetBotClass() == BOT_CLASS_MAGE && u->GetHealthPct() < 70)) + return false; + if (!u->InSamePhase(me)) + return false; + if (!u->isTargetableForAttack()) + return false; + if (!u->IsVisible() || u->IsTotem()) + return false; + if (me->ToCreature()->GetBotClass() == BOT_CLASS_MAGE ? !u->getAttackers().empty() : u->getAttackers().size() > 1) + return false; + if (!u->IsHostileTo(me)) + return false; + if (u->IsPolymorphed() || + u->IsFrozen() || + u->IsRooted() || + u->HasAuraType(SPELL_AURA_PERIODIC_DAMAGE) || + u->HasAuraType(SPELL_AURA_MOD_PACIFY_SILENCE) || + u->HasAuraTypeWithFamilyFlags(SPELL_AURA_MOD_STUN, SPELLFAMILY_PALADIN, 0x4)) + return false; + + if (me->ToCreature()->GetBotClass() == BOT_CLASS_MAGE && !u->IsImmunedToSpell(sSpellMgr->GetSpellInfo(118), me))//Polymorph + return true; + if (me->ToCreature()->GetBotClass() == BOT_CLASS_SHAMAN && !u->IsImmunedToSpell(sSpellMgr->GetSpellInfo(51514), me))//Hex + return true; + + return false; + } + private: + Unit const* me; + float m_range; + PolyUnitCheck(PolyUnitCheck const&); +}; + +class FearUnitCheck +{ + public: + explicit FearUnitCheck(Unit const* unit, float dist = 30) : me(unit), m_range(dist) {} + bool operator()(Unit const* u) const + { + if (!_botPvP && me->IsPvP() && u->IsControlledByPlayer()) + return false; + if (!u->IsAlive()) + return false; + if (!u->IsInCombat()) + return false; + if (!u->InSamePhase(me)) + return false; + if (u->HasUnitState(UNIT_STATE_CONFUSED | UNIT_STATE_STUNNED | UNIT_STATE_FLEEING | UNIT_STATE_DISTRACTED | UNIT_STATE_CONFUSED_MOVE | UNIT_STATE_FLEEING_MOVE)) + return false; + if (u->IsFeared()) + return false; + if (!me->IsWithinDistInMap(u, m_range)) + return false; + if (!u->IsVisible() || u->IsTotem()) + return false; + if (u->GetCreatureType() == CREATURE_TYPE_UNDEAD) + return false; + if (u->GetCreatureType() != CREATURE_TYPE_BEAST && + me->ToCreature()->GetBotClass() == BOT_CLASS_HUNTER) + return false; + if (!u->isTargetableForAttack()) + return false; + if (u->getAttackers().size() > 1 && u->GetVictim() != me) + return false; + //Unit::GetDiminishing() should be const but it isn't + if (const_cast(u)->GetDiminishing(DIMINISHING_FEAR) > DIMINISHING_LEVEL_3) + return false; + if (u->GetReactionTo(me) > REP_NEUTRAL) + return false; + + if (me->ToCreature()->GetBotClass() == BOT_CLASS_WARLOCK && + !u->IsImmunedToSpell(sSpellMgr->GetSpellInfo(5782), me))//fear rank1 + return true; + if (me->ToCreature()->GetBotClass() == BOT_CLASS_HUNTER && + !u->IsImmunedToSpell(sSpellMgr->GetSpellInfo(1513), me))//scare beast rank1 + return true; + + return false; + } + private: + Unit const* me; + float m_range; + FearUnitCheck(FearUnitCheck const&); +}; + +class StunUnitCheck +{ + public: + explicit StunUnitCheck(Unit const* unit, float dist = 20) : me(unit), m_range(dist) {} + bool operator()(Unit const* u) const + { + if (!_botPvP && me->IsPvP() && u->IsControlledByPlayer()) + return false; + if (!u->IsAlive()) + return false; + if (!u->IsInCombat()) + return false; + if (!u->InSamePhase(me)) + return false; + if (u->HasUnitState(UNIT_STATE_CONFUSED | UNIT_STATE_STUNNED | UNIT_STATE_FLEEING | UNIT_STATE_DISTRACTED | UNIT_STATE_CONFUSED_MOVE | UNIT_STATE_FLEEING_MOVE)) + return false; + if (!me->IsWithinDistInMap(u, m_range)) + return false; + if (!u->IsVisible() || u->IsTotem()) + return false; + if (!u->isTargetableForAttack()) + return false; + if (!u->getAttackers().empty()) + return false; + if (me->ToCreature()->GetBotClass() != BOT_CLASS_DREADLORD && u->HasAuraType(SPELL_AURA_PERIODIC_DAMAGE)) + return false; + if (me->ToCreature()->GetBotClass() == BOT_CLASS_HUNTER && me->GetTarget() == u->GetGUID())//auto shot + return false; + if (u->GetReactionTo(me) > REP_NEUTRAL) + return false; + if (me->ToCreature()->GetBotClass() == BOT_CLASS_PALADIN && + !(u->GetCreatureType() == CREATURE_TYPE_HUMANOID || + u->GetCreatureType() == CREATURE_TYPE_DEMON || + u->GetCreatureType() == CREATURE_TYPE_DRAGONKIN || + u->GetCreatureType() == CREATURE_TYPE_GIANT || + u->GetCreatureType() == CREATURE_TYPE_UNDEAD)) + return false; + if (me->ToCreature()->GetBotClass() == BOT_CLASS_DRUID && + !(u->GetCreatureType() == CREATURE_TYPE_BEAST || + u->GetCreatureType() == CREATURE_TYPE_DRAGONKIN)) + return false; + if (me->ToCreature()->GetBotClass() == BOT_CLASS_PALADIN && me->GetDistance(u) < 10)//prevent break due to AOE damage + return false; + if (me->ToCreature()->GetBotClass() == BOT_CLASS_PALADIN && + !u->IsImmunedToSpell(sSpellMgr->GetSpellInfo(20066), me))//repentance + return true; + if (me->ToCreature()->GetBotClass() == BOT_CLASS_HUNTER && + !u->IsImmunedToSpell(sSpellMgr->GetSpellInfo(60210), me))//freezing arrow effect + return true; + if (me->ToCreature()->GetBotClass() == BOT_CLASS_HUNTER && + !u->IsImmunedToSpell(sSpellMgr->GetSpellInfo(19386), me))//wyvern sting rank 1 + return true; + if (me->ToCreature()->GetBotClass() == BOT_CLASS_HUNTER && + !u->IsImmunedToSpell(sSpellMgr->GetSpellInfo(1991), me))//scatter shot + return true; + if (me->ToCreature()->GetBotClass() == BOT_CLASS_ROGUE && + !u->IsImmunedToSpell(sSpellMgr->GetSpellInfo(2094), me))//blind + return true; + if (me->ToCreature()->GetBotClass() == BOT_CLASS_WARLOCK && + u->GetCreatureType() == CREATURE_TYPE_HUMANOID && + (u->GetVictim() || u->IsControlledByPlayer()) && + !u->IsImmunedToSpell(sSpellMgr->GetSpellInfo(6358), me))//seduction + return true; + if (me->ToCreature()->GetBotClass() == BOT_CLASS_DREADLORD && + u->GetCreatureType() != CREATURE_TYPE_UNDEAD && + !u->IsImmunedToSpell(sSpellMgr->GetSpellInfo(SPELL_SLEEP), me)) + return true; + + return false; + } + private: + Unit const* me; + float m_range; + StunUnitCheck(StunUnitCheck const&); +}; + +class UndeadCCUnitCheck +{ + public: + explicit UndeadCCUnitCheck(Unit const* unit, float dist, bot_ai const* ai, uint32 spell, bool unattacked) : + me(unit), m_range(dist), m_ai(ai), m_spellId(spell), _unattacked(unattacked) { } + bool operator()(Unit const* u) const + { + if (!_botPvP && me->IsPvP() && u->IsControlledByPlayer()) + return false; + if (!me->IsWithinDistInMap(u, m_range)) + return false; + if (!u->InSamePhase(me)) + return false; + if (!u->IsInCombat()) + return false; + if (!u->IsAlive()) + return false; + if (!u->isTargetableForAttack()) + return false; + if (!u->IsVisible()) + return false; + if (_unattacked && !u->getAttackers().empty()) + return false; + if (u->HasUnitState(UNIT_STATE_CONFUSED | UNIT_STATE_STUNNED | UNIT_STATE_FLEEING | UNIT_STATE_DISTRACTED | UNIT_STATE_CONFUSED_MOVE | UNIT_STATE_FLEEING_MOVE)) + return false; + if (me->ToCreature()->GetBotClass() == BOT_CLASS_PRIEST && + !(u->GetCreatureType() == CREATURE_TYPE_UNDEAD && !u->HasAuraType(SPELL_AURA_PERIODIC_DAMAGE))) + return false; + if (me->ToCreature()->GetBotClass() == BOT_CLASS_PALADIN && + u->GetCreatureType() != CREATURE_TYPE_UNDEAD && u->GetCreatureType() != CREATURE_TYPE_DEMON) + return false; + if (me->ToCreature()->GetBotClass() == BOT_CLASS_WARLOCK && + ((u->GetCreatureType() != CREATURE_TYPE_DEMON && u->GetCreatureType() != CREATURE_TYPE_ELEMENTAL) || + m_ai->IsPointedAnyAttackTarget(u))) + return false; + if (u->GetVictim() && !m_ai->IsInBotParty(u->GetVictim())) + return false; + if (u->GetReactionTo(me) > REP_NEUTRAL) + return false; + if (u->IsImmunedToSpell(sSpellMgr->GetSpellInfo(m_spellId)->TryGetSpellInfoOverride(me), me)) + return false; + if (m_ai->IsPointedNoDPSTarget(u) && bot_ai::IsDamagingSpell(sSpellMgr->GetSpellInfo(m_spellId)->TryGetSpellInfoOverride(me))) + return false; + + return true; + } + private: + Unit const* me; + float m_range; + bot_ai const* m_ai; + uint32 m_spellId; + bool _unattacked; + UndeadCCUnitCheck(UndeadCCUnitCheck const&); +}; + +class RootUnitCheck +{ + public: + explicit RootUnitCheck(Unit const* unit, float dist, bot_ai const* ai, uint32 spell = 0) : + me(unit), m_range(dist), m_ai(ai), m_spellId(spell) { if (!spell) return; } + bool operator()(Unit const* u) const + { + if (!_botPvP && me->IsPvP() && u->IsControlledByPlayer()) + return false; + if (u == me->GetVictim()) + return false; + if (!u->IsAlive()) + return false; + if (!u->IsInCombat()) + return false; + if (!me->IsWithinDistInMap(u, m_range)) + return false; + if (me->GetDistance(u) < 8) + return false; + if (!u->InSamePhase(me)) + return false; + if (!u->IsVisible()) + return false; + if (!u->isTargetableForAttack(false)) + return false; + if (u->IsFrozen() || u->IsRooted()) + return false; + if (!u->getAttackers().empty()) + return false; + if (u->GetVictim() && !m_ai->IsInBotParty(u->GetVictim())) + return false; + if (u->GetReactionTo(me) > REP_NEUTRAL) + return false; + if (u->IsPolymorphed() || + u->HasAuraType(SPELL_AURA_MOD_PACIFY_SILENCE)/*hex*/ || + u->HasAuraTypeWithFamilyFlags(SPELL_AURA_MOD_STUN, SPELLFAMILY_PALADIN, 0x4)/*repentance*/ || + u->HasAuraTypeWithFamilyFlags(SPELL_AURA_MOD_STUN, SPELLFAMILY_PRIEST, 0x40000000)/*shackle undead*/) + return false; + if (m_ai->IsPointedNoDPSTarget(u) && bot_ai::IsDamagingSpell(sSpellMgr->GetSpellInfo(m_spellId)->TryGetSpellInfoOverride(me))) + return false; + if (!u->IsImmunedToSpell(sSpellMgr->GetSpellInfo(m_spellId)->TryGetSpellInfoOverride(me), me)) + return true; + + return false; + } + private: + Unit const* me; + float m_range; + bot_ai const* m_ai; + uint32 m_spellId; + RootUnitCheck(RootUnitCheck const&); +}; + +class CastingUnitCheck +{ + public: + explicit CastingUnitCheck(Unit const* unit, float mindist = 0.f, float maxdist = 30, uint32 spell = 0, uint8 minHpPct = 0) : + me(unit), min_range(mindist), max_range(maxdist), m_spell(spell), m_minHpPct(minHpPct) {} + bool operator()(Unit const* u) const + { + if (!u->IsAlive()) + return false; + if (!u->InSamePhase(me)) + return false; + if (!u->IsVisible() || u->IsTotem()) + return false; + if (!u->GetTarget() && !u->IsInCombat()) + return false; + if (!_botPvP && me->IsPvP() && u->IsControlledByPlayer()) + return false; + if (u->HealthBelowPct(m_minHpPct)) + return false; + if (min_range > 0.1f && me->GetDistance(u) < min_range) + return false; + if (!u->isTargetableForAttack(false)) + return false; + //if (!m_friend && u->HasUnitFlag(UNIT_FLAG_SILENCED))//prevent double silence + // return false; + if (!u->IsNonMeleeSpellCast(false,false,true)) + return false; + if (!me->IsWithinDistInMap(u, max_range)) + return false; + if (u->GetReactionTo(me) >= REP_FRIENDLY) + return false; + if (m_spell) + { + if ((m_spell == 5782 || //fear (warlock) + m_spell == 64044 || //fear (priest) + m_spell == SPELL_SLEEP) && + u->GetCreatureType() == CREATURE_TYPE_UNDEAD) + return false; + if (m_spell == 10326 && //turn evil + !(u->GetCreatureType() == CREATURE_TYPE_UNDEAD || + u->GetCreatureType() == CREATURE_TYPE_DEMON)) + return false; + if (m_spell == 20066 && //repentance + !(u->GetCreatureType() == CREATURE_TYPE_HUMANOID || + u->GetCreatureType() == CREATURE_TYPE_DEMON || + u->GetCreatureType() == CREATURE_TYPE_DRAGONKIN || + u->GetCreatureType() == CREATURE_TYPE_GIANT || + u->GetCreatureType() == CREATURE_TYPE_UNDEAD)) + return false; + if (m_spell == 2637 && //hibernate + !(u->GetCreatureType() == CREATURE_TYPE_BEAST || + u->GetCreatureType() == CREATURE_TYPE_DRAGONKIN)) + return false; + if (m_spell == 9484 && //shackle undead (priest) + u->GetCreatureType() != CREATURE_TYPE_UNDEAD) + return false; + + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(m_spell)->TryGetSpellInfoOverride(me); + if (u->IsImmunedToSpell(spellInfo, me)) + return false; + + if (me->GetTypeId() == TYPEID_UNIT && me->ToCreature()->GetBotAI() && me->ToCreature()->GetBotAI()->IsPointedNoDPSTarget(u) && + bot_ai::IsDamagingSpell(spellInfo)) + return false; + + if (!CastInterruptionCheck(u, spellInfo)) + return false; + } + + return true; + } + + static bool CastInterruptionCheck(Unit const* u, SpellInfo const* spellInfo) + { + if (spellInfo->HasEffect(SPELL_EFFECT_INTERRUPT_CAST) && spellInfo->GetFirstRankSpell()->Id != 853) //hammer of justice + { + if (u->GetTypeId() == TYPEID_UNIT && + (u->ToCreature()->GetCreatureTemplate()->MechanicImmuneMask & (1 << (MECHANIC_INTERRUPT - 1)))) + return false; + + Spell* curSpell; + for (uint8 i = CURRENT_FIRST_NON_MELEE_SPELL; i != CURRENT_AUTOREPEAT_SPELL; ++i) + { + curSpell = u->GetCurrentSpell(i); + if (!curSpell) + continue; + + //copied conditions from Spell::EffectInterruptCast + if (!((curSpell->getState() == SPELL_STATE_CASTING || + (curSpell->getState() == SPELL_STATE_PREPARING && curSpell->GetCastTime() > 0.0f)) && + curSpell->GetSpellInfo()->PreventionType == SPELL_PREVENTION_TYPE_SILENCE && + ((i == CURRENT_GENERIC_SPELL && curSpell->GetSpellInfo()->InterruptFlags & SPELL_INTERRUPT_FLAG_INTERRUPT) || + (i == CURRENT_CHANNELED_SPELL && curSpell->GetSpellInfo()->ChannelInterruptFlags & CHANNEL_INTERRUPT_FLAG_INTERRUPT)))) + return false; + } + } + bool silenceSpell = false; + for (uint8 i = 0; i != MAX_SPELL_EFFECTS; ++i) + { + if (spellInfo->_effects[i].Effect == SPELL_EFFECT_APPLY_AURA && + spellInfo->_effects[i].ApplyAuraName == SPELL_AURA_MOD_SILENCE) + { + silenceSpell = true; + break; + } + } + if (silenceSpell) + { + if (u->GetTypeId() == TYPEID_UNIT && + (u->ToCreature()->GetCreatureTemplate()->MechanicImmuneMask & (1 << (MECHANIC_SILENCE - 1)))) + return false; + + Spell* curSpell; + for (uint8 i = CURRENT_FIRST_NON_MELEE_SPELL; i != CURRENT_AUTOREPEAT_SPELL; ++i) + { + curSpell = u->GetCurrentSpell(i); + if (curSpell && curSpell->GetSpellInfo()->PreventionType != SPELL_PREVENTION_TYPE_SILENCE) + return false; + } + } + + return true; //do not check players and non-interrupt non-silence spells + } + + private: + Unit const* me; + float min_range, max_range; + uint32 m_spell; + uint8 m_minHpPct; + CastingUnitCheck(CastingUnitCheck const&); +}; + +class SecondEnemyCheck +{ + public: + explicit SecondEnemyCheck(Unit const* unit, float dist, float splashdist, Unit const* currtarget, bot_ai const* m_ai) : + me(unit), m_range(dist), m_splashrange(splashdist), mytar(currtarget), ai(m_ai) {} + bool operator()(Unit const* u) const + { + if (!u->IsAlive()) + return false; + if (!u->IsInCombat()) + return false; + if (u == mytar) + return false;//We need to find SECONDARY target + if (u->isMoving() != mytar->isMoving())//only when both targets idle or both moving + return false; + if (!_botPvP && me->IsPvP() && u->IsControlledByPlayer()) + return false; + if (!me->IsWithinDistInMap(u, m_range))//distance check + return false; + if (mytar->GetDistance(u) > m_splashrange)//not close enough to each other + return false; + + if (ai->CanBotAttack(u)) + return true; + + return false; + } + private: + Unit const* me; + float m_range, m_splashrange; + Unit const* mytar; + bot_ai const* ai; + SecondEnemyCheck(SecondEnemyCheck const&); +}; + +class TranquilTargetCheck +{ + public: + explicit TranquilTargetCheck(Unit const* unit, float mindist, float maxdist, bot_ai const* m_ai) : + me(unit), min_range(mindist), max_range(maxdist), ai(m_ai) { } + bool operator()(Unit const* u) const + { + if (!_botPvP && me->IsPvP() && u->IsControlledByPlayer()) + return false; + if (u != me->GetVictim() &&//check hunter_bot::hunter_botAI::CheckTranquil(uint32) + u->IsWithinDistInMap(me, max_range) && + u->GetDistance(me) > min_range && + u->IsAlive() && + u->InSamePhase(me) && + u->IsInCombat() && + u->isTargetableForAttack(false) && + u->IsVisible() && + ai->IsInBotParty(u->GetVictim()) && + u->GetReactionTo(me) <= REP_NEUTRAL) + { + if (u->IsImmunedToSpell(sSpellMgr->GetSpellInfo(19801), me)) + return false;//immune to tranquilizing shot + Unit::AuraMap const &Auras = u->GetOwnedAuras(); + for (Unit::AuraMap::const_iterator itr = Auras.begin(); itr != Auras.end(); ++itr) + { + SpellInfo const* Info = itr->second->GetSpellInfo(); + if (Info->Dispel != DISPEL_MAGIC && Info->Dispel != DISPEL_ENRAGE) continue; + if (Info->Attributes & (SPELL_ATTR0_PASSIVE | SPELL_ATTR0_HIDDEN_CLIENTSIDE)) continue; + //if (Info->AttributesEx & SPELL_ATTR1_DONT_DISPLAY_IN_AURA_BAR) continue; + AuraApplication const* aurApp = itr->second->GetApplicationOfTarget(u->GetGUID()); + if (aurApp && aurApp->IsPositive()) + { + return true; + } + } + } + + return false; + } + private: + Unit const* me; + float min_range, max_range; + bot_ai const* ai; + TranquilTargetCheck(TranquilTargetCheck const&); +}; + +class NearbyHostileUnitCheck +{ + public: + explicit NearbyHostileUnitCheck(Unit const* unit, float maxdist, bot_ai const* m_ai, uint8 CCoption, WorldObject const* source) : + me(unit), max_range(maxdist), ai(m_ai), m_CCoption(CCoption), _source(source) + { + free = ai->IAmFree(); + } + bool operator()(Unit const* u) const + { + if (u == me || u == _source) + return false; + if (/*!free && */!u->IsInCombat()) + return false; + if (!u->InSamePhase(_source)) + return false; + if ((m_CCoption & 1) && u->HasUnitState(UNIT_STATE_CONFUSED|UNIT_STATE_STUNNED|UNIT_STATE_FLEEING|UNIT_STATE_DISTRACTED|UNIT_STATE_CONFUSED_MOVE)) + return false; + if ((m_CCoption & 2) && u->HasAuraType(SPELL_AURA_PERIODIC_DAMAGE)) + return false; + if (me->HasUnitState(UNIT_STATE_ROOT) && (ai->HasRole(BOT_ROLE_RANGED) == me->IsWithinDistInMap(u, 8.f))) + return false; + if (!_botPvP && me->IsPvP() && u->IsControlledByPlayer()) + return false; + if (u->GetCreatureType() == CREATURE_TYPE_CRITTER) + return false; + if (!_source->IsWithinDistInMap(u, max_range)) + return false; + if (!free && !ai->CanBotAttack(u)) + return false; + //if (ai->InDuel(u)) + // return false; + + if (!ai->IsInBotParty(u->GetVictim())) + return false; + + if (free) + { + if (u->IsControlledByPlayer()) + return false; + if (!me->IsValidAttackTarget(u) || !u->isTargetableForAttack(false)) + return false; + } + + return true; + } + private: + Unit const* me; + float max_range; + bot_ai const* ai; + uint8 m_CCoption; + bool free; + WorldObject const* _source; + NearbyHostileUnitCheck(NearbyHostileUnitCheck const&); +}; + +class NearbyHostileUnitInConeCheck +{ + public: + explicit NearbyHostileUnitInConeCheck(Unit const* unit, float maxdist, bot_ai const* m_ai) : + me(unit), max_range(maxdist), ai(m_ai), cone(float(M_PI)/2) + { + free = ai->IAmFree(); + } + bool operator()(Unit const* u) const + { + if (u == me) + return false; + //if (me->HasUnitState(UNIT_STATE_ROOT) && (ai->HasRole(BOT_ROLE_RANGED) == me->IsWithinDistInMap(u, 8.f))) + // return false; + if (/*!free && */!u->IsInCombat()) + return false; + if (!u->InSamePhase(me)) + return false; + if (u->HasUnitState(UNIT_STATE_CONFUSED|UNIT_STATE_STUNNED|UNIT_STATE_FLEEING|UNIT_STATE_DISTRACTED|UNIT_STATE_CONFUSED_MOVE)) + return false; + if (!free && !ai->CanBotAttack(u)) + return false; + if (!_botPvP && me->IsPvP() && u->IsControlledByPlayer()) + return false; + if (!me->IsWithinDistInMap(u, max_range)) + return false; + if (!me->HasInArc(cone, u)) + return false; + //if (ai->InDuel(u)) + // return false; + + if (!ai->IsInBotParty(u->GetVictim())) + return false; + + if (free) + { + if (u->IsControlledByPlayer()) + return false; + if (!me->IsValidAttackTarget(u) || !u->isTargetableForAttack(false)) + return false; + } + + return true; + } + private: + Unit const* me; + float max_range; + bot_ai const* ai; + float cone; + bool free; + NearbyHostileUnitInConeCheck(NearbyHostileUnitInConeCheck const&); +}; + +class NearbyFriendlyUnitCheck +{ + public: + explicit NearbyFriendlyUnitCheck(Unit const* unit, float maxdist, bot_ai const* m_ai) : me(unit), max_range(maxdist), ai(m_ai) { } + bool operator()(Unit const* u) const + { + if (u == me) + return false; + //if (!u->IsInCombat()) + // return false; + if (!u->IsAlive()) + return false; + if (u->HasUnitState(UNIT_STATE_ISOLATED)) + return false; + //if (u->IsTotem() || u->IsSummon()) + // return false; + if (!u->InSamePhase(me)) + return false; + if (!me->IsWithinDistInMap(u, max_range)) + return false; + //if (ai->InDuel(u)) + // return false; + if (!ai->IsInBotParty(u)) + return false; + if (!me->CanSeeOrDetect(u)) + return false; + if (!me->IsValidAssistTarget(u)) + return false; + + return true; + } + private: + Unit const* me; + float max_range; + bot_ai const* ai; + NearbyFriendlyUnitCheck(NearbyFriendlyUnitCheck const&); +}; + +class FarTauntUnitCheck +{ + public: + explicit FarTauntUnitCheck(Unit const* unit, float maxdist, bool ally, bot_ai const* m_ai) : + me(unit), max_range(maxdist), targetAlly(ally), ai(m_ai) { } + bool operator()(Unit const* u) const + { + if (u == me) + return false; + if (!u->IsAlive()) + return false; + if (!u->IsInCombat()) + return false; + if (!u->InSamePhase(me)) + return false; + if (u->HasUnitState(UNIT_STATE_CONFUSED|UNIT_STATE_STUNNED|UNIT_STATE_FLEEING|UNIT_STATE_DISTRACTED|UNIT_STATE_CONFUSED_MOVE)) + return false; + if (!u->GetVictim() || u->GetVictim() == me) + return false; + if (!u->CanHaveThreatList()) + return false; + if (u->HasAuraType(SPELL_AURA_MOD_TAUNT)) + return false; + if (!ai->IsInBotParty(u->GetVictim())) + return false; + if (ai->GetBotClass() == BOT_CLASS_WARRIOR && u->IsImmunedToSpell(sSpellMgr->GetSpellInfo(355), me)) + return false; //taunt + else if (ai->GetBotClass() == BOT_CLASS_PALADIN && u->IsImmunedToSpell(sSpellMgr->GetSpellInfo(62124), me)) + return false; //HoR + else if (ai->GetBotClass() == BOT_CLASS_DRUID && u->IsImmunedToSpell(sSpellMgr->GetSpellInfo(6795), me)) + return false; //Growl + + if (!me->IsValidAttackTarget(u) || !u->isTargetableForAttack(false)) + return false; + + if (me->GetDistance(targetAlly ? u->GetVictim() : u) > max_range) + return false; + + if (ai->IsTank(u->GetVictim())) + { + if (!ai->IsTank(me)) + return false; + + const bool isofftank = ai->IsOffTank(me); + const bool vofftank = ai->IsOffTank(u->GetVictim()); + if (isofftank && (vofftank || !ai->IsPointedOffTankingTarget(u))) + return false; + else if (!isofftank && vofftank && !ai->IsPointedTankingTarget(u)) + return false; + } + + return true; + } + private: + const Unit* const me; + const float max_range; + const bool targetAlly; + const bot_ai* const ai; + FarTauntUnitCheck(FarTauntUnitCheck const&); +}; + +class ManaDrainUnitCheck +{ + public: + explicit ManaDrainUnitCheck(Unit const* unit, float maxdist, bot_ai const* ai) : me(unit), max_range(maxdist), ai(ai) + { maxPool = me->GetMaxPower(POWER_MANA) * 3 / 2; free = ai->IAmFree(); } + bool operator()(Unit const* u) + { + if (!_botPvP && me->IsPvP() && u->IsControlledByPlayer()) + return false; + if (u == me) + return false; + if (!u->IsAlive()) + return false; + if (!u->IsInCombat()) + return false; + if (!u->InSamePhase(me)) + return false; + if (u->GetTypeId() == TYPEID_PLAYER && !u->HasUnitState(UNIT_STATE_CONFUSED|UNIT_STATE_STUNNED|UNIT_STATE_FLEEING|UNIT_STATE_DISTRACTED|UNIT_STATE_CONFUSED_MOVE)) + return false; + //if (u->IsControlledByPlayer()) + // return false; + if (free) + { + if (!me->IsValidAttackTarget(u) || !u->isTargetableForAttack()) + return false; + if (ai->IsInBotParty(u)) + return false; + } + //if (ai->InDuel(u)) + // return false; + if (u->GetPowerType() != POWER_MANA) + return false; + if (u->GetMaxPower(POWER_MANA) < maxPool) + return false; + if (u->GetPower(POWER_MANA)*10/(u->GetMaxPower(POWER_MANA)/10) < 15) + return false; + if (!me->HasInArc(float(M_PI)*0.5f, u)) + return false; + if (me->GetDistance(u) > max_range) + return false; + + maxPool = u->GetMaxPower(POWER_MANA); + return true; + } + private: + Unit const* me; + float max_range; + bot_ai const* ai; + uint32 maxPool; + bool free; + ManaDrainUnitCheck(ManaDrainUnitCheck const&); +}; + +class NearbyRezTargetCheck +{ + public: + explicit NearbyRezTargetCheck(Unit const* unit, float maxdist, bot_ai const* m_ai) : me(unit), max_range(maxdist), ai(m_ai) { } + bool operator()(WorldObject const* u) const + { + if (u == me) + return false; + if (u->GetTypeId() != TYPEID_PLAYER && u->GetTypeId() != TYPEID_CORPSE) + return false; + if (!u->InSamePhase(me)) + return false; + if (!me->IsWithinDistInMap(u, max_range)) + return false; + if (!me->CanSeeOrDetect(u)) + return false; + if (Player const* p = u->IsPlayer() ? u->ToPlayer() : ObjectAccessor::FindPlayer(u->ToCorpse()->GetOwnerGUID())) + { + if (p->IsAlive()) + return false; + if (p->IsResurrectRequested()) + return false; + if (p->GetUInt32Value(PLAYER_SELF_RES_SPELL)) + return false; + if (!ai->IsInBotParty(p)) + return false; + } + else + return false; + + return true; + } + private: + Unit const* me; + float max_range; + bot_ai const* ai; + NearbyRezTargetCheck(NearbyRezTargetCheck const&); +}; + +class NearestLockedGameObjectInRangeCheck +{ +public: + NearestLockedGameObjectInRangeCheck(WorldObject const* unit, float range) : _unit(unit), _range(range) { } + bool operator()(GameObject* go) + { + if (go->GetGOInfo()->GetLockId() && + !go->HasFlag(GO_FLAG_IN_USE) && + //go->getLootState() == GO_READY && + _unit->IsWithinDistInMap(go, _range)) + { + _range = _unit->GetExactDist(go); + return true; + } + return false; + } +private: + WorldObject const* _unit; + float _range; + + NearestLockedGameObjectInRangeCheck(NearestLockedGameObjectInRangeCheck const&); +}; + +class NearestVehicleWithEmptySeatInRangeCheck +{ +public: + NearestVehicleWithEmptySeatInRangeCheck(WorldObject const* unit, float range, Unit const* exveh) : _unit(unit), _range(range), _exveh(exveh) + { ASSERT(_unit->isType(TYPEMASK_UNIT)); } + bool operator()(Unit* u) + { + if (u->GetTypeId() == TYPEID_UNIT && u->IsVehicle() && u->IsAlive() && u != _exveh && + u->GetVehicleKit()->GetAvailableSeatCount() > 0 && _unit->IsWithinDistInMap(u, _range)) + { + _range = _unit->GetExactDist(u); + return true; + } + return false; + } +private: + WorldObject const* _unit; + float _range; + Unit const* _exveh; //only compare, may be NULL + + NearestVehicleWithEmptySeatInRangeCheck(NearestVehicleWithEmptySeatInRangeCheck const&); +}; + +//Professions +class NearbyObjectBySkillCheck +{ +public: + NearbyObjectBySkillCheck(WorldObject const* checker, float const range, uint32 skillMask) : + _checker(checker), _range(range), _skillMask(skillMask) { ASSERT(_checker->GetTypeId() == TYPEID_PLAYER); } + + bool operator()(WorldObject const* ob) + { + if (!_checker->IsWithinDistInMap(ob, _range)) + return false; + + if (GameObject const* go = ob->ToGameObject()) + { + if (/*go->getLootState() == GO_READY && */go->isSpawned() && go->GetGOInfo()->GetLockId() && + go->IsLootAllowedFor(_checker->ToPlayer()) && _checker->CanSeeOrDetect(go) && _checker->ToPlayer()->HaveAtClient(ob)) + { + if (LockEntry const* lockInfo = sLockStore.LookupEntry(go->GetGOInfo()->GetLockId())) + { + for (uint8 i = 0; i != MAX_LOCK_CASE; ++i) + { + if (lockInfo->Type[i] == LOCK_KEY_SKILL && + CheckSkill(SkillByLockType(LockType(lockInfo->Index[i]))) && + lockInfo->Skill[i] <= MaxSkillForLevel(_checker->ToUnit()->GetLevel())) + return true; + } + } + } + } + else if (Creature const* cre = ob->ToCreature()) + { + if (cre->IsVisible() && cre->getDeathState() == CORPSE && cre->HasUnitFlag(UNIT_FLAG_SKINNABLE) && cre->loot.isLooted() && + cre->isTappedBy(_checker->ToPlayer()) && CheckSkill(cre->GetCreatureTemplate()->GetRequiredLootSkill())) + { + if (int32(cre->GetLevel() < 20 ? (cre->GetLevel() - 10) * 10 : cre->GetLevel() * 5) <= int32(MaxSkillForLevel(_checker->ToUnit()->GetLevel()))) + return true; + } + } + return false; + } +private: + WorldObject const* _checker; + float const _range; + uint32 const _skillMask; + + inline bool CheckSkill(SkillType const skill) const + { + switch (skill) + { + case SKILL_MINING: + return (_skillMask & BOT_ROLE_GATHERING_MINING); + case SKILL_HERBALISM: + return (_skillMask & BOT_ROLE_GATHERING_HERBALISM); + case SKILL_SKINNING: + return (_skillMask & BOT_ROLE_GATHERING_SKINNING); + case SKILL_ENGINEERING: + return (_skillMask & BOT_ROLE_GATHERING_ENGINEERING); + default: + return false; + } + } + + inline uint32 MaxSkillForLevel(uint8 const level) const + { + return + level <= 20 ? 150 : + level <= 40 ? 225 : + level <= 60 ? 300 : + level <= 70 ? 375 : 450; + + //return level <= 60 ? level * 5 : 300 + (((level - 60) * 15) / 2); + } + + NearbyObjectBySkillCheck(NearbyObjectBySkillCheck const&); +}; + +//Autolooting +class NearbyLootableCreatureCheck +{ +public: + NearbyLootableCreatureCheck(WorldObject* checker, float const range) : _checker(checker), _range(range) + { ASSERT(_checker->GetTypeId() == TYPEID_PLAYER); } + + bool operator()(Unit const* unit) + { + if (Creature const* cre = unit->ToCreature()) + if (_checker->IsWithinDistInMap(cre, _range)) + return cre->IsVisible() && cre->getDeathState() == CORPSE && + cre->HasDynamicFlag(UNIT_DYNFLAG_LOOTABLE) && + _checker->ToPlayer()->isAllowedToLoot(cre); + return false; + } +private: + WorldObject* _checker; + float const _range; + + NearbyLootableCreatureCheck(NearbyLootableCreatureCheck const&); +}; + +//AoE caster dynobject +class NearbyHostileAoEDynobjectCheck +{ + public: + explicit NearbyHostileAoEDynobjectCheck(Unit const* unit, float maxdist) : _me(unit), _range(maxdist) { } + bool operator()(WorldObject const* u) const + { + DynamicObject const* dObj = u->ToDynObject(); + if (!dObj || !dObj->GetSpellId() || !dObj->GetCaster()) + return false; + if (dObj->GetByteValue(DYNAMICOBJECT_BYTES, 0) != DYNAMIC_OBJECT_AREA_SPELL) + return false; + if (!dObj->InSamePhase(_me)) + return false; + if (!dObj->GetRadius()) + return false; + if (!dObj->IsWithinDistInMap(_me, _range)) + return false; + if (!dObj->GetCaster()->IsValidAttackTarget(_me)) + return false; + + return true; + } + private: + Unit const* _me; + float _range; + NearbyHostileAoEDynobjectCheck(NearbyHostileAoEDynobjectCheck const&); +}; + +namespace BOTAI_PRED +{ + class HealTargetExclude + { + public: + bool operator()(Unit const* target) + { + return target->IsSummon() || target->IsTotem() || (target->GetHealthPct() > 90 && target->GetMaxHealth() - target->GetHealth() < 500); + } + }; + class BuffTargetExclude + { + public: + bool operator()(Unit const* target) + { + return target->IsSummon() || target->IsTotem(); + } + }; + class DrainTargetExclude + { + public: + bool operator()(Unit const* target) + { + return target->IsTotem() || target->GetPowerType() != POWER_MANA || target->GetPower(POWER_MANA) < 1000; + } + }; + class UnitExclude + { + public: + UnitExclude(Unit const* unit) { _unit = unit; } + bool operator()(Unit const* target) + { + return target == _unit; + } + private: + Unit const* _unit; + }; + + class AuraedTargetExclude + { + public: + AuraedTargetExclude(uint32 spellId, uint8 minstacks = 0) : _spellId(spellId), _minstacks(minstacks) {} + bool operator()(Unit const* target) + { + AuraApplication const* aurApp = target->GetAuraApplicationOfRankedSpell(_spellId); + return aurApp && (!_minstacks || aurApp->GetBase()->GetStackAmount() >= _minstacks); + } + private: + uint32 _spellId; + uint8 _minstacks; + }; + + class AuraedTargetExcludeByCaster + { + public: + AuraedTargetExcludeByCaster(uint32 spellId, ObjectGuid caster, uint8 minstacks = 0) : + _spellId(spellId), _caster(caster), _minstacks(minstacks) {} + bool operator()(Unit const* target) + { + AuraApplication const* aurApp = target->GetAuraApplicationOfRankedSpell(_spellId, _caster); + return aurApp && (!_minstacks || aurApp->GetBase()->GetStackAmount() >= _minstacks); + } + private: + uint32 _spellId; + ObjectGuid _caster; + uint8 _minstacks; + }; + + class HpPctAboveExclude + { + public: + HpPctAboveExclude(float pct) : _pct(pct) {} + bool operator()(Unit const* target) + { + return target->GetHealthPct() > _pct; + } + private: + float _pct; + }; + + class UnitCombatStateExclude + { + public: + UnitCombatStateExclude(bool combat) : _combat(combat) {} + bool operator()(Unit const* target) + { + return target->IsInCombat() == _combat; + } + private: + bool _combat; + }; + + class UnitLivingStateExclude + { + public: + UnitLivingStateExclude(bool living) : _living(living) {} + bool operator()(Unit const* target) + { + return target->IsAlive() == _living; + } + private: + bool _living; + }; +}; + +#endif diff --git a/src/server/game/AI/NpcBots/bot_ai.cpp b/src/server/game/AI/NpcBots/bot_ai.cpp new file mode 100644 index 000000000..6a62b5f24 --- /dev/null +++ b/src/server/game/AI/NpcBots/bot_ai.cpp @@ -0,0 +1,20134 @@ +#include "Battleground.h" +#include "BattlegroundAB.h" +#include "BattlegroundAV.h" +#include "BattlegroundWS.h" +#include "bot_ai.h" +#include "bot_Events.h" +#include "bot_GridNotifiers.h" +#include "botdatamgr.h" +#include "botlog.h" +#include "botmgr.h" +#include "botgearscore.h" +#include "botgossip.h" +#include "botspell.h" +#include "bottext.h" +#include "botwanderful.h" +#include "bpet_ai.h" +#include "Bag.h" +#include "BattlegroundMgr.h" +#include "CellImpl.h" +#include "CharacterCache.h" +#include "CharacterDatabase.h" +#include "Chat.h" +#include "Containers.h" +#include "DatabaseEnv.h" +#include "DBCStores.h" +#include "GameEventMgr.h" +#include "GameObjectAI.h" +#include "GossipDef.h" +#include "GridNotifiersImpl.h" +#include "InstanceScript.h" +#include "Item.h" +#include "LFGMgr.h" +#include "Log.h" +#include "Loot.h" +#include "LootMgr.h" +#include "Mail.h" +#include "MapManager.h" +#include "MotionMaster.h" +#include "ObjectMgr.h" +#include "PathGenerator.h" +#include "PointMovementGenerator.h" +#include "ScriptedGossip.h" +#include "ScriptMgr.h" +#include "SpellAuraEffects.h" +#include "TemporarySummon.h" +#include "Transport.h" +#include "World.h" + +#include "G3DPosition.hpp" +/* +NpcBot System by Trickerer (https://github.com/trickerer/Trinity-Bots; onlysuffering@gmail.com) +Version 5.2.77a +Original idea: https://bitbucket.org/lordpsyan/trinitycore-patches/src/3b8b9072280e/Individual/11185-BOTS-NPCBots.patch +TODO: +dk pets (garg, aod, rdw) +'Go there and do stuff' scenarios +Encounter Scenarios +Notes: +Methods may have null arg1 (Unit*): +DamageTaken(Unit*, ), JustDied(Unit*, ), OwnerAttackedBy(Unit*, ), HealReceived(Unit*, ) +Possibly others +*/ + +#ifdef _MSC_VER +# pragma warning(push, 4) +#endif + +static constexpr GossipOptionIcon BOT_ICON_ON = GOSSIP_ICON_BATTLE; +static constexpr GossipOptionIcon BOT_ICON_OFF = GOSSIP_ICON_CHAT; + +static constexpr uint32 MAX_AMMO_LEVEL = 13; +static constexpr uint8 AmmoDPSForLevel[MAX_AMMO_LEVEL][2] = +{ + { 1, 1 }, + { 5, 2 }, + { 10, 3 }, + { 15, 4 }, + { 25, 7 }, + { 30, 8 }, + { 37, 12 }, + { 44, 15 }, + { 52, 17 }, + { 57, 26 }, + { 62, 43 }, + { 72, 67 }, + { 80, 91 } +}; +static constexpr uint32 MAX_POTION_SPELLS = 8; +static constexpr uint32 MAX_FEAST_SPELLS = 11; +static constexpr uint32 ManaPotionSpells[MAX_POTION_SPELLS][2] = +{ + { 5, 437 }, + { 14, 438 }, + { 22, 2023 }, + { 31, 11903 }, + { 41, 17530 }, + { 49, 17531 }, + { 55, 28499 }, + { 70, 43186 } +}; +static constexpr uint32 HealingPotionSpells[MAX_POTION_SPELLS][2] = +{ + { 1, 439 }, + { 3, 440 }, + { 12, 441 }, + { 21, 2024 }, + { 35, 4042 }, + { 45, 17534 }, + { 55, 28495 }, + { 70, 43185 } +}; +static constexpr uint32 DrinkSpells[MAX_FEAST_SPELLS][2] = +{ + { 1, 430 }, + { 5, 431 }, + { 15, 432 }, + { 25, 1133 }, + { 35, 1135 }, + { 45, 1137 }, + { 60, 34291 }, + { 65, 27089 }, + { 70, 43182 }, + { 75, 43183 }, + { 80, 57073 } +}; +static constexpr uint32 EatSpells[MAX_FEAST_SPELLS][2] = +{ + { 1, 433 }, + { 5, 434 }, + { 15, 435 }, + { 25, 1127 }, + { 35, 1129 }, + { 45, 1131 }, + { 55, 27094 }, + { 65, 35270 }, + { 70, 43180 }, //req 65 but + { 75, 45548 }, + { 80, 45548 } +}; +uint8 GroupIconsFlags[TARGET_ICONS_COUNT] = +{ + /*STAR = */0x001, + /*CIRCLE = */0x002, + /*DIAMOND = */0x004, + /*TRIANGLE = */0x008, + /*MOON = */0x010, + /*SQUARE = */0x020, + /*CROSS = */0x040, + /*SKULL = */0x080 +}; + +struct TSpellSummary +{ + uint8 Targets; // set of enum SelectTarget + uint8 Effects; // set of enum SelectEffect +}; +extern TSpellSummary* SpellSummary; + +void ApplyBotPercentModFloatVar(float &var, float val, bool apply) +{ + var *= (apply ? ((100.f + val) / 100.f) : (100.f / (100.f + val))); +} + +static uint16 __rand; //calculated for each bot separately once every updateAI tick + +static std::set BotCustomSpells; + +bot_ai::bot_ai(Creature* creature) : CreatureAI(creature) +{ + //moved + _potionTimer = 0; + + _classinfo = new PlayerClassLevelInfo(); + + for (uint8 i = BOT_SLOT_MAINHAND; i != BOT_INVENTORY_SIZE; ++i) + for (uint8 j = 0; j != MAX_BOT_ITEM_MOD; ++j) + _stats[i][j] = 0; + + for (uint8 i = BOT_SLOT_MAINHAND; i != BOT_INVENTORY_SIZE; ++i) + _equips[i] = nullptr; + + _powersTimer = 0; + _chaseTimer = 0; + _engageTimer = 0; + + _unreachableCount = 0; + _jumpCount = 0; + _evadeCount = 0; + + _lastTargetGuid = ObjectGuid::Empty; + + checkMasterTimer = urand(5000, 15000); + feast_health = false; + feast_mana = false; + spawned = false; + firstspawn = true; + _evadeMode = false; + _atHome = true; + _roleMask = 0; + _healHpPctThreshold = 95; + _primaryIconTank = -1; + _primaryIconDamage = -1; + haste = 0; + hit = 0.f; + parry = 0.f; + dodge = 0.f; + block = 0.f; + crit = 0.f; + dmg_taken_phy = 1.f; + dmg_taken_mag = 1.f; + armor_pen = 0.f; + resilience = 0.f; + expertise = 0; + spellpower = 0; + spellpen = 0; + defense = 0; + blockvalue = 1; + regenTimer = 0; + m_botSpellInfo = nullptr; + waitTimer = 0; + _moveBehindTimer = 0; + itemsAutouseTimer = 0; + _usableItemSlotsMask = 0; + evadeDelayTimer = 0; + indoorsTimer = 0; + outdoorsTimer = 0; + GC_Timer = 0; + lastdiff = 0; + _energyFraction = 0.f; + _updateTimerMedium = 0; + _updateTimerEx1 = urand(12000, 15000); + _updateTimerEx2 = urand(8000, 12000); + checkAurasTimer = 0; + roleTimer = 0; + ordersTimer = 0; + doHealth = false; + doMana = false; + //shouldUpdateStats = true; + movepos.m_positionX = 0.f; + movepos.m_positionY = 0.f; + movepos.m_positionZ = 0.f; + aftercastTargetGuid = ObjectGuid::Empty; + + shouldEnterVehicle = false; + + _saveDisabledSpellsTimer = 0; + _saveDisabledSpells = false; + + _deathsCount = 0; + _killsCount = 0; + _pvpKillsCount = 0; + _playerKillsCount = 0; + + for (uint8 i = SPELL_SCHOOL_HOLY; i != MAX_SPELL_SCHOOL; ++i) + resistbonus[i - 1] = 0; + + botPet = nullptr; + canUpdate = true; + _duringTeleport = false; + _canAppearInWorld = false; + + teleHomeEvent = nullptr; + teleFinishEvent = nullptr; + awaitStateRemEvent = nullptr; + + _lastZoneId = 0; + _lastAreaId = 0; + _lastWMOAreaId = 0; + + _selfrez_spell_id = 0; + + _wmoAreaUpdateTimer = 0; + + _contestedPvPTimer = 0; + _groupUpdateTimer = BOT_GROUP_UPDATE_TIMER; + + _ownerGuid = 0; + + _wanderer = false; + _baseLevel = 0; + _travel_node_last = nullptr; + _travel_node_cur = nullptr; + + _groupUpdateMask = 0; + _auraRaidUpdateMask = 0; + _bg = nullptr; + + opponent = nullptr; + disttarget = nullptr; + + ResetBotAI(BOTAI_RESET_INIT); + + if (!IsTempBot()) + BotDataMgr::RegisterBot(me); +} +bot_ai::~bot_ai() +{ + TC_LOG_INFO("scripts", "bot_ai destructor call for {} ({})", me->GetName(), me->GetEntry()); + + while (!_spells.empty()) + { + BotSpellMap::iterator itr = _spells.begin(); + delete itr->second; + _spells.erase(itr); + } + + for (uint8 i = BOT_SLOT_MAINHAND; i != BOT_INVENTORY_SIZE; ++i) + if (_equips[i]) + delete _equips[i]; + + delete _classinfo; + + BotDataMgr::UnregisterBot(me); +} + +uint16 bot_ai::Rand() const +{ + return __rand; +} +//0-178 +void bot_ai::GenerateRand() const +{ + __rand = urand(0, IAmFree() ? 100 : 100 + (master->GetNpcBotsCount() - 1) * 2); +} + +const std::string& bot_ai::LocalizedNpcText(Player const* forPlayer, uint32 textId) +{ + LocaleConstant loc = forPlayer ? forPlayer->GetSession()->GetSessionDbLocaleIndex() : sWorld->GetDefaultDbcLocale(); + + if (GossipText const* nt = sObjectMgr->GetGossipText(textId)) + { + std::wstring wnamepart; + NpcTextLocale const* ntl = sObjectMgr->GetNpcTextLocale(textId); + if (loc != DEFAULT_LOCALE && ntl && ntl->Text_0[0].size() > size_t(loc) && !ntl->Text_0[0][loc].empty() && Utf8FitTo(ntl->Text_0[0][loc], wnamepart)) + return ntl->Text_0[0][loc]; + else + return nt->Options[0].Text_0; + } + + { + static std::map unk_botstrings; + + if (!unk_botstrings.contains(textId)) + { + TC_LOG_ERROR("entities.player", "NPCBots: bot text string #{} is not localized, at least for {}", + textId, localeNames[loc]); + + std::ostringstream msg; + msg << (loc == DEFAULT_LOCALE ? ""; + unk_botstrings[textId] = msg.str(); + } + + return unk_botstrings[textId]; + } +} + +void bot_ai::InitializeAI() +{ + if (!me->GetSpawnId() && !IsTempBot()) + SetWanderer(); + + Reset(); +} + +void bot_ai::BotSay(const std::string &text, Player const* target) const +{ + if (!target && master->GetTypeId() == TYPEID_PLAYER) + target = master; + if (!target) + return; + + me->Say(text, LANG_UNIVERSAL, target); +} +void bot_ai::BotWhisper(const std::string &text, Player const* target) const +{ + if (!target && master->GetTypeId() == TYPEID_PLAYER) + target = master; + if (!target) + return; + + Player* playerTarget = const_cast(target); + + me->Whisper(text, LANG_UNIVERSAL, playerTarget); +} +void bot_ai::BotYell(const std::string &text, Player const* /*target*/) const +{ + //if (!target && master->GetTypeId() == TYPEID_PLAYER) + // target = master; + //if (!target) + // return; + + me->Yell(text, LANG_UNIVERSAL); +} +void bot_ai::BotSay(std::string&& text, Player const* target) const +{ + if (!target && master->GetTypeId() == TYPEID_PLAYER) + target = master; + if (!target) + return; + + me->Say(text, LANG_UNIVERSAL, target); +} +void bot_ai::BotWhisper(std::string&& text, Player const* target) const +{ + if (!target && master->GetTypeId() == TYPEID_PLAYER) + target = master; + if (!target) + return; + + Player* playerTarget = const_cast(target); + + me->Whisper(text, LANG_UNIVERSAL, playerTarget); +} +void bot_ai::BotYell(std::string&& text, Player const* /*target*/) const +{ + me->Yell(text, LANG_UNIVERSAL); +} + +void bot_ai::ReportSpellCast(uint32 spellId, const std::string& followedByString, Player const* target) const +{ + std::string spellName; + _LocalizeSpell(target, spellName, spellId); + BotWhisper(spellName + followedByString, target); +} + +bool bot_ai::SetBotOwner(Player* newowner) +{ + ASSERT(newowner, "Trying to set NULL owner!!!"); + ASSERT(newowner->GetGUID().IsPlayer(), "Trying to set a non-player as owner!!!"); + //ASSERT(master->GetGUID() == me->GetGUID()); + //ASSERT(IAmFree()); + + //have master already + if (master->GetGUID() != me->GetGUID()) + { + TC_LOG_ERROR("entities.player", "bot_ai::SetBotOwner(): bot {} (id: {}) has master {} while trying to set to {}...", + me->GetName(), me->GetEntry(), master->GetName(), newowner->GetName()); + return false; + } + if (!IAmFree()) + { + TC_LOG_ERROR("entities.player", "bot_ai::SetBotOwner(): minion bot {} (id: {}) IS NOT FREE (has master {}) while trying to set to {}", + me->GetName(), me->GetEntry(), master->GetName(), newowner->GetName()); + return false; + } + + if (newowner->GetBotMgr()->AddBot(me) & BOT_ADD_FATAL) + { + checkMasterTimer += 30000; + return false; + } + + spawned = false; + + (const_cast(me->GetCreatureTemplate()))->unit_flags2 &= ~(UNIT_FLAG2_ALLOW_ENEMY_INTERACT); + me->ReplaceAllUnitFlags2(UnitFlags2(me->GetCreatureTemplate()->unit_flags2)); + + //recursive + if (master->GetGUID() == newowner->GetGUID()) + return true; + + master = newowner; + _ownerGuid = newowner->GetGUID().GetCounter(); + _checkOwershipTimer = BotMgr::GetOwnershipExpireTime() ? CalculateOwnershipCheckTime() : 0; + + return true; +} +//Check if should totally unlink from owner +void bot_ai::CheckOwnerExpiry() +{ + if (!BotMgr::GetOwnershipExpireTime()) + return; //disabled + + if (IsTempBot() || !IAmFree()) + return; + + NpcBotData const* npcBotData = BotDataMgr::SelectNpcBotData(me->GetEntry()); + ASSERT(npcBotData, "bot_ai::CheckOwnerExpiry(): data not found!"); + + NpcBotExtras const* npcBotExtra = BotDataMgr::SelectNpcBotExtras(me->GetEntry()); + ASSERT(npcBotExtra, "bot_ai::CheckOwnerExpiry(): extra data not found!"); + + if (npcBotData->owner == 0) + return; + + ObjectGuid ownerGuid = ObjectGuid(HighGuid::Player, 0, npcBotData->owner); + time_t timeNow = time(0); + time_t expireTime = time_t(BotMgr::GetOwnershipExpireTime()); + time_t baseTimeStamp; + + if (BotMgr::GetOwnershipExpireMode() == BOT_OWNERSHIP_EXPIRE_OFFLINE) + { + uint32 accId = sCharacterCache->GetCharacterAccountIdByGuid(ownerGuid); + QueryResult result = accId ? CharacterDatabase.PQuery("SELECT MAX(logout_time) FROM characters WHERE account = {}", accId) : nullptr; + + Field* fields = result ? result->Fetch() : nullptr; + time_t lastLoginTime = fields ? time_t(fields[0].GetUInt32()) : timeNow; + baseTimeStamp = lastLoginTime; + } + else //if (BotMgr::GetOwnershipExpireMode() == BOT_OWNERSHIP_EXPIRE_HIRE) + { + baseTimeStamp = time_t(npcBotData->hire_time); + } + + //either expired or owner does not exist + if (timeNow >= baseTimeStamp + expireTime) + { + std::string name = "unknown"; + sCharacterCache->GetCharacterNameByGuid(ownerGuid, name); + TC_LOG_DEBUG("npcbots", "{}'s (guid: {}) ownership over bot {} ({}) has expired!", + name, npcBotData->owner, me->GetName(), me->GetEntry()); + + //send all items back + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_NPCBOT_EQUIP_BY_ITEM_INSTANCE); + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 + //"SELECT creatorGuid, giftCreatorGuid, count, duration, charges, flags, enchantments, randomPropertyId, durability, playedTime, text, guid, itemEntry, owner_guid " + // "FROM item_instance WHERE guid IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_SYNCH + + for (uint8 i = 0; i != BOT_INVENTORY_SIZE; ++i) + stmt->setUInt32(i, npcBotData->equips[i] ? npcBotData->equips[i] : std::numeric_limits::max()); + + PreparedQueryResult iiresult = CharacterDatabase.Query(stmt); + if (iiresult) + { + std::vector items; + + do + { + Field* fields2 = iiresult->Fetch(); + uint32 itemGuidLow = fields2[11].GetUInt32(); + uint32 itemId = fields2[12].GetUInt32(); + uint8 item_idx = std::numeric_limits::max(); + + for (uint8 i = 0; i != BOT_INVENTORY_SIZE; ++i) + { + if (_equips[i] && _equips[i]->GetEntry() == itemId) + { + item_idx = i; + break; + } + } + if (item_idx >= BOT_INVENTORY_SIZE) + TC_LOG_ERROR("npcbots", "bot_ai::CheckOwnerExpiry(): item id {} guid {} not found in bot's inventory!\n{}", itemId, itemGuidLow, me->GetGUID().ToString()); + + items.push_back(_equips[item_idx]); + _removeEquipment(item_idx); + + } while (iiresult->NextRow()); + + CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); + while (!items.empty()) + { + static const std::string subject = LocalizedNpcText(nullptr, BOT_TEXT_OWNERSHIP_EXPIRED); + MailDraft draft(subject, ""); + for (uint8 i = 0; !items.empty() && i < MAX_MAIL_ITEMS; ++i) + { + Item* item = items.back(); + items.pop_back(); + item->SetOwnerGUID(ownerGuid); + item->FSetState(ITEM_CHANGED); + item->SaveToDB(trans); + draft.AddItem(item); + } + draft.SendMailTo(trans, MailReceiver(npcBotData->owner), MailSender(me, MAIL_STATIONERY_GM)); + } + CharacterDatabase.CommitTransaction(trans); + + for (uint8 slot = BOT_SLOT_MAINHAND; slot <= BOT_SLOT_RANGED; ++slot) + _resetEquipment(slot, ObjectGuid::Empty); + + BotDataMgr::UpdateNpcBotData(me->GetEntry(), NPCBOT_UPDATE_EQUIPS, _equips); + } + + //hard reset owner + _ownerGuid = 0; + uint32 newOwner = 0; + BotDataMgr::UpdateNpcBotData(me->GetEntry(), NPCBOT_UPDATE_OWNER, &newOwner); + //...spec + uint8 spec = SelectSpecForClass(npcBotExtra->bclass); + BotDataMgr::UpdateNpcBotData(me->GetEntry(), NPCBOT_UPDATE_SPEC, &spec); + //...and roles + uint32 roleMask = DefaultRolesForClass(npcBotExtra->bclass, spec); + BotDataMgr::UpdateNpcBotData(me->GetEntry(), NPCBOT_UPDATE_ROLES, &roleMask); + + if (Group* gr = GetGroup()) + gr->RemoveMember(me->GetGUID()); + } +} + +void bot_ai::InitUnitFlags() +{ + if (BotMgr::DisplayEquipment() == true && CanDisplayNonWeaponEquipmentChanges()) + { + (const_cast(me->GetCreatureTemplate()))->unit_flags2 |= UNIT_FLAG2_MIRROR_IMAGE; + me->ReplaceAllUnitFlags2(UnitFlags2(me->GetCreatureTemplate()->unit_flags2)); + } + (const_cast(me->GetMovementTemplate())).Chase = CreatureChaseMovementType::CanWalk; +} + +void bot_ai::ResetBotAI(uint8 resetType) +{ + //ASSERT(me->IsInWorld()); + + _botCommandState = 0; + _botAwaitState = BOT_AWAIT_NONE; + _reviveTimer = 0; + + master = reinterpret_cast(me); + if (resetType & BOTAI_RESET_MASK_ABANDON_MASTER) + _ownerGuid = 0; + if (resetType == BOTAI_RESET_INIT || resetType == BOTAI_RESET_LOGOUT) + { + NpcBotData const* npcBotData = BotDataMgr::SelectNpcBotData(me->GetEntry()); + _checkOwershipTimer = (BotMgr::GetOwnershipExpireTime() && npcBotData->owner) ? + ((resetType == BOTAI_RESET_INIT || BotMgr::GetOwnershipExpireMode() == BOT_OWNERSHIP_EXPIRE_HIRE) ? 1000 : CalculateOwnershipCheckTime()) : 0; + if (resetType == BOTAI_RESET_INIT) + homepos.Relocate(me); + else //if (resetType == BOTAI_RESET_LOGOUT) + _saveStats(); + } + + if (!IsWanderer() || BotMgr::IsWanderingWorldBot(me)) + { + (const_cast(me->GetCreatureTemplate()))->unit_flags2 |= (UNIT_FLAG2_ALLOW_ENEMY_INTERACT); + me->ReplaceAllUnitFlags2(UnitFlags2(me->GetCreatureTemplate()->unit_flags2)); + } + + if ((resetType == BOTAI_RESET_DISMISS || resetType == BOTAI_RESET_LOGOUT) && !IsTempBot()) + { + EnableAllSpells(resetType == BOTAI_RESET_DISMISS); + InitRoles(); + } + + //me->IsAIEnabled = true; + canUpdate = true; + + if (spawned) + ReturnHome(); + + if (!me->IsInWorld() || resetType == BOTAI_RESET_FORCERECALL) + { + TeleportHomeStart(resetType != BOTAI_RESET_UNBIND); + } + else + { + _atHome = false; + spawned = false; + ResetContestedPvP(); + } +} + +bool bot_ai::_checkImmunities(Unit const* target, SpellInfo const* spellInfo) const +{ + return target && spellInfo && !target->IsImmunedToDamage(spellInfo); +} + +SpellCastResult bot_ai::CheckBotCast(Unit const* victim, uint32 spellId) const +{ + if (spellId == 0) + return SPELL_FAILED_DONT_REPORT; + + if (HasBotCommandState(BOT_COMMAND_NO_CAST | BOT_COMMAND_INACTION)) + return SPELL_FAILED_DONT_REPORT; + + if (victim->GetTypeId() == TYPEID_PLAYER && victim->ToPlayer()->IsGameMaster()) + return SPELL_FAILED_BAD_TARGETS; + + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); + if (!spellInfo) + return SPELL_FAILED_DONT_REPORT; + + spellInfo = spellInfo->TryGetSpellInfoOverride(me); + + if (me->IsMounted() && !(spellInfo->Attributes & SPELL_ATTR0_CASTABLE_WHILE_MOUNTED)) + return SPELL_FAILED_NOT_MOUNTED; + + if (spellInfo->IsChanneled() && HasBotCommandState(BOT_COMMAND_NO_CAST_LONG)) + return SPELL_FAILED_NOT_IDLE; + + if (spellInfo->CastTimeEntry) + { + int32 castTime = spellInfo->CastTimeEntry->Base; + if (castTime > 0) + ApplyClassSpellCastTimeMods(spellInfo, castTime); + + if (castTime > 0) + { + if (HasBotCommandState(BOT_COMMAND_NO_CAST_LONG) || (me->GetMap()->IsDungeon() && !CCed(me, true) && IsWithinAoERadius(*me))) + return SPELL_FAILED_NOT_IDLE; + } + } + + if (int32(me->GetPower(spellInfo->PowerType)) < spellInfo->CalcPowerCost(me, spellInfo->GetSchoolMask())) + return SPELL_FAILED_NO_POWER; + + if (!IsSpellReady(spellInfo->GetFirstRankSpell()->Id, lastdiff, false)) + return SPELL_FAILED_NOT_READY; + + //if (victim->isType(TYPEMASK_UNIT) && InDuel(victim)) + // return SPELL_FAILED_BAD_TARGETS; + + if (!CanBotAttackOnVehicle()) + return SPELL_FAILED_CASTER_AURASTATE; + + //forced to follow but not close enough to master + if (!IAmFree() && !master->GetBotMgr()->GetBotAllowCombatPositioning()) + { + Position mpos; + _calculatePos(master, mpos); + + if (me->GetDistance(mpos) > float(std::max(5, master->GetBotMgr()->GetBotFollowDist() / 8))) + return SPELL_FAILED_NOT_IDLE; + } + + //scaling aura + if (victim->isType(TYPEMASK_UNIT) && victim != me && + !spellInfo->IsPassive() && spellInfo->SpellLevel && !spellInfo->IsChanneled() && + (victim->GetLevel() + 10) < int32(spellInfo->GetFirstRankSpell()->SpellLevel)) + { + for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) + { + if (spellInfo->_effects[i].Effect == SPELL_EFFECT_APPLY_AURA && spellInfo->IsPositiveEffect(i)) + { + //TC_LOG_ERROR("scripts", "CheckBotCast(): deny cast of {} by {} on low-level target {} (lvl {})", + // spellInfo->SpellName[0], me->GetName(), victim->GetName(), victim->GetLevel()); + return SPELL_FAILED_LOWLEVEL; + } + } + } + + //disarmed + if (spellInfo->EquippedItemClass == ITEM_CLASS_WEAPON) + { + if (spellInfo->EquippedItemInventoryTypeMask != 0) + { + if ((spellInfo->EquippedItemInventoryTypeMask & (1<CanUseAttackType(BASE_ATTACK)) + return SPELL_FAILED_EQUIPPED_ITEM_CLASS_MAINHAND; + if ((spellInfo->EquippedItemInventoryTypeMask & (1<CanUseAttackType(OFF_ATTACK)) + return SPELL_FAILED_EQUIPPED_ITEM_CLASS_OFFHAND; + if ((spellInfo->EquippedItemInventoryTypeMask & ((1<CanUseAttackType(RANGED_ATTACK)) + return SPELL_FAILED_EQUIPPED_ITEM_CLASS; + } + else if (!me->CanUseAttackType(BASE_ATTACK)) + return SPELL_FAILED_EQUIPPED_ITEM_CLASS_MAINHAND; + } + + //immunities + if (victim->isType(TYPEMASK_UNIT)) + { + if (spellInfo->HasEffect(SPELL_EFFECT_HEAL) || spellInfo->HasAura(SPELL_AURA_PERIODIC_HEAL)) + { + //banish or something + Unit::AuraEffectList const& healPctEffects = victim->GetAuraEffectsByType(SPELL_AURA_MOD_HEALING_PCT); + if (!healPctEffects.empty()) + { + int32 castTime = spellInfo->CastTimeEntry ? spellInfo->CastTimeEntry->Base : 0; + if (castTime) + ApplyClassSpellCastTimeMods(spellInfo, castTime); + for (Unit::AuraEffectList::const_iterator itr = healPctEffects.begin(); itr != healPctEffects.end(); ++itr) + if ((*itr)->GetAmount() <= -100 && (!castTime || (*itr)->GetBase()->GetDuration() >= castTime)) + return SPELL_FAILED_BAD_TARGETS; + } + } + else if (//spells that ignore immunities + spellId != 64382 && //shattering throw + spellId != 32375 && //mass dispel + !_checkImmunities(victim, spellInfo)) + return SPELL_FAILED_BAD_TARGETS; + } + + switch (_botclass) + { + case BOT_CLASS_PALADIN: + case BOT_CLASS_MAGE: + case BOT_CLASS_PRIEST: + case BOT_CLASS_DRUID: + case BOT_CLASS_WARLOCK: + case BOT_CLASS_SHAMAN: + if (Feasting() && !master->IsInCombat() && !master->HasAuraType(SPELL_AURA_PERIODIC_DAMAGE)) + return SPELL_FAILED_CANT_DO_THAT_RIGHT_NOW; + break; + case BOT_CLASS_WARRIOR: //BladeStorm + case BOT_CLASS_BM: //BladeStorm PLACEHOLDER + case BOT_CLASS_ROGUE: //Killing Spree + if (me->HasAuraType(SPELL_AURA_ALLOW_ONLY_ABILITY)) + return SPELL_FAILED_CANT_DO_THAT_RIGHT_NOW; + break; + case BOT_CLASS_HUNTER: + case BOT_CLASS_DEATH_KNIGHT: + case BOT_CLASS_SPHYNX: + case BOT_CLASS_ARCHMAGE: + case BOT_CLASS_DREADLORD: + case BOT_CLASS_SPELLBREAKER: + case BOT_CLASS_DARK_RANGER: + case BOT_CLASS_NECROMANCER: + case BOT_CLASS_SEA_WITCH: + case BOT_CLASS_CRYPT_LORD: + break; + default: + TC_LOG_ERROR("entities.player", "CheckBotCast(): Unknown bot class {}", _botclass); + break; + } + + return SPELL_CAST_OK; +} + +bool bot_ai::doCast(Unit* victim, uint32 spellId, bool triggered) +{ + return doCast(victim, spellId, triggered ? TRIGGERED_FULL_MASK : TRIGGERED_NONE); +} + +bool bot_ai::doCast(Unit* victim, uint32 spellId, TriggerCastFlags flags) +{ + if (spellId == 0) return false; + if (!victim || !victim->IsInWorld() || me->GetMap() != victim->FindMap()) return false; + if (IsCasting()) return false; + + m_botSpellInfo = sSpellMgr->GetSpellInfo(spellId); + if (!m_botSpellInfo) + return false; + + m_botSpellInfo = m_botSpellInfo->TryGetSpellInfoOverride(me); + + //select aura level + if (victim->isType(TYPEMASK_UNIT)) + { + if (SpellInfo const* actualSpellInfo = m_botSpellInfo->GetAuraRankForLevel(victim->GetLevel())) + m_botSpellInfo = actualSpellInfo; + + if (!m_botSpellInfo->IsTargetingArea()) + { + uint8 approximateAuraEffectMask = 0; + uint8 nonAuraEffectMask = 0; + for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) + { + if (m_botSpellInfo->_effects[i].IsAura()) + approximateAuraEffectMask |= 1 << i; + else if (m_botSpellInfo->_effects[i].IsEffect()) + nonAuraEffectMask |= 1 << i; + } + + for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) + { + // check if target already has the same type, but more powerful aura + if (!nonAuraEffectMask && (approximateAuraEffectMask & (1 << i))) + if (!victim->IsHighestExclusiveAuraEffect(m_botSpellInfo, AuraType(m_botSpellInfo->_effects[i].ApplyAuraName), + m_botSpellInfo->_effects[i].CalcValue(me, &m_botSpellInfo->_effects[i].BasePoints), approximateAuraEffectMask, false)) + return false; + } + } + + if ((flags & TRIGGERED_FULL_MASK) != TRIGGERED_FULL_MASK && + !(m_botSpellInfo->AttributesEx2 & SPELL_ATTR2_CAN_TARGET_NOT_IN_LOS) && + !IsInBotParty(victim) && !me->IsWithinLOSInMap(victim, LINEOFSIGHT_ALL_CHECKS, VMAP::ModelIgnoreFlags::M2)) + return false; + } + + //check wrong spell interruption attempts + if (/*victim->isType(TYPEMASK_UNIT) && */!HasBotCommandState(BOT_COMMAND_ISSUED_ORDER) && + !CastingUnitCheck::CastInterruptionCheck(victim, m_botSpellInfo)) + return false; + + //for debug only + if (victim->isType(TYPEMASK_UNIT) && victim->isDead()) + { + if (victim->getDeathState() == DeathState::DEAD) + TC_LOG_DEBUG("npcbots", "bot_ai::doCast(): {} (bot class {}) tried to cast spell {} on a DEAD target {}", me->GetName(), _botclass, spellId, victim->GetName()); + else if (!(m_botSpellInfo->AttributesEx2 & SPELL_ATTR2_CAN_TARGET_DEAD) && + !m_botSpellInfo->HasEffect(SPELL_EFFECT_RESURRECT) && + !m_botSpellInfo->HasEffect(SPELL_EFFECT_RESURRECT_NEW) && + !m_botSpellInfo->HasEffect(SPELL_EFFECT_SELF_RESURRECT)) + TC_LOG_DEBUG("npcbots", "bot_ai::doCast(): {} (bot class {}) tried to cast spell {} on a CORPSE target {}", me->GetName(), _botclass, spellId, victim->GetName()); + //return false; + } + + //spells with cast time + if (me->isMoving() && !(flags & TRIGGERED_CAST_DIRECTLY) && !m_botSpellInfo->IsAutoRepeatRangedSpell() && (m_botSpellInfo->InterruptFlags & SPELL_INTERRUPT_FLAG_MOVEMENT) && + !m_botSpellInfo->HasAttribute(SPELL_ATTR0_ON_NEXT_SWING)) + { + int32 cast_time; + if (m_botSpellInfo->IsChanneled()) + cast_time = m_botSpellInfo->GetDuration(); + else + { + cast_time = int32(m_botSpellInfo->CalcCastTime()); + me->ModSpellCastTime(m_botSpellInfo, cast_time); + } + + if (cast_time > 0) + { + if (!HasBotCommandState(BOT_COMMAND_ISSUED_ORDER)) + { + if (JumpingOrFalling() || HasBotCommandState(BOT_COMMAND_STAY)) + return false; + if (!me->GetVictim() && me->IsInWorld() && (me->GetMap()->IsRaid() || me->GetMap()->IsHeroic()) && + !m_botSpellInfo->HasAura(SPELL_AURA_MOUNTED)) + return false; + if (!m_botSpellInfo->HasEffect(SPELL_EFFECT_HEAL) && Rand() > (IAmFree() ? 80 : 50)) + return false; + } + + me->BotStopMovement(); + } + } + + if ((!victim->isType(TYPEMASK_UNIT) || IsInBotParty(victim)) && !victim->IsWithinLOSInMap(me, LINEOFSIGHT_ALL_CHECKS, VMAP::ModelIgnoreFlags::M2) && + !HasBotCommandState(BOT_COMMAND_STAY) && !me->GetVehicle()) + { + if (!IAmFree()) + { + if (me->GetDistance(victim) > 10.f) + { + Position pos = victim->GetPosition(); + //victim->GetPosition(&pos); + BotMovement(BOT_MOVE_POINT, &pos); + //me->GetMotionMaster()->MovePoint(me->GetMapId(), pos); + } + else + me->Relocate(victim); + } + else + return false; + } + + //remove shapeshifts manually to restore powers/stats + if (me->GetShapeshiftForm() != FORM_NONE) + { + if (m_botSpellInfo->CheckShapeshift(me->GetShapeshiftForm()) != SPELL_CAST_OK) + if (!removeShapeshiftForm()) + return false; + } + + //CHECKS PASSED, NOW DO IT + + if (me->GetStandState() == UNIT_STAND_STATE_SIT && !(m_botSpellInfo->Attributes & SPELL_ATTR0_CASTABLE_WHILE_SITTING)) + { + if (!doMana && me->HasInterruptFlag(AURA_INTERRUPT_FLAG_NOT_SEATED)) + UpdateMana(); + + feast_health = false; + feast_mana = false; + me->SetStandState(UNIT_STAND_STATE_STAND); + } + + //debug + //TC_LOG_ERROR("entities.player", "CheckBotCast(): {} ({}) by {} on {}", m_botSpellInfo->SpellName[0], spellId, me->GetName(), victim->GetName()); + + bool triggered = !!(flags & TRIGGERED_CAST_DIRECTLY); + SpellCastTargets targets; + targets.SetUnitTarget(victim); + Spell* spell = new Spell(me, m_botSpellInfo, flags); + spell->prepare(targets); //sets current spell if succeed +/* + SpellCastResult result = spell->CheckCast(true); + if (result != SPELL_CAST_OK) + TC_LOG_ERROR("entities.player", "doCast(): {} ({}) by {} on {} failed with {}", + m_botSpellInfo->SpellName[0], spellId, me->GetName(), victim->GetName(), uint32(result)); +*/ + bool casted = triggered; //triggered casts are casted immediately + for (uint8 i = 0; i != CURRENT_MAX_SPELL; ++i) + { + if (me->GetCurrentSpell(i) == spell) + { + casted = true; + break; + } + } + + if (!casted) + { + //failed to cast + if (HasBotCommandState(BOT_COMMAND_ISSUED_ORDER) && + !_orders.empty() && _orders.front()._type == BOT_ORDER_SPELLCAST && + _orders.front().params.spellCastParams.baseSpell == m_botSpellInfo->GetFirstRankSpell()->Id) + { + if (DEBUG_BOT_ORDERS) + TC_LOG_ERROR("entities.player", "doCast(): ordered spell {} is not casted!", m_botSpellInfo->Id); + CancelOrder(_orders.front()); + } + + return false; + } + + if (triggered) + return true; + if (m_botSpellInfo->IsPassive() || m_botSpellInfo->IsCooldownStartedOnEvent()) + return true; + if (!m_botSpellInfo->StartRecoveryCategory || !m_botSpellInfo->StartRecoveryTime) + return true; + + float gcd = float(m_botSpellInfo->StartRecoveryTime); + + ApplyBotSpellGlobalCooldownMods(m_botSpellInfo, gcd); + //Apply haste to cooldown + if (haste && m_botSpellInfo->StartRecoveryCategory == 133 && m_botSpellInfo->StartRecoveryTime == 1500 && + m_botSpellInfo->DmgClass != SPELL_DAMAGE_CLASS_MELEE && m_botSpellInfo->DmgClass != SPELL_DAMAGE_CLASS_RANGED && + !(m_botSpellInfo->Attributes & (SPELL_ATTR0_REQ_AMMO|SPELL_ATTR0_ABILITY))) + ApplyBotPercentModFloatVar(gcd, float(haste), false); + + //if cast time is lower than 1.5 sec it also reduces gcd but only if not instant + if (m_botSpellInfo->CastTimeEntry) + { + if (int32 castTime = m_botSpellInfo->CastTimeEntry->Base) + { + if (castTime > 0) + { + ApplyClassSpellCastTimeMods(m_botSpellInfo, castTime); + if (castTime < gcd) + gcd = float(castTime); + } + } + } + + GC_Timer = uint32(gcd); + //global cd cannot be less than 1000 ms + GC_Timer = std::max(GC_Timer, 1000); + //global cd cannot be greater than 1500 ms + GC_Timer = std::min(GC_Timer, 1500); + + return true; +} +//Follow point calculation +void bot_ai::_calculatePos(Unit const* followUnit, Position& pos, float* speed/* = nullptr*/) const +{ + Player const* player = followUnit->ToPlayer(); + uint8 followdist = !player ? BotMgr::GetBotFollowDistDefault() / 2 : player->GetBotMgr()->GetBotFollowDist(); + float mydist, angle; + + if (HasRole(BOT_ROLE_TANK) && !IsTank(followUnit)) + { + uint8 tanks = player != master ? 10 : std::max(1, player->GetBotMgr()->GetNpcBotsCountByRole(BOT_ROLE_TANK)); + uint8 slot = player != master ? urand(0, 9) : player->GetBotMgr()->GetNpcBotSlotByRole(BOT_ROLE_TANK, me); + angle = float(M_PI) / 6.0f; //max bias (left of right) //total arc is angle * 2 + angle = (angle / tanks) * (slot - (slot % 2)); //bias + if (slot % 2) angle *= -1.f; //bias interchange + mydist = 3.5f; + } + else if (HasRole(BOT_ROLE_RANGED)) + { + uint8 rangeds = player != master ? 20 : std::max(1, player->GetBotMgr()->GetNpcBotsCountByRole(BOT_ROLE_RANGED)); + uint8 slot = player != master ? urand(0, 19) : player->GetBotMgr()->GetNpcBotSlotByRole(BOT_ROLE_RANGED, me); + angle = float(M_PI) / 3.5f; //max bias (left of right) //total arc is angle * 2 + angle = (angle / rangeds) * (slot - (slot % 2)); //bias + if (slot % 2) angle *= -1.f; //bias interchange + angle += float(M_PI); //behind + mydist = 1.0f; + } + else if (HasRole(BOT_ROLE_DPS)) + { + uint8 dpss = player != master ? 20 : std::max(1, player->GetBotMgr()->GetNpcBotsCountByRole(BOT_ROLE_DPS)); + uint8 slot = player != master ? urand(0, 19) : player->GetBotMgr()->GetNpcBotSlotByRole(BOT_ROLE_DPS, me); + angle = float(M_PI) / 7.5f; //max bias (left of right) //total arc is angle * 2 + angle = (angle / dpss) * (slot); //bias + if (slot % 2) angle *= -1.f; //bias interchange + angle += float(((slot % 4) < 2) ? (M_PI/2.f) : -(M_PI/2.f)); //sides + mydist = 2.0f; + } + else + { + angle = float((me->GetEntry() % 2) ? (M_PI/2.f) : -(M_PI/2.f)); + mydist = 0.5f; + } + + mydist += std::max(int32(followdist) - 30, 5) / 7.f; //1.f-10.f + mydist = std::max(mydist - 2.f, 0.0f); //get bots closer + + if (me->GetVehicle()) + mydist *= 2.f; + + Position mpos; + Unit const* bmover = me->GetVehicle() ? me->GetVehicleBase() : me; + Unit const* mmover = followUnit->GetVehicle() ? followUnit->GetVehicleBase() : followUnit; + uint32 movFlags = mmover->m_movementInfo.GetMovementFlags(); + float size = bmover->GetCombatReach() * 2; + if (bmover->CanFly()) + { + angle += mmover->GetOrientation(); + float &x = mpos.m_positionX, &y = mpos.m_positionY, &z = mpos.m_positionZ; + bool over = false; + for (uint8 i = 0; i != 5 + over; ++i) + { + if (over) + { + mydist *= 0.2f; + break; + } + mmover->GetNearPoint(bmover, x, y, z, mydist, angle); + if (!followUnit->IsWithinLOS(x,y,z)) //try to get much closer to follow unit + { + mydist *= 0.4f - float(i*0.07f); + size *= 0.1f; + if (size < 0.1) + size = 0.f; + } + else + over = true; + } + } + else + mpos = mmover->GetFirstCollisionPosition(mydist+size, angle); + + if ((movFlags & MOVEMENTFLAG_FORWARD) && !(movFlags & MOVEMENTFLAG_FALLING_FAR)) + { + float const aheadDist = std::max(followdist * 0.08f, 6.f); + float tx = mpos.m_positionX + aheadDist * std::cos(mmover->GetOrientation()); + float ty = mpos.m_positionY + aheadDist * std::sin(mmover->GetOrientation()); + float tz = mpos.m_positionZ; + if (!bmover->CanFly()) + bmover->UpdateAllowedPositionZ(tx, ty, tz); + if (mmover->IsWithinLOS(tx, ty, tz)) + mpos.Relocate(tx, ty, tz); + } + + if (me->GetPositionZ() < mpos.GetPositionZ()) + mpos.m_positionZ += 0.5f; //prevent going underground while moving + + if (speed && !IAmFree() && player == master) + { + const float posdist = bmover->GetDistance(mpos); + if (mmover->IsWalking() || HasBotCommandState(BOT_COMMAND_WALK)) + { + const float basewalkspeed = bmover->GetSpeed(MOVE_WALK); + *speed = basewalkspeed; + if (!HasBotCommandState(BOT_COMMAND_WALK) && posdist > 10.0f && bmover->GetDistance(mmover) > 10.0f) + *speed = bmover->GetSpeed(MOVE_RUN); + else if (posdist > 7.5f) + *speed = basewalkspeed * 1.15f; + } + else + { + const float baserunspeed = bmover->GetSpeed(MOVE_RUN); + if (posdist > 50.0f) + *speed = baserunspeed * 2.0f; + else if (posdist > 30.0f) + *speed = baserunspeed * 1.5f; + else if (posdist > 10.0f) + *speed = baserunspeed * 1.25f; + } + } + + pos.Relocate(mpos); + + // TTT + // m m + // m M m + // m m + // rrrrr + // + //M - master (followUnit) + //T - bot tank (ROLE_TANK) + //r - ranged (ROLE_RANGED) + //m - melee (ROLE_DPS) +} +// Movement set +// Uses MovePoint() for following instead of MoveFollow() +// This helps bots overcome a bug with fanthom walls on grid borders blocking pathing +void bot_ai::BotMovement(BotMovementType type, Position const* pos, Unit* target, bool generatePath, float speed) const +{ + Vehicle* veh = me->GetVehicle(); + VehicleSeatEntry const* seat = veh ? veh->GetSeatForPassenger(me) : nullptr; + bool canControl = seat ? (seat->Flags & VEHICLE_SEAT_FLAG_CAN_CONTROL) : false; + Unit* mover = canControl ? veh->GetBase() : !veh ? me : nullptr; + if (!mover) + return; + + switch (type) + { + case BOT_MOVE_CHASE: + ASSERT(target); + mover->GetMotionMaster()->MoveChase(target, {}, ChaseAngle(target->GetRelativeAngle(me), float(target->IsPlayer() ? M_PI * 2.0 : M_PI / 8.0))); + break; + case BOT_MOVE_POINT: + mover->GetMotionMaster()->Add(new PointMovementGenerator(1, pos->m_positionX, pos->m_positionY, pos->m_positionZ, generatePath, speed)); + break; + default: + TC_LOG_ERROR("scripts", "BotMovement: unhandled bot movement type {}", uint32(type)); + return; + } +} +bool bot_ai::CanBotMoveVehicle() const +{ + if (VehicleSeatEntry const* seat = me->GetVehicle() ? me->GetVehicle()->GetSeatForPassenger(me) : nullptr) + return seat->Flags & VEHICLE_SEAT_FLAG_CAN_CONTROL; + + return false; +} +void bot_ai::MoveToSendPosition(Position const& mpos) +{ + EventRemoveBotAwaitState(BOT_AWAIT_SEND); + if (me->GetExactDist(mpos) <= 70.0f && !CCed(me, true)) + { + SetBotCommandState(BOT_COMMAND_STAY); + BotMovement(BOT_MOVE_POINT, &mpos, nullptr, false); + if (botPet && !CCed(botPet, true)) + { + botPet->GetBotPetAI()->SetBotCommandState(BOT_COMMAND_STAY); + botPet->GetMotionMaster()->MovePoint(me->GetMapId(), mpos, false); + } + sendlastpos.Relocate(me); + BotWhisper("Moving to position!"); + } + else + BotWhisper("Position is too far away!"); +} +void bot_ai::MoveToSendPosition(uint32 point_id) +{ + ASSERT(point_id < MAX_SEND_POINTS); + + MoveToSendPosition(sendpos[point_id]); +} +void bot_ai::MarkSendPosition(uint32 point_id) +{ + ASSERT(point_id < MAX_SEND_POINTS); + + sendpos[point_id].Relocate(me); +} + +void bot_ai::SetBotAwaitState(uint8 state) +{ + if (HasBotAwaitState(state)) + return; + + if (!me->IsAlive()) + return; + + _botAwaitState |= state; + + AbortAwaitStateRemoval(); + awaitStateRemEvent = new AwaitStateRemovalEvent(this, state); + Events.AddEvent(awaitStateRemEvent, Events.CalculateTime(30s)); +} + +void bot_ai::EventRemoveBotAwaitState(uint8 state) +{ + AbortAwaitStateRemoval(); + RemoveBotAwaitState(state); +} + +void bot_ai::AbortAwaitStateRemoval() +{ + if (awaitStateRemEvent) + { + if (awaitStateRemEvent->IsActive()) + awaitStateRemEvent->ScheduleAbort(); + awaitStateRemEvent = nullptr; + } +} + +void bot_ai::SetBotCommandState(uint32 st, bool force, Position* newpos, float* speed/* = nullptr*/) +{ + if (!(st & (BOT_COMMAND_UNBIND | BOT_COMMAND_INACTION))) + { + if (!me->IsAlive() || JumpingOrFalling()) + return; + } + + Vehicle* veh = me->GetVehicle(); + VehicleSeatEntry const* seat = veh ? veh->GetSeatForPassenger(me) : nullptr; + bool canControl = seat ? (seat->Flags & VEHICLE_SEAT_FLAG_CAN_CONTROL) : false; + Unit* mover = canControl ? veh->GetBase() : !veh ? me : nullptr; + if (mover) + { + if ((st & BOT_COMMAND_FOLLOW) && !IsChanneling() && + (force || (!mover->isMoving() && !IsCasting() && master->IsAlive() && !Feasting()))) + { + if (!me->IsInMap(master)) return; + if (CCed(mover, true)/* || master->HasUnitState(UNIT_STATE_FLEEING)*/) return; + float myspeed = 0.0f; + if (!newpos) + { + ASSERT(!IAmFree()); + _calculatePos(master, movepos, &myspeed); + } + else + { + movepos.m_positionX = newpos->m_positionX; + movepos.m_positionY = newpos->m_positionY; + movepos.m_positionZ = newpos->m_positionZ; + } + if (me->GetStandState() == UNIT_STAND_STATE_SIT && !Feasting()) + me->SetStandState(UNIT_STAND_STATE_STAND); + if (IsShootingWand()) + me->InterruptSpell(CURRENT_AUTOREPEAT_SPELL); + BotMovement(BOT_MOVE_POINT, &movepos, nullptr, true, speed ? *speed : myspeed); + //me->GetMotionMaster()->MovePoint(master->GetMapId(), pos); + //me->GetMotionMaster()->MoveFollow(master, mydist, angle); + RemoveBotCommandState(BOT_COMMAND_STAY | BOT_COMMAND_FULLSTOP | BOT_COMMAND_ATTACK | BOT_COMMAND_COMBATRESET); + } + else if (st & BOT_COMMAND_MASK_NOCAST_ANY) + { + uint32 removeMask = BOT_COMMAND_MASK_NOCAST_ANY & GetBotCommandState(); + st &= ~removeMask; + RemoveBotCommandState(removeMask); + me->InterruptNonMeleeSpells(false); + if (mover != me->ToUnit()) + mover->InterruptNonMeleeSpells(false); + } + else if (st & BOT_COMMAND_INACTION) + { + uint32 removeMask = BOT_COMMAND_INACTION & GetBotCommandState(); + st &= ~removeMask; + RemoveBotCommandState(removeMask | BOT_COMMAND_MASK_NOCAST_ANY | BOT_COMMAND_STAY | BOT_COMMAND_FULLSTOP | BOT_COMMAND_ATTACK | BOT_COMMAND_COMBATRESET); + me->AttackStop(); + me->InterruptNonMeleeSpells(true); + if (mover != me->ToUnit()) + { + mover->AttackStop(); + mover->InterruptNonMeleeSpells(true); + } + opponent = nullptr; + } + else if (st & BOT_COMMAND_FULLSTOP) + { + RemoveBotCommandState(BOT_COMMAND_FOLLOW | BOT_COMMAND_STAY | BOT_COMMAND_ATTACK); + me->AttackStop(); + me->InterruptNonMeleeSpells(true); + if (mover != me->ToUnit()) + { + mover->AttackStop(); + mover->InterruptNonMeleeSpells(true); + } + opponent = nullptr; + if (mover->isMoving()) + mover->ToCreature()->BotStopMovement(); + } + else if (st & BOT_COMMAND_STAY) + { + RemoveBotCommandState(BOT_COMMAND_FOLLOW | BOT_COMMAND_FULLSTOP); + if (mover->isMoving()) + mover->ToCreature()->BotStopMovement(); + } + else if (st & BOT_COMMAND_ATTACK) + { + RemoveBotCommandState(BOT_COMMAND_FOLLOW); + } + else if (st & BOT_COMMAND_COMBATRESET) + { + RemoveBotCommandState(BOT_COMMAND_ATTACK); + } + } + + _botCommandState |= st; +} + +void bot_ai::RemoveBotCommandState(uint32 st) +{ + _botCommandState &= ~st; +} + +bool bot_ai::IsPointedTarget(Unit const* target, uint8 targetFlags) const +{ + if (Group const* gr = (IAmFree() ? nullptr : master->GetGroup())) + if (targetFlags) + for (uint8 i = 0; i != TARGET_ICONS_COUNT; ++i) + if (targetFlags & GroupIconsFlags[i]) + if (target->GetGUID() == gr->GetTargetIcons()[i]) + return true; + + return false; +} +bool bot_ai::IsPointedHealTarget(Unit const* target) const +{ + return IsPointedTarget(target, BotMgr::GetHealTargetIconFlags()); +} +bool bot_ai::IsPointedTankingTarget(Unit const* target) const +{ + return IsPointedTarget(target, BotMgr::GetTankTargetIconFlags()); +} +bool bot_ai::IsPointedOffTankingTarget(Unit const* target) const +{ + return IsPointedTarget(target, BotMgr::GetOffTankTargetIconFlags()); +} +//unused +bool bot_ai::IsPointedDPSTarget(Unit const* target) const +{ + return IsPointedTarget(target, BotMgr::GetDPSTargetIconFlags()); +} +//unused +bool bot_ai::IsPointedRangedDPSTarget(Unit const* target) const +{ + return IsPointedTarget(target, BotMgr::GetRangedDPSTargetIconFlags()); +} +bool bot_ai::IsPointedNoDPSTarget(Unit const* target) const +{ + return IsPointedTarget(target, BotMgr::GetNoDPSTargetIconFlags()); +} +bool bot_ai::IsPointedAnyAttackTarget(Unit const* target) const +{ + return IsPointedTarget(target, BotMgr::GetOffTankTargetIconFlags() | BotMgr::GetDPSTargetIconFlags() | BotMgr::GetRangedDPSTargetIconFlags()); +} +// Buffs And Heal (really) +// Priority as follows: 1) heal players 2) buff players 3) heal bots 4) buff bots +// Priority adjustments to be considered +void bot_ai::BuffAndHealGroup(uint32 diff) +{ + if (GC_Timer > diff) return; + if (me->IsMounted() && !IsWanderer()) return; + if (IsCasting() || Feasting()) return; + + if (IAmFree()) + { + if (BuffTarget(me, diff)) + return; + + if (HealTarget(me, diff)) + return; + + if (me->GetFaction() == 14 || me->HasAura(BERSERK)) + return; + + std::list targets2; + GetNearbyFriendlyTargetsList(targets2, 30); + targets2.remove_if(BOTAI_PRED::BuffTargetExclude()); + targets2.remove_if([this](Unit const* unit) { + return unit->GetTypeId() != TYPEID_PLAYER && !(IsWanderer() && unit->IsNPCBot() && unit->ToCreature()->GetBotAI()->IsWanderer()); + }); + if (!targets2.empty() && BuffTarget(targets2.size() == 1 ? targets2.front() : Trinity::Containers::SelectRandomContainerElement(targets2), diff)) + return; + for (std::list::const_iterator itr = targets2.begin(); itr != targets2.end(); ++itr) + if (GetHealthPCT(*itr) < 95 && urand(1, 100) <= (30 + 30*uint32(GetBG() != nullptr)) && HealTarget(*itr, diff)) + break; + + return; + } + + BotMap const* map; + Group const* pGroup = master->GetGroup(); + uint8 hppctthreshold = GetHealHpPctThreshold(); + if (!pGroup) + { + //heals + map = master->GetBotMgr()->GetBotMap(); + if (HasRole(BOT_ROLE_HEAL)) + { + std::list targets3; + if (master->IsAlive() && !master->HasUnitState(UNIT_STATE_ISOLATED) && GetHealthPCT(master) <= hppctthreshold && me->GetDistance(master) < 40) + targets3.push_back(master); + if (master->GetVehicleBase() && !(master->GetVehicleBase()->GetTypeId() == TYPEID_UNIT && + master->GetVehicleCreatureBase()->GetCreatureTemplate()->type == CREATURE_TYPE_MECHANICAL) && + !master->GetVehicleBase()->HasUnitState(UNIT_STATE_ISOLATED) && GetHealthPCT(master->GetVehicleBase()) <= hppctthreshold && + me->GetDistance(master->GetVehicleBase()) < 40) + targets3.push_back(master->GetVehicleBase()); + for (BotMap::const_iterator itr = map->begin(); itr != map->end(); ++itr) + { + Unit* u = itr->second; + if (!(!u->IsInWorld() || me->GetMap() != u->FindMap() || !u->IsAlive() || u->HasUnitState(UNIT_STATE_ISOLATED) || + u->ToCreature()->IsTempBot() || me->GetDistance(u) > 40 || + (GetHealthPCT(u) > hppctthreshold && !IsTank(u)))) + targets3.push_back(u); + + u = itr->second->GetBotsPet(); + + if (!(!u || !u->IsAlive() || u->HasUnitState(UNIT_STATE_ISOLATED) || me->GetDistance(u) > 40 || GetHealthPCT(u) > hppctthreshold)) + targets3.push_back(u); + + u = itr->second->GetVehicleBase(); + if (u && !(u->GetTypeId() == TYPEID_UNIT && u->ToCreature()->GetCreatureTemplate()->type == CREATURE_TYPE_MECHANICAL) && + !u->HasUnitState(UNIT_STATE_ISOLATED) && GetHealthPCT(u) <= hppctthreshold && me->GetDistance(u) < 40) + targets3.push_back(u); + } + for (Unit::ControlList::const_iterator itr = master->m_Controlled.begin(); itr != master->m_Controlled.end(); ++itr) + { + Unit* u = *itr; + if (!u->IsInWorld() || me->GetMap() != u->FindMap() || !u->IsAlive() || u->HasUnitState(UNIT_STATE_ISOLATED) || + u->IsTotem() || u->GetEntry() == SHAMAN_EARTH_ELEMENTAL || me->GetDistance(u) > 40 || + (GetHealthPCT(u) > hppctthreshold && !IsTank(u))) + continue; + + targets3.push_back(u); + } + + if (!targets3.empty() && HealTarget(Trinity::Containers::SelectRandomContainerElement(targets3), diff)) + return; + } + //buffs + std::list targets4; + if (master->IsAlive() && me->GetDistance(master) < 30) + targets4.push_back(master); + for (BotMap::const_iterator itr = map->begin(); itr != map->end(); ++itr) + { + Unit* u = itr->second; + if (!(!u->IsInWorld() || me->GetMap() != u->FindMap() || !u->IsAlive() || u->HasUnitState(UNIT_STATE_ISOLATED) || + u->IsTotem() || me->GetDistance(u) > 30)) + targets4.push_back(u); + + //u = itr->second->GetBotsPet(); + + //if (!(!u || !u->IsAlive() || u->HasUnitState(UNIT_STATE_ISOLATED) || me->GetDistance(u) > 30)) + // targets4.push_back(u); + } + for (Unit::ControlList::const_iterator itr = master->m_Controlled.begin(); itr != master->m_Controlled.end(); ++itr) + { + Unit* u = *itr; + if (!u || !u->IsPet() || me->GetMap() != u->FindMap() || !u->IsAlive() || u->HasUnitState(UNIT_STATE_ISOLATED) || + u->IsTotem() || me->GetDistance(u) > 30) continue; + + targets4.push_back(u); + } + + if (!targets4.empty() && BuffTarget(Trinity::Containers::SelectRandomContainerElement(targets4), diff)) + return; + + return; + } + bool Bots = false; + //heals + if (HasRole(BOT_ROLE_HEAL)) + { + std::list targets5; + for (GroupReference const* itr = pGroup->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* tPlayer = itr->GetSource(); + if (tPlayer == nullptr) continue; + if (me->GetMap() != tPlayer->FindMap()) continue; + if (tPlayer->HaveBot() && !Bots) + Bots = true; + if (!tPlayer->IsAlive() || tPlayer->HasUnitState(UNIT_STATE_ISOLATED)) continue; + if (me->GetDistance(tPlayer) > 40) continue; + if (GetHealthPCT(tPlayer) <= hppctthreshold || IsTank(tPlayer)) + targets5.push_back(tPlayer); + if (tPlayer->GetVehicleBase() && !(tPlayer->GetVehicleBase()->GetTypeId() == TYPEID_UNIT && + tPlayer->GetVehicleCreatureBase()->GetCreatureTemplate()->type == CREATURE_TYPE_MECHANICAL) && + !tPlayer->GetVehicleBase()->HasUnitState(UNIT_STATE_ISOLATED) && GetHealthPCT(tPlayer->GetVehicleBase()) <= hppctthreshold && + me->GetDistance(tPlayer->GetVehicleBase()) < 40) + targets5.push_back(tPlayer->GetVehicleBase()); + } + if (Bots) + { + for (GroupReference const* itr = pGroup->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player const* tPlayer = itr->GetSource(); + if (tPlayer == nullptr) continue; + if (me->GetMap() != tPlayer->FindMap()) continue; + + if (tPlayer->HaveBot()) + { + map = tPlayer->GetBotMgr()->GetBotMap(); + for (BotMap::const_iterator bitr = map->begin(); bitr != map->end(); ++bitr) + { + Unit* u = bitr->second; + if (!(!u->IsInWorld() || me->GetMap() != u->FindMap() || !u->IsAlive() || u->HasUnitState(UNIT_STATE_ISOLATED) || + u->ToCreature()->IsTempBot() || me->GetDistance(u) > 40 || + (GetHealthPCT(u) > hppctthreshold && !IsTank(u)))) + targets5.push_back(u); + + u = bitr->second->GetBotsPet(); + + if (!(!u || !u->IsAlive() || u->HasUnitState(UNIT_STATE_ISOLATED) || me->GetDistance(u) > 40 || GetHealthPCT(u) > hppctthreshold)) + targets5.push_back(u); + + u = bitr->second->GetVehicleBase(); + if (u && !(u->GetTypeId() == TYPEID_UNIT && u->ToCreature()->GetCreatureTemplate()->type == CREATURE_TYPE_MECHANICAL) && + !u->HasUnitState(UNIT_STATE_ISOLATED) && GetHealthPCT(u) <= hppctthreshold && me->GetDistance(u) < 40) + targets5.push_back(u); + } + } + for (Unit::ControlList::const_iterator bitr = master->m_Controlled.begin(); bitr != master->m_Controlled.end(); ++bitr) + { + Unit* u = *bitr; + if (!u || !u->IsInWorld() || me->GetMap() != u->FindMap() || !u->IsAlive() || u->HasUnitState(UNIT_STATE_ISOLATED) || + u->IsTotem() || u->GetEntry() == SHAMAN_EARTH_ELEMENTAL || me->GetDistance(u) > 40 || + (GetHealthPCT(u) > hppctthreshold && !IsTank(u))) + continue; + + targets5.push_back(u); + } + } + } + + //check if we have pointed heal target + for (uint8 i = 0; i != TARGET_ICONS_COUNT; ++i) + { + if (BotMgr::GetHealTargetIconFlags() & GroupIconsFlags[i]) + { + if (ObjectGuid guid = pGroup->GetTargetIcons()[i]) + { + if (Unit* unit = ObjectAccessor::GetUnit(*me, guid)) + { + if (unit->IsAlive() && !unit->HasUnitState(UNIT_STATE_ISOLATED) && me->GetMap() == unit->FindMap() && me->GetDistance(unit) < 40 && + !unit->IsFullHealth() && master->GetVictim() != unit && !IsInBotParty(unit->GetVictim()) && + unit->GetEntry() != SHAMAN_EARTH_ELEMENTAL && + !(unit->GetTypeId() == TYPEID_UNIT && unit->ToCreature()->GetCreatureTemplate()->type == CREATURE_TYPE_MECHANICAL) && + unit->GetReactionTo(master) >= REP_NEUTRAL) + { + targets5.push_back(unit); + } + } + } + } + } + if (!targets5.empty() && HealTarget(Trinity::Containers::SelectRandomContainerElement(targets5), diff)) + return; + } + //buffs + std::list targets6; + for (GroupReference const* itr = pGroup->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* tPlayer = itr->GetSource(); + if (tPlayer == nullptr) continue; + if (me->GetMap() != tPlayer->FindMap()) continue; + if (tPlayer->HaveBot() && !Bots) + Bots = true; + if (!tPlayer->IsAlive() || tPlayer->HasUnitState(UNIT_STATE_ISOLATED)) continue; + if (me->GetDistance(tPlayer) > 30) continue; + targets6.push_back(tPlayer); + } + if (Bots) + { + for (GroupReference const* itr = pGroup->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player const* tPlayer = itr->GetSource(); + if (tPlayer == nullptr) continue; + if (me->GetMap() != tPlayer->FindMap()) continue; + + if (tPlayer->HaveBot()) + { + map = tPlayer->GetBotMgr()->GetBotMap(); + for (BotMap::const_iterator bitr = map->begin(); bitr != map->end(); ++bitr) + { + Unit* u = bitr->second; + if (!(!u->IsInWorld() || me->GetMap() != u->FindMap() || !u->IsAlive() || u->HasUnitState(UNIT_STATE_ISOLATED) || + u->IsTotem() || me->GetDistance(u) > 30)) + targets6.push_back(u); + + //u = bitr->second->GetBotsPet(); + + //if (!(!u || !u->IsAlive() || u->HasUnitState(UNIT_STATE_ISOLATED) || me->GetDistance(u) > 30)) + // targets6.push_back(u); + } + } + for (Unit::ControlList::const_iterator bitr = master->m_Controlled.begin(); bitr != master->m_Controlled.end(); ++bitr) + { + Unit* u = *bitr; + if (!u || !u->IsPet() || me->GetMap() != u->FindMap() || !u->IsAlive() || u->HasUnitState(UNIT_STATE_ISOLATED) || + u->IsTotem() || me->GetDistance(u) > 30) continue; + + targets6.push_back(u); + } + } + } + + if (!targets6.empty() && BuffTarget(Trinity::Containers::SelectRandomContainerElement(targets6), diff)) + return; +} +// Attempt to resurrect dead players and bots +// Target is either bot, player or player corpse +// no need to check global cooldown +void bot_ai::ResurrectGroup(uint32 spell_id) +{ + if (!spell_id || Rand() > 10) + return; + + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spell_id); + ASSERT(spellInfo); + spellInfo = spellInfo->TryGetSpellInfoOverride(me); + if (int32(me->GetPower(spellInfo->PowerType)) < spellInfo->CalcPowerCost(me, spellInfo->GetSchoolMask())) + return; + + //TC_LOG_ERROR("entities.player", "ResurrectGroup by {}", me->GetName()); + + if (IAmFree()) + { + if (me->GetFaction() == 14 || me->HasAura(BERSERK)) + return; + + WorldObject* playerOrCorpse = GetNearbyRezTarget(); + if (!playerOrCorpse) + return; + + if (!playerOrCorpse->IsWithinLOSInMap(me, LINEOFSIGHT_ALL_CHECKS, VMAP::ModelIgnoreFlags::M2)) + me->Relocate(*playerOrCorpse); + + Unit* target = playerOrCorpse->GetTypeId() == TYPEID_PLAYER ? playerOrCorpse->ToUnit() : (Unit*)playerOrCorpse->ToCorpse(); + if (doCast(target, spell_id)) //rezzing it + { + if (Player const* player = playerOrCorpse->GetTypeId() == TYPEID_PLAYER ? playerOrCorpse->ToPlayer() : ObjectAccessor::FindPlayer(playerOrCorpse->ToCorpse()->GetOwnerGUID())) + BotWhisper(LocalizedNpcText(player, BOT_TEXT_REZZING_YOU), player); + } + + return; + } + + Group const* group = master->GetGroup(); + std::vector bottargets; + BotMap const* map; + Player* player; + Unit* target; + if (!group) + { + player = master; + if (!player->IsAlive() && !player->IsResurrectRequested() && !player->GetUInt32Value(PLAYER_SELF_RES_SPELL)) + { + target = player->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST) ? player->ToUnit() : (Unit*)player->GetCorpse(); + if (target && target->IsInWorld() && me->GetMap() == target->FindMap() && + !player->GetBotMgr()->IsBeingResurrected(target)) + { + if (me->GetDistance(target) > 30 && !HasBotCommandState(BOT_COMMAND_STAY) && !me->GetVehicle()) + { + BotMovement(BOT_MOVE_POINT, target); + //me->GetMotionMaster()->MovePoint(master->GetMapId(), *target); + return; + } + else if (me->GetDistance(target) < 15 && !target->IsWithinLOSInMap(me, LINEOFSIGHT_ALL_CHECKS, VMAP::ModelIgnoreFlags::M2)) + me->Relocate(*target); + + if (doCast(target, spell_id))//rezzing it + { + BotWhisper(LocalizedNpcText(player, BOT_TEXT_REZZING_YOU)); + return; + } + } + } + + map = player->GetBotMgr()->GetBotMap(); + for (BotMap::const_iterator bitr = map->begin(); bitr != map->end(); ++bitr) + { + target = bitr->second; + if (!target || !target->IsInWorld() || target->IsAlive()) continue; + if (bitr->second->GetBotAI()->GetReviveTimer() < 15000 || bitr->second->GetBotAI()->GetSelfRezSpell()) continue; + if (me->GetDistance(target) < 30 && target->IsWithinLOSInMap(me, LINEOFSIGHT_ALL_CHECKS, VMAP::ModelIgnoreFlags::M2) && + !player->GetBotMgr()->IsBeingResurrected(target)) + bottargets.push_back(bitr->second); + } + } + else + { + bool Bots = false; + for (GroupReference const* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + player = itr->GetSource(); + target = player; + if (!player || player->FindMap() != me->GetMap()) continue; + if (!Bots && player->HaveBot()) + Bots = true; + if (player->IsAlive() || player->IsResurrectRequested() || player->GetUInt32Value(PLAYER_SELF_RES_SPELL)) continue; + if (player->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST)) + target = (Unit*)player->GetCorpse(); + if (!target || !target->IsInWorld()) continue; + if (target->GetTypeId() != player->GetTypeId() && me->GetMap() != target->FindMap()) continue; + if (master->GetBotMgr()->IsBeingResurrected(target)) return; + if (me->GetDistance(target) > 30 && !HasBotCommandState(BOT_COMMAND_STAY) && !me->GetVehicle()) + { + if (player == master) + { + BotMovement(BOT_MOVE_POINT, target); + //me->GetMotionMaster()->MovePoint(me->GetMapId(), *target); + return; + } + continue; + } + else if (me->GetDistance(target) < 15 && !target->IsWithinLOSInMap(me, LINEOFSIGHT_ALL_CHECKS, VMAP::ModelIgnoreFlags::M2)) + me->Relocate(*target); + + if (doCast(target, spell_id))//rezzing it + { + BotWhisper(LocalizedNpcText(player, BOT_TEXT_REZZING_YOU), player); + if (player != master) + BotWhisper(LocalizedNpcText(master, BOT_TEXT_REZZING_) + player->GetName()); + return; + } + } + + if (!Bots) + return; + + for (GroupReference const* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + player = itr->GetSource(); + if (!player || player->FindMap() != me->GetMap() || !player->HaveBot()) continue; + + map = player->GetBotMgr()->GetBotMap(); + for (BotMap::const_iterator bitr = map->begin(); bitr != map->end(); ++bitr) + { + target = bitr->second; + if (!target || !target->IsInWorld() || target->IsAlive()) continue; + if (bitr->second->GetBotAI()->GetReviveTimer() < 15000 || bitr->second->GetBotAI()->GetSelfRezSpell()) continue; + if (me->GetDistance(target) < 30 && target->IsWithinLOSInMap(me, LINEOFSIGHT_ALL_CHECKS, VMAP::ModelIgnoreFlags::M2) && + !player->GetBotMgr()->IsBeingResurrected(target)) + bottargets.push_back(bitr->second); + } + } + } + + //TC_LOG_ERROR("entities.unit", "ResurrectGroup: {} found {} targets", me->GetName(), uint32(bottargets.size())); + + if (bottargets.empty()) + return; + + target = bottargets.size() < 2 ? bottargets.front() : Trinity::Containers::SelectRandomContainerElement(bottargets); + + if (doCast(target, spell_id)) + { + Player const* targetOwner = target->ToCreature()->GetBotOwner(); + if (targetOwner != master) + { + std::string rezstr1 = + LocalizedNpcText(targetOwner, BOT_TEXT_REZZING_) + target->GetName() + " (" + LocalizedNpcText(targetOwner, BOT_TEXT_YOUR_BOT) + ")"; + std::string rezstr2 = + LocalizedNpcText(master, BOT_TEXT_REZZING_) + target->GetName() + " (" + targetOwner->GetName() + LocalizedNpcText(master, BOT_TEXT__S_BOT) + ")"; + + BotWhisper(std::move(rezstr1), targetOwner); + BotWhisper(std::move(rezstr2)); + } + else + BotWhisper(LocalizedNpcText(master, BOT_TEXT_REZZING_) + target->GetName()); + + return; + } +} +// CURES +//cycle through the group sending members for cure +void bot_ai::CureGroup(uint32 cureSpell, uint32 diff) +{ + if (!cureSpell) return; + if (GC_Timer > diff) return; + if (me->IsMounted()) + if (IsTank() && me->GetVictim() && me->GetMap()->IsRaid()) return; + if (IsCasting()) return; + + if (IAmFree()) + { + std::list cureTargets; + + if (_canCureTarget(me, cureSpell)) + cureTargets.push_back(me); + if (botPet && _canCureTarget(botPet, cureSpell)) + cureTargets.push_back(botPet); + + if (!(me->GetFaction() == 14 || me->HasAura(BERSERK))) + { + std::list targets1; + GetNearbyFriendlyTargetsList(targets1, 38); + for (std::list::const_iterator itr = targets1.begin(); itr != targets1.end(); ++itr) + if (((*itr)->IsPlayer() || (*itr)->ToPet()) && _canCureTarget(*itr, cureSpell)) + cureTargets.push_back(*itr); + } + + if (!cureTargets.empty()) + { + if (doCast(Trinity::Containers::SelectRandomContainerElement(cureTargets), cureSpell)) + return; + } + + return; + } + + if (!master->GetMap()->IsRaid() && Rand() > 35) + return; + + //TC_LOG_ERROR("entities.player", "{}: CureGroup() on {}", me->GetName(), pTarget->GetName()); + std::list targets; + Group const* pGroup = master->GetGroup(); + BotMap const* map; + Unit* u; + if (!pGroup) + { + if (_canCureTarget(master, cureSpell)) + targets.push_back(master); + + map = master->GetBotMgr()->GetBotMap(); + for (BotMap::const_iterator itr = map->begin(); itr != map->end(); ++itr) + { + u = itr->second; + if (!u || !u->IsInWorld() || me->GetMap() != u->FindMap() || !u->IsAlive()) continue; + if (_canCureTarget(u, cureSpell)) + targets.push_back(u); + } + + for (Unit::ControlList::const_iterator itr = master->m_Controlled.begin(); itr != master->m_Controlled.end(); ++itr) + { + u = *itr; + if (!u || !u->IsPet() || !u->IsAlive() || me->GetDistance(u) > 30) continue; + + if (_canCureTarget(u, cureSpell)) + targets.push_back(u); + } + } + else + { + bool Bots = false; + for (GroupReference const* itr = pGroup->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* tPlayer = itr->GetSource(); + if (!tPlayer || (!tPlayer->IsAlive() && !tPlayer->HaveBot())) continue; + if (!tPlayer->IsInWorld() || tPlayer->IsBeingTeleported()) continue; + if (me->GetMap() != tPlayer->FindMap()) continue; + if (!Bots && tPlayer->HaveBot()) + Bots = true; + if (_canCureTarget(tPlayer, cureSpell)) + targets.push_back(tPlayer); + } + if (!Bots) return; + for (GroupReference const* itr = pGroup->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* tPlayer = itr->GetSource(); + if (tPlayer == nullptr) continue; + if (!tPlayer->IsInWorld() || tPlayer->IsBeingTeleported()) continue; + if (me->GetMap() != tPlayer->FindMap()) continue; + + if (tPlayer->HaveBot()) + { + map = tPlayer->GetBotMgr()->GetBotMap(); + for (BotMap::const_iterator bitr = map->begin(); bitr != map->end(); ++bitr) + { + u = bitr->second; + if (!u || !u->IsInWorld() || me->GetMap() != u->FindMap() || !u->IsAlive()) continue; + if (_canCureTarget(u, cureSpell)) + targets.push_back(u); + } + } + + for (Unit::ControlList::const_iterator bitr = tPlayer->m_Controlled.begin(); bitr != tPlayer->m_Controlled.end(); ++bitr) + { + u = *bitr; + if (!u || !u->IsPet() || !u->IsAlive() || me->GetDistance(u) > 30) continue; + + if (_canCureTarget(u, cureSpell)) + targets.push_back(u); + } + } + } + + if (!targets.empty()) + { + if (doCast(Trinity::Containers::SelectRandomContainerElement(targets), cureSpell)) + return; + } +} + +// determines if unit has something to cure +bool bot_ai::_canCureTarget(Unit const* target, uint32 cureSpell) const +{ + if (me->GetLevel() < 10 || target->GetLevel() < 10) return false; + if (target->HasUnitState(UNIT_STATE_ISOLATED)) return false; + if (target->GetTypeId() == TYPEID_UNIT && target->ToCreature()->IsTempBot()) return false; + if (target->HasAuraType(SPELL_AURA_MOD_POSSESS) && !IsInBotParty(target)) return false; + + SpellInfo const* info = sSpellMgr->GetSpellInfo(cureSpell); + if (!info) + return false; + info = info->TryGetSpellInfoOverride(me); + + if (me->GetDistance(target) > CalcSpellMaxRange(cureSpell, false)) + return false; + + uint32 dispelMask = 0; + for (uint8 i = 0; i != MAX_SPELL_EFFECTS; ++i) + if (info->_effects[i].Effect == SPELL_EFFECT_DISPEL) + dispelMask |= SpellInfo::GetDispelMask(DispelType(info->_effects[i].MiscValue)); + + //SpellBreaker addins + if (cureSpell == SPELL_STEAL_MAGIC) + dispelMask |= (1< dispel_list; + _getBotDispellableAuraList(target, dispelMask, dispel_list); + + return !(dispel_list.empty()); +} + +void bot_ai::_getBotDispellableAuraList(Unit const* target, uint32 dispelMask, std::list &dispelList) const +{ + //Unholy Blight prevents diseases from being dispelled + if ((dispelMask & (1<GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_DEATHKNIGHT, 1494, 0)) + dispelMask &= ~(1<GetOwnedAuras(); + for (Unit::AuraMap::const_iterator itr = auras.begin(); itr != auras.end(); ++itr) + { + Aura const* aura = itr->second; + + if (aura->IsPassive()) + continue; + + AuraApplication const* aurApp = aura->GetApplicationOfTarget(target->GetGUID()); + if (!aurApp) + continue; + + if (aura->GetSpellInfo()->GetDispelMask() & dispelMask) + { + //do not dispel positive auras from enemies and negative ones from friends + if (aurApp->IsPositive() == target->IsFriendlyTo(me)) + continue; + + //skip Vampiric Touch to prevent being CCed just heal it out + if (HasRole(BOT_ROLE_HEAL) && aura->GetSpellInfo()->IsRankOf(sSpellMgr->GetSpellInfo(34914))) + continue; + + if (((aura->GetSpellInfo()->AttributesEx7 & SPELL_ATTR7_DISPEL_CHARGES) ? aura->GetCharges() : aura->GetStackAmount()) > 0) + dispelList.push_back(aura); + } + } +} +// Check if can cast some spell out of main rotation to use up target's spell reflection charges +// Supposed to check instant non-damaging spells but these checks are not performed (Shaman, Priest) +bool bot_ai::CanRemoveReflectSpells(Unit const* target, uint32 spellId) const +{ + if (!target || !spellId) + return false; + + if (!target->HasAuraType(SPELL_AURA_REFLECT_SPELLS) && !target->HasAuraType(SPELL_AURA_REFLECT_SPELLS_SCHOOL)) + return false; + + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); + if (!spellInfo) + return false; + spellInfo = spellInfo->TryGetSpellInfoOverride(me); + + if (!spellInfo->IsPositive() && spellInfo->DmgClass == SPELL_DAMAGE_CLASS_MAGIC && + !(spellInfo->Attributes & SPELL_ATTR0_ABILITY) && !(spellInfo->AttributesEx & SPELL_ATTR1_CANT_BE_REFLECTED) && + !(spellInfo->Attributes & SPELL_ATTR0_UNAFFECTED_BY_INVULNERABILITY)) + { + //bool directDamage = false; + //for (uint8 i = 0; i != MAX_SPELL_EFFECTS; ++i) + //{ + // if (spellInfo->_effects[i].TargetA.GetTarget() == TARGET_UNIT_TARGET_ENEMY) + // { + // if (spellInfo->_effects[i].IsEffect(SPELL_EFFECT_SCHOOL_DAMAGE) || + // spellInfo->_effects[i].IsAura(SPELL_AURA_PERIODIC_DAMAGE) || + // spellInfo->_effects[i].IsAura(SPELL_AURA_PERIODIC_LEECH) || + // spellInfo->_effects[i].IsAura(SPELL_AURA_MOD_SPEED_SLOW_ALL) ||//Icy Touch + // spellInfo->_effects[i].IsAura(SPELL_AURA_HASTE_SPELLS))//Slow + // { + // directDamage = true; + // break; + // } + // } + //} + //if (directDamage) + //{ + Unit::AuraEffectList const& reflectAuras1 = target->GetAuraEffectsByType(SPELL_AURA_REFLECT_SPELLS); + for (Unit::AuraEffectList::const_iterator itr = reflectAuras1.begin(); itr != reflectAuras1.end(); ++itr) + { + //All existing SPELL_AURA_REFLECT_SPELLS spells have at least amount 50 + if ((*itr)->GetBase()->IsUsingCharges() && + (*itr)->GetBase()->GetCharges() <= target->getAttackers().size() * ((*itr)->GetBase()->GetDuration() / 1000) / 4) + return true; + } + + Unit::AuraEffectList const& reflectAuras2 = target->GetAuraEffectsByType(SPELL_AURA_REFLECT_SPELLS_SCHOOL); + for (Unit::AuraEffectList::const_iterator itr = reflectAuras2.begin(); itr != reflectAuras2.end(); ++itr) + { + if ((*itr)->GetBase()->IsUsingCharges() && + (*itr)->GetAmount() >= 50 && ((*itr)->GetMiscValue() & spellInfo->GetSchoolMask()) && + (*itr)->GetBase()->GetCharges() <= ((*itr)->GetBase()->GetDuration() / 1000) / 4) + return true; + } + //} + } + + return false; +} +//LIST AURAS +// Debug: Returns bot's info to called player +void bot_ai::_listAuras(Player const* player, Unit const* unit) const +{ + //if (player->GetSession()->GetSecurity() == SEC_PLAYER) return; + if (!player->IsGameMaster() && (IAmFree() || !IsInBotParty(player))) return; + if (!IsInBotParty(unit)) return; + ChatHandler ch(player->GetSession()); + std::ostringstream botstring; + botstring.setf(std::ios_base::fixed); + uint32 const bot_pet_player_class = unit->GetTypeId() == TYPEID_PLAYER ? unit->GetClass() : unit->ToCreature()->GetBotAI()->GetBotClass(); + botstring << unit->GetName() << " (" << LocalizedNpcText(player, BOT_TEXT_CLASS) << ": " << uint32(bot_pet_player_class) << "), "; + if (unit->GetTypeId() == TYPEID_PLAYER) + botstring << LocalizedNpcText(player, BOT_TEXT_PLAYER); + else if (unit->IsNPCBot()) + { + bot_ai const* ai = unit->ToCreature()->GetBotAI(); + botstring << LocalizedNpcText(player, BOT_TEXT_MASTER) << ": "; + Player const* owner = ai->GetBotOwner(); + botstring << (owner != unit ? owner->GetName() : LocalizedNpcText(player, BOT_TEXT_NONE)); + } + uint8 locale = player->GetSession()->GetSessionDbcLocale(); + Unit::AuraMap const &vAuras = unit->GetOwnedAuras(); + for (Unit::AuraMap::const_iterator itr = vAuras.begin(); itr != vAuras.end(); ++itr) + { + SpellInfo const* spellInfo = itr->second->GetSpellInfo(); + if (!spellInfo) + continue; + spellInfo = spellInfo->TryGetSpellInfoOverride(me); + uint32 id = spellInfo->Id; + SpellInfo const* learnSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->_effects[0].TriggerSpell); + const std::string name = spellInfo->SpellName[locale]; + botstring << "\n" << id << " - |cffffffff|Hspell:" << id << "|h[" << name; + botstring << ' ' << localeNames[locale] << "]|h|r"; + uint32 talentcost = GetTalentSpellCost(id); + uint32 rank = 0; + if (talentcost > 0 && (spellInfo->GetNextRankSpell() || spellInfo->GetPrevRankSpell())) + rank = talentcost; + else if (learnSpellInfo && (spellInfo->GetNextRankSpell() || spellInfo->GetPrevRankSpell())) + rank = spellInfo->GetRank(); + else if (spellInfo->GetNextRankSpell() || spellInfo->GetPrevRankSpell()) + rank = spellInfo->GetRank(); + if (rank > 0) + botstring << " " << LocalizedNpcText(player, BOT_TEXT_RANK) << " " << rank; + if (talentcost > 0) + botstring << " [" << LocalizedNpcText(player, BOT_TEXT_TALENT) << "]"; + if (spellInfo->IsPassive()) + botstring << " [" << LocalizedNpcText(player, BOT_TEXT_PASSIVE) << "]"; + if ((spellInfo->Attributes & SPELL_ATTR0_HIDDEN_CLIENTSIDE) || + (spellInfo->AttributesEx & SPELL_ATTR1_DONT_DISPLAY_IN_AURA_BAR)) + botstring << " [" << LocalizedNpcText(player, BOT_TEXT_HIDDEN) << "]"; + if (unit->GetTypeId() == TYPEID_PLAYER && unit->ToPlayer()->HasSpell(id)) + botstring << " [" << LocalizedNpcText(player, BOT_TEXT_KNOWN) << "]"; + else if (unit == me && GetSpell(spellInfo->GetFirstRankSpell()->Id)) + botstring << " [" << LocalizedNpcText(player, BOT_TEXT_ABILITY) << "]"; + } + botstring.precision(1); + for (uint8 i = STAT_STRENGTH; i != MAX_STATS; ++i) + { + std::string mystat; + switch (i) + { + case STAT_STRENGTH: mystat = LocalizedNpcText(player, BOT_TEXT_STAT_STR); break; + case STAT_AGILITY: mystat = LocalizedNpcText(player, BOT_TEXT_STAT_AGI); break; + case STAT_STAMINA: mystat = LocalizedNpcText(player, BOT_TEXT_STAT_STA); break; + case STAT_INTELLECT: mystat = LocalizedNpcText(player, BOT_TEXT_STAT_INT); break; + case STAT_SPIRIT: mystat = LocalizedNpcText(player, BOT_TEXT_STAT_SPI); break; + default: mystat = LocalizedNpcText(player, BOT_TEXT_STAT_UNK); break; + } + //ch.PSendSysMessage("base %s: {}", mystat, unit->GetCreateStat(Stats(i)); + float totalstat = unit->GetTotalStatValue(Stats(i)); + //ch.PSendSysMessage("base total %s: {}", mystat, totalstat); + if (unit == me) + { + BotStatMods t = MAX_BOT_ITEM_MOD; + switch (i) + { + case STAT_STRENGTH: t = BOT_STAT_MOD_STRENGTH; break; + case STAT_AGILITY: t = BOT_STAT_MOD_AGILITY; break; + case STAT_STAMINA: t = BOT_STAT_MOD_STAMINA; break; + case STAT_INTELLECT: t = BOT_STAT_MOD_INTELLECT; break; + case STAT_SPIRIT: t = BOT_STAT_MOD_SPIRIT; break; + default: break; + } + + if (t < MAX_BOT_ITEM_MOD) + totalstat = GetTotalBotStat(t); + } + botstring << "\n" << LocalizedNpcText(player, BOT_TEXT_TOTAL) << " " << mystat << ": " << float(totalstat); + } + botstring.precision(2); + botstring << "\n" << LocalizedNpcText(player, BOT_TEXT_MELEE_AP) << ": " << int32(unit->GetTotalAttackPowerValue(BASE_ATTACK)); + botstring << "\n" << LocalizedNpcText(player, BOT_TEXT_RANGED_AP) << ": " << int32(unit->GetTotalAttackPowerValue(RANGED_ATTACK)); + botstring << "\n" << LocalizedNpcText(player, BOT_TEXT_ARMOR) << ": " << uint32(unit->GetArmor()); + botstring << "\n" << LocalizedNpcText(player, BOT_TEXT_CRIT) << ": " << float(unit->GetUnitCriticalChanceDone(BASE_ATTACK)); + botstring << "\n" << LocalizedNpcText(player, BOT_TEXT_DEFENSE) << ": " << uint32(unit->GetDefenseSkillValue()); + botstring << "\n" << LocalizedNpcText(player, BOT_TEXT_MISS) << ": " << float(unit->GetUnitMissChance()); + botstring << "\n" << LocalizedNpcText(player, BOT_TEXT_DODGE) << ": " << float(unit->GetUnitDodgeChance(BASE_ATTACK, me)); + botstring << "\n" << LocalizedNpcText(player, BOT_TEXT_PARRY) << ": " << float(unit->GetUnitParryChance(BASE_ATTACK, me)); + botstring << "\n" << LocalizedNpcText(player, BOT_TEXT_BLOCK) << ": " << float(unit->GetUnitBlockChance(BASE_ATTACK, me)); + botstring << "\n" << LocalizedNpcText(player, BOT_TEXT_BLOCKVALUE) << ": " << uint32(unit->GetShieldBlockValue()); + botstring << "\n" << LocalizedNpcText(player, BOT_TEXT_DMG_TAKEN_MELEE) << ": " << float(dmg_taken_phy * unit->GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN, SPELL_SCHOOL_MASK_NORMAL)); + botstring << "\n" << LocalizedNpcText(player, BOT_TEXT_DMG_TAKEN_SPELL) << ": " << float(dmg_taken_mag * unit->GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN, SPELL_SCHOOL_MASK_MAGIC)); + + //float resilience_base = unit->GetMeleeCritChanceReduction(); + //botstring << "\n" << "Resilience pct" << ": -" << resilience_base << " / -" << float(resilience_base * 2.2f) << " / -" << float(resilience_base * 2.0f); + + WeaponAttackType type = BASE_ATTACK; + float attSpeed = (unit->GetAttackTime(type) * unit->m_modAttackSpeedPct[type])/1000.f; + botstring << "\n" << LocalizedNpcText(player, BOT_TEXT_DMG_RANGE_MAINHAND) << ": " << LocalizedNpcText(player, BOT_TEXT_MIN) << ": " << int32(unit->GetFloatValue(UNIT_FIELD_MINDAMAGE)) << ", " << LocalizedNpcText(player, BOT_TEXT_MAX) << ": " << int32(unit->GetFloatValue(UNIT_FIELD_MAXDAMAGE) + 1.f); + botstring << "\n" << LocalizedNpcText(player, BOT_TEXT_DMG_MULT_MAINHAND) << ": " << float(unit->GetPctModifierValue(UNIT_MOD_DAMAGE_MAINHAND, BASE_PCT)*unit->GetPctModifierValue(UNIT_MOD_DAMAGE_MAINHAND, TOTAL_PCT)); + botstring << "\n" << LocalizedNpcText(player, BOT_TEXT_ATTACK_TIME_MAINHAND) << ": " << float(attSpeed) + << " (" << float(((unit->GetFloatValue(UNIT_FIELD_MINDAMAGE) + unit->GetFloatValue(UNIT_FIELD_MAXDAMAGE)) / 2) / attSpeed) << " " << LocalizedNpcText(player, BOT_TEXT_DPS) << ")"; + if (unit->haveOffhandWeapon()) + { + type = OFF_ATTACK; + attSpeed = (unit->GetAttackTime(type) * unit->m_modAttackSpeedPct[type])/1000.f; + botstring << "\n" << LocalizedNpcText(player, BOT_TEXT_DMG_RANGE_OFFHAND) << ": " << LocalizedNpcText(player, BOT_TEXT_MIN) << ": " << int32(unit->GetFloatValue(UNIT_FIELD_MINOFFHANDDAMAGE)) << ", " << LocalizedNpcText(player, BOT_TEXT_MAX) << ": " << int32(unit->GetFloatValue(UNIT_FIELD_MAXOFFHANDDAMAGE) + 1.f); + botstring << "\n" << LocalizedNpcText(player, BOT_TEXT_DMG_MULT_OFFHAND) << ": " << float(unit->GetPctModifierValue(UNIT_MOD_DAMAGE_OFFHAND, BASE_PCT)*unit->GetPctModifierValue(UNIT_MOD_DAMAGE_OFFHAND, TOTAL_PCT)); + botstring << "\n" << LocalizedNpcText(player, BOT_TEXT_ATTACK_TIME_OFFHAND) << ": " << float(attSpeed) + << " (" << float(((unit->GetFloatValue(UNIT_FIELD_MINOFFHANDDAMAGE) + unit->GetFloatValue(UNIT_FIELD_MAXOFFHANDDAMAGE)) / 2) / attSpeed) << " " << LocalizedNpcText(player, BOT_TEXT_DPS) << ")"; + } + if (unit != me || + (me->GetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + uint32(BOT_SLOT_RANGED)) && + _botclass != BOT_CLASS_PALADIN && + _botclass != BOT_CLASS_DEATH_KNIGHT && + _botclass != BOT_CLASS_DRUID && + _botclass != BOT_CLASS_SHAMAN)) + { + type = RANGED_ATTACK; + attSpeed = (unit->GetAttackTime(type) * unit->m_modAttackSpeedPct[type])/1000.f; + botstring << "\n" << LocalizedNpcText(player, BOT_TEXT_DMG_RANGE_RANGED) << ": " << LocalizedNpcText(player, BOT_TEXT_MIN) << ": " << int32(unit->GetFloatValue(UNIT_FIELD_MINRANGEDDAMAGE)) << ", " << LocalizedNpcText(player, BOT_TEXT_MAX) << ": " << int32(unit->GetFloatValue(UNIT_FIELD_MAXRANGEDDAMAGE) + 1.f); + botstring << "\n" << LocalizedNpcText(player, BOT_TEXT_DMG_MULT_RANGED) << ": " << float(unit->GetPctModifierValue(UNIT_MOD_DAMAGE_RANGED, BASE_PCT)*unit->GetPctModifierValue(UNIT_MOD_DAMAGE_RANGED, TOTAL_PCT)); + botstring << "\n" << LocalizedNpcText(player, BOT_TEXT_ATTACK_TIME_RANGED) << ": " << float(attSpeed) + << " (" << float(((unit->GetFloatValue(UNIT_FIELD_MINRANGEDDAMAGE) + unit->GetFloatValue(UNIT_FIELD_MAXRANGEDDAMAGE)) / 2) / attSpeed) << " " << LocalizedNpcText(player, BOT_TEXT_DPS) << ")"; + } + botstring << "\n" << LocalizedNpcText(player, BOT_TEXT_BASE_HP) << ": " << int32(unit->GetCreateHealth()); + botstring << "\n" << LocalizedNpcText(player, BOT_TEXT_TOTAL_HP) << ": " << int32(unit->GetMaxHealth()); + botstring << "\n" << LocalizedNpcText(player, BOT_TEXT_BASE_MP) << ": " << int32(unit->GetCreateMana()); + botstring << "\n" << LocalizedNpcText(player, BOT_TEXT_TOTAL_MP) << ": " << int32(unit->GetMaxPower(POWER_MANA)); + if (unit->GetMaxPower(POWER_MANA) > 1 && unit->GetPowerType() != POWER_MANA) + botstring << "\n" << LocalizedNpcText(player, BOT_TEXT_CURR_MP) << ": " << int32(unit->GetPower(POWER_MANA)); + + if (unit == me) + { + botstring << "\n" << LocalizedNpcText(player, BOT_TEXT_SPELLPOWER) << ": " << int32(me->SpellBaseDamageBonusDone(SPELL_SCHOOL_MASK_MAGIC)); + botstring << "\n" << LocalizedNpcText(player, BOT_TEXT_REGEN_HP) << ": " << int32(_getTotalBotStat(BOT_STAT_MOD_HEALTH_REGEN)); + if (me->GetMaxPower(POWER_MANA) > 1) + { + botstring << "\n" << LocalizedNpcText(player, BOT_TEXT_REGEN_MP_CAST) << ": " << float((_botclass == BOT_CLASS_SPHYNX ? -1.f : 1.f) * me->GetFloatValue(UNIT_FIELD_POWER_REGEN_INTERRUPTED_FLAT_MODIFIER) * sWorld->getRate(RATE_POWER_MANA) * 5.0f); + botstring << "\n" << LocalizedNpcText(player, BOT_TEXT_REGEN_MP_NOCAST) << ": " << float((_botclass == BOT_CLASS_SPHYNX ? -1.f : 1.f) * me->GetFloatValue(UNIT_FIELD_POWER_REGEN_FLAT_MODIFIER) * sWorld->getRate(RATE_POWER_MANA) * 5.0f); + } + botstring << "\n" << LocalizedNpcText(player, BOT_TEXT_HASTE) << ": " << (haste >= 0 ? "+" : "-") << float(haste) << " " << LocalizedNpcText(player, BOT_TEXT_PCT); + botstring << "\n" << LocalizedNpcText(player, BOT_TEXT_HIT) << ": +" << float(hit) << " " << LocalizedNpcText(player, BOT_TEXT_PCT); + botstring << "\n" << LocalizedNpcText(player, BOT_TEXT_EXPERTISE) << ": " << int32(expertise) << " (-" << float(float(expertise) * 0.25f) << " " << LocalizedNpcText(player, BOT_TEXT_PCT) << ")"; + botstring << "\n" << LocalizedNpcText(player, BOT_TEXT_ARMOR_PEN) << ": " << float(me->GetCreatureArmorPenetrationCoef()) << " " << LocalizedNpcText(player, BOT_TEXT_PCT); + botstring << "\n" << LocalizedNpcText(player, BOT_TEXT_SPELL_PEN) << ": " << uint32(spellpen) + uint32(std::abs(me->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_TARGET_RESISTANCE, SPELL_SCHOOL_MASK_MAGIC))); + + for (uint8 i = SPELL_SCHOOL_HOLY; i != MAX_SPELL_SCHOOL; ++i) + { + uint32 curresist = me->GetResistance(SpellSchools(i)) + resistbonus[i-1]; + + std::string resist; + switch (i) + { + case 1: resist = LocalizedNpcText(player, BOT_TEXT_HOLY); break; + case 2: resist = LocalizedNpcText(player, BOT_TEXT_FIRE); break; + case 3: resist = LocalizedNpcText(player, BOT_TEXT_NATURE); break; + case 4: resist = LocalizedNpcText(player, BOT_TEXT_FROST); break; + case 5: resist = LocalizedNpcText(player, BOT_TEXT_SHADOW); break; + case 6: resist = LocalizedNpcText(player, BOT_TEXT_ARCANE); break; + } + botstring << "\n" << LocalizedNpcText(player, BOT_TEXT_RESISTANCE) << ": " << resist << ": " << uint32(curresist); + } + + auto scores = GetBotGearScores(); + botstring << "\nGear score total: " << scores.first << ", avg: " << scores.second; + + botstring << "\n" << LocalizedNpcText(player, BOT_TEXT_COMMAND_STATES) << "(" << GetBotCommandState() << "):"; + if (HasBotCommandState(BOT_COMMAND_FOLLOW)) + botstring << " " << LocalizedNpcText(player, BOT_TEXT_COMMAND_FOLLOW); + if (HasBotCommandState(BOT_COMMAND_ATTACK)) + botstring << " " << LocalizedNpcText(player, BOT_TEXT_COMMAND_ATTACK); + if (HasBotCommandState(BOT_COMMAND_STAY)) + botstring << " " << LocalizedNpcText(player, BOT_TEXT_COMMAND_STAY); + if (HasBotCommandState(BOT_COMMAND_COMBATRESET)) + botstring << " " << LocalizedNpcText(player, BOT_TEXT_COMMAND_RESET); + if (HasBotCommandState(BOT_COMMAND_FULLSTOP)) + botstring << " " << LocalizedNpcText(player, BOT_TEXT_COMMAND_FULLSTOP); + if (!IAmFree()) + botstring << "\n" << LocalizedNpcText(player, BOT_TEXT_FOLLOW_DISTANCE) << ": " << uint32(master->GetBotMgr()->GetBotFollowDist()); + + if (_botclass < BOT_CLASS_EX_START) + botstring << "\n" << LocalizedNpcText(player, BOT_TEXT_SPEC) << ": " << uint32(_spec); + + if (IsWanderer()) + botstring << "\n_baseLevel: " << uint32(_baseLevel); + + botstring << "\n" << LocalizedNpcText(player, BOT_TEXT_BOT_ROLEMASK_MAIN) << ": " << uint32(_roleMask & BOT_ROLE_MASK_MAIN); + botstring << "\n" << LocalizedNpcText(player, BOT_TEXT_BOT_ROLEMASK_GATHERING) << ": " << uint32(_roleMask & BOT_ROLE_MASK_GATHERING); + + botstring << "\n" << LocalizedNpcText(player, BOT_TEXT_PVP_KILLS) << ": " << uint32(_pvpKillsCount) << ", " << LocalizedNpcText(player, BOT_TEXT_PLAYERS) << ": " << uint32(_playerKillsCount) << ", " << LocalizedNpcText(player, BOT_TEXT_TOTAL) << ": " << uint32(_killsCount); + botstring << "\n" << LocalizedNpcText(player, BOT_TEXT_DIED_) << uint32(_deathsCount) << LocalizedNpcText(player, BOT_TEXT__TIMES); + + //debug + botstring << "\n_lastWMOAreaId: " << uint32(_lastWMOAreaId); + botstring << "\nGCD: " << uint32(GC_Timer); + //botstring << "\nPotion CD: " << uint32(_potionTimer); + //botstring << "\ncurrent Engage timer: " << GetEngageTimer(); + //for (uint32 i = 0; i != 148; ++i) + //{ + // float val = me->GetFloatValue(i); + // ch.PSendSysMessage("Float value at %u: {}", i, val); + //} + + //ch.PSendSysMessage("healTargetIconFlags: %u", healTargetIconFlags); + + //ch.PSendSysMessage("Roles:"); + //for (uint32 i = BOT_MAX_ROLE; i != BOT_ROLE_NONE; i >>= 1) + //{ + // if (_roleMask & i) + // { + // switch (i) + // { + // case BOT_ROLE_TANK: + // ch.PSendSysMessage("BOT_ROLE_TANK"); + // break; + // case BOT_ROLE_DPS: + // ch.PSendSysMessage("BOT_ROLE_DPS"); + // break; + // case BOT_ROLE_HEAL: + // ch.PSendSysMessage("BOT_ROLE_HEAL"); + // break; + // //case BOT_ROLE_MELEE: + // // ch.PSendSysMessage("BOT_ROLE_MELEE"); + // // break; + // case BOT_ROLE_RANGED: + // ch.PSendSysMessage("BOT_ROLE_RANGED"); + // break; + // } + // } + //} + + //ch.PSendSysMessage("Stat bonuses:"); + //for (uint8 i = 0; i != MAX_BOT_ITEM_MOD; ++i) + //{ + // int32 val = 0; + // uint32 const a = i; + // for (uint8 j = 0; j != BOT_INVENTORY_SIZE; ++j) + // val += static_cast(_stats[j])[a]; + + // if (val != 0) + // ch.PSendSysMessage("Item mod %u: bonus = %i", i, val); + //} + } + + ch.SendSysMessage(botstring.str().c_str()); +} +//SetStats +// Health, Armor, Powers, Combat Ratings, and global update setup +void bot_ai::SetStats(bool force) +{ + if (IsTempBot() && !force) + return; + + shouldUpdateStats = false; + + uint8 myclass = _botclass; + if (myclass == BOT_CLASS_DRUID && GetBotStance() != BOT_STANCE_NONE) + myclass = GetBotStance(); + + uint8 mylevel = std::min(master->GetLevel(), DEFAULT_MAX_LEVEL); + if (IsWanderer()) + { + if (_baseLevel == 0) //this only happens once + { + mylevel = urand(me->GetCreatureTemplate()->minlevel, me->GetCreatureTemplate()->maxlevel); + mylevel += BotDataMgr::GetLevelBonusForBotRank(me->GetCreatureTemplate()->rank); + _baseLevel = std::max(mylevel, BotDataMgr::GetMinLevelForBotClass(_botclass)); + if (me->GetMap()->IsBattlegroundOrArena()) + TC_LOG_DEBUG("npcbots", "BG bot {} id {} selected level {}...", me->GetName(), me->GetEntry(), uint32(_baseLevel)); + else + TC_LOG_DEBUG("npcbots", "Wandering bot {} id {} selected level {}...", me->GetName(), me->GetEntry(), uint32(_baseLevel)); + } + else if (me->GetMap()->GetEntry()->IsContinent()) + { + uint8 mapmaxlevel = BotDataMgr::GetMaxLevelForMapId(me->GetMap()->GetEntry()->ID); + mapmaxlevel += BotDataMgr::GetLevelBonusForBotRank(me->GetCreatureTemplate()->rank); + //TODO: experience system for levelups + mylevel = std::max(mylevel, std::min(_baseLevel + uint8(uint32(float(_killsCount) * BotMgr::GetBotWandererXPGainMod()) / (mylevel * 20)), mapmaxlevel)); + } + } + else + mylevel += BotDataMgr::GetLevelBonusForBotRank(me->GetCreatureTemplate()->rank); + + mylevel = std::min(mylevel, DEFAULT_MAX_LEVEL + 3); + + //Do not remove this code + mylevel = std::max(mylevel, BotDataMgr::GetMinLevelForBotClass(_botclass)); + + //LEVEL + if (me->GetLevel() != mylevel) + { + if (me->GetLevel() > mylevel) + UnsummonAll(false); + + me->SetLevel(mylevel); + force = true; //reinit spells/passives/other + } + if (force) + { + InitPowers(); + InitSpells(); //this must stay before class passives + ApplyClassPassives(); + + sObjectMgr->GetPlayerClassLevelInfo(GetPlayerClass(), std::min(mylevel, DEFAULT_MAX_LEVEL), _classinfo); + + PlayerLevelInfo info; + sObjectMgr->GetPlayerLevelInfo(GetPlayerRace(), GetPlayerClass(), std::min(mylevel, DEFAULT_MAX_LEVEL), &info); + for (uint8 i = STAT_STRENGTH; i != MAX_STATS; ++i) + me->SetCreateStat(Stats(i), info.stats[i]); + } + + switch (myclass) + { + case BOT_CLASS_WARRIOR: + case BOT_CLASS_DEATH_KNIGHT: + case BOT_CLASS_PALADIN: + case BOT_CLASS_ROGUE: + case BOT_CLASS_HUNTER: + case BOT_CLASS_SHAMAN: + case BOT_CLASS_DRUID: + case BOT_CLASS_MAGE: + case BOT_CLASS_PRIEST: + case BOT_CLASS_WARLOCK: + case DRUID_BEAR_FORM: + case DRUID_CAT_FORM: + case DRUID_MOONKIN_FORM: + case DRUID_TREE_FORM: + case DRUID_TRAVEL_FORM: + case DRUID_AQUATIC_FORM: + //case DRUID_FLIGHT_FORM: + case BOT_CLASS_BM: + case BOT_CLASS_SPHYNX: + case BOT_CLASS_ARCHMAGE: + case BOT_CLASS_DREADLORD: + case BOT_CLASS_SPELLBREAKER: + case BOT_CLASS_DARK_RANGER: + case BOT_CLASS_NECROMANCER: + case BOT_CLASS_SEA_WITCH: + case BOT_CLASS_CRYPT_LORD: + break; + + default: + TC_LOG_ERROR("entities.player", "minion_ai: *etStats():Init - unknown bot class {}, real class: {}, _botclass: {}", myclass, GetPlayerClass(), _botclass); + break; + } + + float value; + float tempval; + float ap_mod = 1.0f, armor_mod = 1.0f; + + //DAMAGE PHYSICAL + for (uint8 i = 0; i != MAX_EQUIPMENT_ITEMS; ++i) + { + float weap_damage_base_min = _getBotStat(i, BOT_STAT_MOD_DAMAGE_MIN); + float weap_damage_base_max = _getBotStat(i, BOT_STAT_MOD_DAMAGE_MAX); + me->SetBaseWeaponDamage(WeaponAttackType(BASE_ATTACK + i), MINDAMAGE, std::max(weap_damage_base_min, 1.f)); + me->SetBaseWeaponDamage(WeaponAttackType(BASE_ATTACK + i), MAXDAMAGE, std::max(weap_damage_base_max, 1.f)); + } + + //Update Attack Time on main hand for shapeshifters + //do not add me->GetShapeshiftForm() check here, need to change attack time after shapeshift removal too + if (_botclass == BOT_CLASS_DRUID && RespectEquipsAttackTime()) + { + uint32 delay; + SpellShapeshiftFormEntry const* ssEntry = sSpellShapeshiftFormStore.LookupEntry(me->GetShapeshiftForm()); + if (!ssEntry || !ssEntry->CombatRoundTime) + delay = _equips[BOT_SLOT_MAINHAND] ? _equips[BOT_SLOT_MAINHAND]->GetTemplate()->Delay : me->GetCreatureTemplate()->BaseAttackTime; + else + delay = ssEntry->CombatRoundTime; + + me->SetAttackTime(BASE_ATTACK, delay); + } + + float atpower = float(me->GetLevel() * (/*IAmFree() ? 100 : */3)); //+8000/+240(legit) base ap at 80 + atpower += _getTotalBotStat(BOT_STAT_MOD_ATTACK_POWER); + + float strmult, agimult; + switch (myclass) + { + case BOT_CLASS_WARRIOR: + case BOT_CLASS_PALADIN: + case BOT_CLASS_DEATH_KNIGHT: + case BOT_CLASS_DRUID: + strmult = 2.f; agimult = 0.f; break; + case BOT_CLASS_ROGUE: + case BOT_CLASS_SHAMAN: + strmult = 1.f; agimult = 1.f; break; + case BOT_CLASS_HUNTER: + strmult = 0.5f; agimult = 1.f;break; //until attack power is separated + case BOT_CLASS_PRIEST: + case BOT_CLASS_MAGE: + case BOT_CLASS_WARLOCK: + strmult = 1.f; agimult = 0.f; break; + case DRUID_CAT_FORM: + strmult = 2.f; agimult = 1.f; break; + case DRUID_BEAR_FORM: + case DRUID_MOONKIN_FORM: + case DRUID_TREE_FORM: + case DRUID_TRAVEL_FORM: + case DRUID_AQUATIC_FORM: + //case DRUID_FLIGHT_FORM: + strmult = 2.f; agimult = 0.f; break; + case BOT_CLASS_BM: + strmult = 0.f; agimult = 9.f; break; + case BOT_CLASS_SPHYNX: + strmult = 2.f; agimult = 0.f; break; + case BOT_CLASS_ARCHMAGE: + strmult = 0.f; agimult = 0.f; break; + case BOT_CLASS_DREADLORD: + strmult = 8.f; agimult = 0.f; break; + case BOT_CLASS_SPELLBREAKER: + strmult = 5.f; agimult = 0.f; break; + case BOT_CLASS_DARK_RANGER: + strmult = 0.f; agimult = 4.f; break; + case BOT_CLASS_NECROMANCER: + strmult = 0.f; agimult = 0.f; break; + case BOT_CLASS_SEA_WITCH: + strmult = 0.f; agimult = 2.f; break; + case BOT_CLASS_CRYPT_LORD: + strmult = 9.f; agimult = 0.f; break; + default: + TC_LOG_ERROR("entities.player", "_MeleeDamageUpdate(): NIY myclass {}!", uint32(myclass)); + strmult = 0.f; agimult = 0.f; break; + } + + atpower += (strmult != 0x0) ? strmult * _getTotalBotStat(BOT_STAT_MOD_STRENGTH) : 0.f; + atpower += (agimult != 0x0) ? agimult * _getTotalBotStat(BOT_STAT_MOD_AGILITY) : 0.f; + + //hunter Expose Weakness checked + Unit::AuraEffectList const& mAPbyStat = me->GetAuraEffectsByType(SPELL_AURA_MOD_ATTACK_POWER_OF_STAT_PERCENT); + for (Unit::AuraEffectList::const_iterator i = mAPbyStat.begin(); i != mAPbyStat.end(); ++i) + atpower += CalculatePct(me->GetStat(Stats((*i)->GetMiscValue())), (*i)->GetAmount()); + + atpower += me->GetTotalAuraModifier(SPELL_AURA_MOD_ATTACK_POWER_OF_ARMOR); + + //Unit::AuraEffectList const& mAPbyArmor = me->GetAuraEffectsByType(SPELL_AURA_MOD_ATTACK_POWER_OF_ARMOR); + //for (Unit::AuraEffectList::const_iterator iter = mAPbyArmor.begin(); iter != mAPbyArmor.end(); ++iter) + // atpower += int32(me->GetArmor() / (*iter)->GetAmount()); + + //Handle mods + if (_botclass == BOT_CLASS_DRUID) + { + //Heart of the Wild part 3 + if (mylevel >= 35 && myclass == DRUID_CAT_FORM && GetSpec() == BOT_SPEC_DRUID_FERAL) + ap_mod *= 1.1f; + //Protector of the Pack part 2 + if (mylevel >= 45 && myclass == DRUID_BEAR_FORM && GetSpec() == BOT_SPEC_DRUID_FERAL) + ap_mod *= 1.06f; + } + if (_botclass == BOT_CLASS_ROGUE) + { + //Deadliness + if (mylevel >= 35 && GetSpec() == BOT_SPEC_ROGUE_SUBTLETY) + ap_mod *= 1.1f; + //Savage Combat + if (mylevel >= 50 && GetSpec() == BOT_SPEC_ROGUE_COMBAT) + ap_mod *= 1.04f; + } + //from stats mods + if (myclass == DRUID_BEAR_FORM || myclass == DRUID_CAT_FORM) + { + atpower += _getTotalBotStat(BOT_STAT_MOD_FERAL_ATTACK_POWER); + //Predatory Strikes + if (me->GetLevel() >= 25) + { + uint8 slot = BOT_SLOT_MAINHAND; + atpower += 1.5f * me->GetLevel(); + atpower += 0.2f * ( + _getBotStat(slot, BOT_STAT_MOD_FERAL_ATTACK_POWER) + + _getBotStat(slot, BOT_STAT_MOD_ATTACK_POWER) + //+ _getBotStat(slot, BOT_STAT_MOD_RANGED_ATTACK_POWER) + ); + } + } + if (_botclass == BOT_CLASS_HUNTER) + { + //Careful Aim + if (me->GetLevel() >= 15) + atpower += _getTotalBotStat(BOT_STAT_MOD_INTELLECT); + //Hunter vs. Wild + if (me->GetLevel() >= 30 && GetSpec() == BOT_SPEC_HUNTER_SURVIVAL) + atpower += 0.3f * _getTotalBotStat(BOT_STAT_MOD_STAMINA); + } + if (_botclass == BOT_CLASS_SHAMAN) + { + //Mental Dexterity + if (me->GetLevel() >= 30 && GetSpec() == BOT_SPEC_SHAMAN_ENHANCEMENT) + atpower += _getTotalBotStat(BOT_STAT_MOD_INTELLECT); + } + if (_botclass == BOT_CLASS_DARK_RANGER) + { + atpower += 2.f * _getTotalBotStat(BOT_STAT_MOD_INTELLECT); + if (me->GetLevel() >= 60) + ap_mod *= 1.15f; + } + if (_botclass == BOT_CLASS_SEA_WITCH) + { + if (me->GetLevel() >= 20) + atpower += 2.f * _getTotalBotStat(BOT_STAT_MOD_INTELLECT); + else if (me->GetLevel() >= 10) + atpower += 1.f * _getTotalBotStat(BOT_STAT_MOD_INTELLECT); + } + + atpower *= ap_mod; + me->SetStatFlatModifier(UNIT_MOD_ATTACK_POWER, BASE_VALUE, atpower); + + me->UpdateAttackPowerAndDamage(); + if (_botclass == BOT_CLASS_WARRIOR || _botclass == BOT_CLASS_HUNTER || _botclass == BOT_CLASS_ROGUE || + _botclass == BOT_CLASS_MAGE || _botclass == BOT_CLASS_PRIEST || _botclass == BOT_CLASS_WARLOCK || + _botclass == BOT_CLASS_DARK_RANGER || _botclass == BOT_CLASS_SEA_WITCH) + { + atpower += _getTotalBotStat(BOT_STAT_MOD_RANGED_ATTACK_POWER) * ap_mod; + me->SetStatFlatModifier(UNIT_MOD_ATTACK_POWER_RANGED, BASE_VALUE, atpower); + me->UpdateAttackPowerAndDamage(true); + } + + //ARMOR + //value = IAmFree() ? 0 : me->GetLevel() * 10; //0/800 at 80 + value = 2.f * _getTotalBotStat(BOT_STAT_MOD_AGILITY); + value += _getTotalBotStat(BOT_STAT_MOD_ARMOR); + + if (mylevel >= 10) + { + //Toughness + if (mylevel >= 20 && (_botclass == BOT_CLASS_WARRIOR || _botclass == BOT_CLASS_PALADIN || _botclass == BOT_CLASS_DEATH_KNIGHT)) + armor_mod += 0.1f; + //Frost Presence + if (GetBotStance() == DEATH_KNIGHT_FROST_PRESENCE) + armor_mod += 0.6f; + if (_botclass == BOT_CLASS_DRUID) + { + //Thick Hide + if (mylevel >= 15) + armor_mod += 0.1f; + //Survival of the Fittest + if (myclass == DRUID_BEAR_FORM) + armor_mod += (GetSpec() == BOT_SPEC_DRUID_FERAL ? 0.33f : 0.0f) + (me->GetShapeshiftForm() == FORM_BEAR ? 1.8f : 3.7f); + //Moonkin Form innate + else if (myclass == DRUID_MOONKIN_FORM) + armor_mod += 3.7f; + //Improved Tree Form + else if (myclass == DRUID_TREE_FORM) + armor_mod += 2.0f; + //Improved Barkskin + //else if (myclass == DRUID_TRAVEL_FORM || GetBotStance() == BOT_STANCE_NONE) + // armor_mod += 1.6f; + } + if (_botclass == BOT_CLASS_HUNTER) + { + //Thick Hide + if (mylevel >= 15) + armor_mod += 0.1f; + } + if (_botclass == BOT_CLASS_MAGE) + { + //Arcane Fortitude + if (mylevel >= 15) + value += 1.5f * _getTotalBotStat(BOT_STAT_MOD_INTELLECT); + } + if (_botclass == BOT_CLASS_SPHYNX) + { + value += 5.f * _getTotalBotStat(BOT_STAT_MOD_INTELLECT); + armor_mod += 0.5f; + } + if (_botclass == BOT_CLASS_ARCHMAGE) + { + value += 5.f * _getTotalBotStat(BOT_STAT_MOD_INTELLECT); + } + if (_botclass == BOT_CLASS_DREADLORD) + { + armor_mod += 0.5f; + } + if (_botclass == BOT_CLASS_SPELLBREAKER) + { + armor_mod += -0.3f; // reduce armor so cannot really tank + } + if (_botclass == BOT_CLASS_NECROMANCER) + { + value += 5.f * _getTotalBotStat(BOT_STAT_MOD_INTELLECT); + } + if (_botclass == BOT_CLASS_CRYPT_LORD) + { + armor_mod += mylevel >= 60 ? 1.0f : mylevel >= 40 ? 0.5f : mylevel >= 20 ? 0.25f : 0.125f; + } + } + + value *= armor_mod; + //Druid armor mods should not affect armor from weapons + if (_botclass == BOT_CLASS_DRUID && _stats[BOT_SLOT_MAINHAND][BOT_STAT_MOD_ARMOR] != 0 && armor_mod > 1.f) + value -= _stats[BOT_SLOT_MAINHAND][BOT_STAT_MOD_ARMOR] * (armor_mod - 1.f); + me->SetStatFlatModifier(UNIT_MOD_ARMOR, BASE_VALUE, value); + me->UpdateArmor(); //buffs will be processed here + + //RESISTANCES + //Do not store resistance bonuses directly lest we want calcs screwed up + for (uint8 i = SPELL_SCHOOL_HOLY; i != MAX_SPELL_SCHOOL; ++i) + { + value = IAmFree() ? 0 : mylevel; + value += _getTotalBotStat(BotStatMods(BOT_STAT_MOD_RESIST_HOLY + (i - 1))); + + //res bonuses + if (_botclass == BOT_CLASS_SPHYNX) + value += mylevel * 5; //total 498 at 83 + if (_botclass == BOT_CLASS_DREADLORD) + value += mylevel * 3; //total 332 at 83 + if (_botclass == BOT_CLASS_DARK_RANGER || _botclass == BOT_CLASS_SEA_WITCH || _botclass == BOT_CLASS_CRYPT_LORD) + value += mylevel * 2; //total 249 at 83 + + resistbonus[i-1] = int32(value); + //me->UpdateResistances(i); + } + + //DAMAGE TAKEN + value = 1.0f; + tempval = 1.0f; + + //class-specified + //Protector of the Pack part 1 + if (myclass == DRUID_BEAR_FORM && mylevel >= 45) + { + value -= 0.12f; + tempval -= 0.12f; + } + //Deadened Nerves + if (_botclass == BOT_CLASS_ROGUE && mylevel >= 45 && GetSpec() == BOT_SPEC_ROGUE_ASSASINATION) + { + value -= 0.06f; + tempval -= 0.06f; + } + //Survival Instincts + if (_botclass == BOT_CLASS_HUNTER && mylevel >= 15) + { + value -= 0.04f; + tempval -= 0.04f; + } + //Spell Warding + if (_botclass == BOT_CLASS_PRIEST && mylevel >= 15) + tempval -= 0.1f; + //Elemental Warding + if (_botclass == BOT_CLASS_SHAMAN && mylevel >= 15) + { + value -= 0.06f; + tempval -= 0.06f; + } + if (_botclass == BOT_CLASS_DEATH_KNIGHT) + { + //Magic Suppression (everything) + if (mylevel >= 60 && GetSpec() == BOT_SPEC_DK_UNHOLY) + tempval -= 0.06f; + //Improved Frost Presence + if (mylevel >= 61 && GetBotStance() == DEATH_KNIGHT_FROST_PRESENCE && GetSpec() == BOT_SPEC_DK_FROST) + { + value -= 0.02f; + tempval -= 0.02f; + } + } + if (_botclass == BOT_CLASS_WARLOCK) + { + //Molten Skin + if (mylevel >= 15) + { + value -= 0.06f; + tempval -= 0.06f; + } + //Master Demonologist part 2, Master Demonologist part 4 + if (mylevel >= 35 && GetSpec() == BOT_SPEC_WARLOCK_DEMONOLOGY && botPet && botPet->IsAlive()) + { + if (GetAIMiscValue(BOTAI_MISC_PET_TYPE) == BOT_PET_VOIDWALKER) + value -= 0.1f; + else if (GetAIMiscValue(BOTAI_MISC_PET_TYPE) == BOT_PET_FELHUNTER) + tempval -= 0.1f; + } + } + //Frozen Core (everything), Prismatic Cloak part 1 + if (_botclass == BOT_CLASS_MAGE) + { + if (mylevel >= 30 && GetSpec() == BOT_SPEC_MAGE_FROST) + tempval -= 0.06f; + else if (mylevel >= 35 && GetSpec() == BOT_SPEC_MAGE_ARCANE) + { + value -= 0.06f; + tempval -= 0.06f; + } + } + if (_botclass == BOT_CLASS_SPHYNX) + { + value -= 0.33f; + tempval -= 0.33f; + } + if (_botclass == BOT_CLASS_ARCHMAGE) + { + value -= 0.1f; + tempval -= 0.35f; + } + if (_botclass == BOT_CLASS_DREADLORD) + { + value -= 0.15f; + tempval -= 0.2f; + } + if (_botclass == BOT_CLASS_SPELLBREAKER) + { + value -= 0.2f; + tempval -= 0.75f; + } + if (_botclass == BOT_CLASS_DARK_RANGER) + { + tempval -= 0.35f; + } + if (_botclass == BOT_CLASS_NECROMANCER) + { + tempval -= 0.2f; + } + if (_botclass == BOT_CLASS_SEA_WITCH) + { + tempval -= 0.3f; + } + if (_botclass == BOT_CLASS_CRYPT_LORD) + { + value -= 0.3f; + tempval -= 0.15f; + } + + dmg_taken_phy = value; + dmg_taken_mag = tempval; + + //RESILIENCE + value = 0.f; + + tempval = std::max(_getTotalBotStat(BOT_STAT_MOD_CRIT_TAKEN_MELEE_RATING), std::max(_getTotalBotStat(BOT_STAT_MOD_CRIT_TAKEN_RANGED_RATING), _getTotalBotStat(BOT_STAT_MOD_CRIT_TAKEN_SPELL_RATING))); + tempval += me->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_RATING, (1 << CR_CRIT_TAKEN_MELEE) | (1 << CR_CRIT_TAKEN_RANGED) | (1 << CR_CRIT_TAKEN_SPELL)); + value += tempval * std::max(_getRatingMultiplier(CR_CRIT_TAKEN_MELEE), std::max(_getRatingMultiplier(CR_CRIT_TAKEN_RANGED), _getRatingMultiplier(CR_CRIT_TAKEN_SPELL))); + + resilience = value; + + //HEALTH + _OnHealthUpdate(); + + //HASTE + if (haste) + { + //unapply old haste + for (uint8 att = BASE_ATTACK; att != MAX_ATTACK; ++att) + me->ApplyAttackTimePercentMod(WeaponAttackType(att), float(haste), false); + me->ApplyCastTimePercentMod(float(haste), false); + } + + value = IAmFree() ? std::max(int32(mylevel) - 50, 0) : 0; // +30%/+0% haste at 80 + + //25.5 HR = 1% haste at 80 + tempval = _getTotalBotStat(BOT_STAT_MOD_HASTE_MELEE_RATING) + _getTotalBotStat(BOT_STAT_MOD_HASTE_RANGED_RATING) + _getTotalBotStat(BOT_STAT_MOD_HASTE_SPELL_RATING) + _getTotalBotStat(BOT_STAT_MOD_HASTE_RATING); + tempval += me->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_RATING, (1 << CR_HASTE_MELEE) | (1 << CR_HASTE_RANGED) | (1 << CR_HASTE_SPELL)); + + if (_botclass == BOT_CLASS_WARLOCK) + { + //Spellstone: just emulate the rating bonus + uint8 ratingBonus; + if (mylevel >= 78) ratingBonus = 60; + else if (mylevel >= 72) ratingBonus = 50; + else if (mylevel >= 66) ratingBonus = 40; + else if (mylevel >= 60) ratingBonus = 30; + else if (mylevel >= 48) ratingBonus = 20; + else if (mylevel >= 36) ratingBonus = 10; + else ratingBonus = 0; + + //Master Conjuror + if (mylevel >= 30 && GetSpec() == BOT_SPEC_WARLOCK_DEMONOLOGY) + ratingBonus *= 4; + + tempval += (float)ratingBonus; + } + + value += tempval * ((_botclass == BOT_CLASS_HUNTER || _botclass == BOT_CLASS_DARK_RANGER || _botclass == BOT_CLASS_SEA_WITCH) ? + _getRatingMultiplier(CR_HASTE_RANGED) : + std::max(_getRatingMultiplier(CR_HASTE_MELEE), _getRatingMultiplier(CR_HASTE_SPELL))); + + //class-specific + if (_botclass == BOT_CLASS_HUNTER) + { + value += 15.f; //innate ranged haste bonus 15% for hunters (still applies to all haste types) + //Serpent's Swiftness + if (mylevel >= 45 && GetSpec() == BOT_SPEC_HUNTER_BEASTMASTERY) + value += 20.f; + } + if (_botclass == BOT_CLASS_ROGUE) + { + //Lightning Reflexes part 2 + if (mylevel >= 25 && GetSpec() == BOT_SPEC_ROGUE_COMBAT) + value += 10.f; + } + if (_botclass == BOT_CLASS_PRIEST) + { + //Enlightenment part 2 + if (mylevel >= 35 && GetSpec() == BOT_SPEC_PRIEST_DISCIPLINE) + value += 6.f; + } + if (_botclass == BOT_CLASS_MAGE) + { + //Netherwind Presence + if (mylevel >= 55 && GetSpec() == BOT_SPEC_MAGE_ARCANE) + value += 6.f; + } + if (_botclass >= BOT_CLASS_EX_START) + { + float haste_per_lvl; + switch (_botclass) + { + case BOT_CLASS_BM: + case BOT_CLASS_DREADLORD: + haste_per_lvl = 0.875f; + break; + case BOT_CLASS_ARCHMAGE: + case BOT_CLASS_DARK_RANGER: + case BOT_CLASS_SEA_WITCH: + haste_per_lvl = 0.5f; + break; + case BOT_CLASS_CRYPT_LORD: + haste_per_lvl = 0.35f; + break; + default: + haste_per_lvl = 0.25f; + break; + } + value += mylevel * haste_per_lvl; + } + + haste = int32(value); + + if (haste) + { + //apply new haste (using truncated value - gonna need it for unapply on next SetStats) + for (uint8 att = BASE_ATTACK; att != MAX_ATTACK; ++att) + me->ApplyAttackTimePercentMod(WeaponAttackType(att), float(haste), true); + me->ApplyCastTimePercentMod(float(haste), true); + } + + //HIT + if (CanMiss()) + { + value = IAmFree() ? mylevel / 8 : 0; // +10%/+0% at 80 + //32.5 HR = 1% hit at 80 + tempval = _getTotalBotStat(BOT_STAT_MOD_HIT_MELEE_RATING) + _getTotalBotStat(BOT_STAT_MOD_HIT_RANGED_RATING) + _getTotalBotStat(BOT_STAT_MOD_HIT_SPELL_RATING) + _getTotalBotStat(BOT_STAT_MOD_HIT_RATING); + tempval += me->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_RATING, (1 << CR_HIT_MELEE) | (1 << CR_HIT_RANGED) | (1 << CR_HIT_SPELL)); + value += tempval * (_botclass == BOT_CLASS_HUNTER ? _getRatingMultiplier(CR_HIT_RANGED) : std::max(_getRatingMultiplier(CR_HIT_MELEE), _getRatingMultiplier(CR_HIT_SPELL))); + + //class-specific + //Precision + if (_botclass == BOT_CLASS_ROGUE && mylevel >= 15) + value += 5.f; + //Enlightened Judgements part 2,3 + if (_botclass == BOT_CLASS_PALADIN && GetSpec() == BOT_SPEC_PALADIN_HOLY && mylevel >= 55) + value += 4.f; + //Virulence part 1, Nerves of Cold Steel part 1 + if (_botclass == BOT_CLASS_DEATH_KNIGHT) + value += 3.f; + //Dual Wield Specialization + if (_botclass == BOT_CLASS_SHAMAN && mylevel >= 40 && me->haveOffhandWeapon()) + value += 6.f; + //Precision + if (_botclass == BOT_CLASS_WARRIOR && mylevel >= 30 && GetSpec() == BOT_SPEC_WARRIOR_FURY) + value += 3.f; + //Focused Aim + if (_botclass == BOT_CLASS_HUNTER && mylevel >= 10) + value += 3.f; + //Shadow Focus part 1 + if (_botclass == BOT_CLASS_PRIEST && mylevel >= 15) + value += 3.f; + //Arcane Focus part 1, Precision part 2 + if (_botclass == BOT_CLASS_MAGE && mylevel >= 10) + value += mylevel >= 15 ? 6.f : 3.f; + //Suppression + if (_botclass == BOT_CLASS_WARLOCK && mylevel >= 10) + value += 3.f; + + hit = value; + } + else + hit = 100.0f; + + //ARMOR PENETRATION + value = IAmFree() ? 5 + mylevel / 4 : 0; // 25%/0% at 80 + //? APR = 1% armor ignored at 80 + tempval = _getTotalBotStat(BOT_STAT_MOD_ARMOR_PENETRATION_RATING); + tempval += me->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_RATING, (1 << CR_ARMOR_PENETRATION)); + value += tempval * _getRatingMultiplier(CR_ARMOR_PENETRATION); + + //class-specific + //Blood Gorged + if (_botclass == BOT_CLASS_DEATH_KNIGHT && mylevel >= 64 && GetSpec() == BOT_SPEC_DK_BLOOD) + value += 10.f; + + if (_botclass == BOT_CLASS_DARK_RANGER) + value += 50.f; + + armor_pen = value; + + //EXPERTISE + value = IAmFree() ? mylevel / 2 : 0; // -10%/-0% at 80 + //~8.0 ER = 1 expertise at 80 + tempval = _getTotalBotStat(BOT_STAT_MOD_EXPERTISE_RATING); + tempval += me->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_RATING, (1 << CR_EXPERTISE)); + value += tempval * _getRatingMultiplier(CR_EXPERTISE); + + //class-specific + //Weapon Expertise + if (mylevel >= 35 && _botclass == BOT_CLASS_ROGUE && GetSpec() == BOT_SPEC_ROGUE_COMBAT) + value += 10.f; + //Combat Expertise + if (mylevel >= 45 && _botclass == BOT_CLASS_PALADIN && GetSpec() == BOT_SPEC_PALADIN_PROTECTION) + value += 6.f; + if (_botclass == BOT_CLASS_WARRIOR) + { + //Vitality: 6, Strength of Arms: 4 + if (mylevel >= 45 && GetSpec() == BOT_SPEC_WARRIOR_PROTECTION) + value += 10.f; + else if (mylevel >= 40 && GetSpec() == BOT_SPEC_WARRIOR_ARMS) + value += 4.f; + } + if (_botclass == BOT_CLASS_DEATH_KNIGHT) + { + //Tundra Stalker, Rage of Rivendare: 5 + //Veteral of the Third War part 3: 6 + if (mylevel >= 64 && GetSpec() == BOT_SPEC_DK_FROST) + value += 5.f; + else if (mylevel >= 64 && GetSpec() == BOT_SPEC_DK_UNHOLY) + value += 5.f; + else if (mylevel >= 59 && GetSpec() == BOT_SPEC_DK_BLOOD) + value += 6.f; + } + if (_botclass == BOT_CLASS_DREADLORD) + { + value += 40.f; + } + if (_botclass == BOT_CLASS_CRYPT_LORD) + { + value += 20.f; + } + + expertise = value; + + //CRIT + if (CanCrit()) + { + value = IAmFree() ? mylevel / 4 : 0; // +20%/+0% at 80 + tempval = value; + + GtChanceToMeleeCritBaseEntry const* critBaseMelee = sGtChanceToMeleeCritBaseStore.LookupEntry(GetPlayerClass()-1); + GtChanceToMeleeCritEntry const* critRatioMelee = sGtChanceToMeleeCritStore.LookupEntry((GetPlayerClass()-1)*GT_MAX_LEVEL + mylevel-1); + if (critBaseMelee && critRatioMelee) + value += (critBaseMelee->Data + _getTotalBotStat(BOT_STAT_MOD_AGILITY) * critRatioMelee->Data) * 100.0f; + + //crit from intellect + GtChanceToSpellCritBaseEntry const* critBaseSpell = sGtChanceToSpellCritBaseStore.LookupEntry(GetPlayerClass()-1); + GtChanceToSpellCritEntry const* critRatioSpell = sGtChanceToSpellCritStore.LookupEntry((GetPlayerClass()-1)*GT_MAX_LEVEL + mylevel-1); + if (critBaseSpell && critRatioSpell) + tempval += (critBaseSpell->Data + _getTotalBotStat(BOT_STAT_MOD_INTELLECT) * critRatioSpell->Data) * 100.f; + + value = std::max(value, tempval); + + //45 CR = 1% crit at 80 + tempval = _getTotalBotStat(BOT_STAT_MOD_CRIT_MELEE_RATING) + _getTotalBotStat(BOT_STAT_MOD_CRIT_RANGED_RATING) + _getTotalBotStat(BOT_STAT_MOD_CRIT_SPELL_RATING) + _getTotalBotStat(BOT_STAT_MOD_CRIT_RATING); + tempval += me->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_RATING, (1 << CR_CRIT_MELEE) | (1 << CR_CRIT_RANGED) | (1 << CR_CRIT_SPELL)); + + //Molten Armor: 35% spirit to crit rating (+40% double-glyphed + 15% T9P2 bonus) + if (_botclass == BOT_CLASS_MAGE && me->HasAuraTypeWithFamilyFlags(SPELL_AURA_MOD_RATING_FROM_STAT, SPELLFAMILY_MAGE, 0x40000)) + tempval += _getTotalBotStat(BOT_STAT_MOD_SPIRIT) * (mylevel >= 80 ? 0.9f : mylevel >= 70 ? 0.75f : 0.55f); + //Firestone: just emulate the rating bonus + if (_botclass == BOT_CLASS_WARLOCK) + { + uint8 ratingBonus; + if (mylevel >= 80) ratingBonus = 49; + else if (mylevel >= 74) ratingBonus = 42; + else if (mylevel >= 66) ratingBonus = 35; + else if (mylevel >= 56) ratingBonus = 28; + else if (mylevel >= 46) ratingBonus = 21; + else if (mylevel >= 36) ratingBonus = 14; + else if (mylevel >= 28) ratingBonus = 7; + else ratingBonus = 0; + + //Master Conjuror + if (mylevel >= 30 && GetSpec() == BOT_SPEC_WARLOCK_DEMONOLOGY) + ratingBonus *= 4; + + tempval += (float)ratingBonus; + } + + value += tempval * (_botclass == BOT_CLASS_HUNTER ? _getRatingMultiplier(CR_CRIT_RANGED) : std::max(_getRatingMultiplier(CR_CRIT_MELEE), _getRatingMultiplier(CR_CRIT_SPELL))); + + //common crit talents + if (mylevel >= 10 && + (_botclass != BOT_CLASS_MAGE && _botclass != BOT_CLASS_PRIEST && + _botclass != BOT_CLASS_DRUID && _botclass != BOT_CLASS_WARLOCK)) + value += 5.f; + + //class-specific + if (_botclass == BOT_CLASS_DRUID) + { + //Sharpened Claws + if (mylevel >= 20 && (myclass == DRUID_CAT_FORM || myclass == DRUID_BEAR_FORM)) + value += 6.f; + } + if (_botclass == BOT_CLASS_ROGUE) + { + //Close Quarters Combat + if (mylevel >= 20) + { + if (Item const* mainhand = _equips[BOT_SLOT_MAINHAND]) + { + if (mainhand->GetTemplate()->Class == ITEM_CLASS_WEAPON && + (mainhand->GetTemplate()->SubClass == ITEM_SUBCLASS_WEAPON_DAGGER || + mainhand->GetTemplate()->SubClass == ITEM_SUBCLASS_WEAPON_FIST_WEAPON)) + value += 5.f; + } + } + } + if (_botclass == BOT_CLASS_PALADIN) + { + //Sanctity of Battle part 1 + if (mylevel >= 25 && GetSpec() == BOT_SPEC_PALADIN_RETRIBUTION) + value += 3.f; + //Combat Expertise + if (mylevel >= 45 && GetSpec() == BOT_SPEC_PALADIN_PROTECTION) + value += 6.f; + } + if (_botclass == BOT_CLASS_HUNTER) + { + //Killer Instinct + if (mylevel >= 30 && GetSpec() == BOT_SPEC_HUNTER_BEASTMASTERY) + value += 3.f; + //Master Marksman + if (mylevel >= 45 && GetSpec() == BOT_SPEC_HUNTER_MARKSMANSHIP) + value += 5.f; + } + if (_botclass == BOT_CLASS_PRIEST) + { + //Focused Will part 1 + if (mylevel >= 40 && GetSpec() == BOT_SPEC_PRIEST_DISCIPLINE) + value += 3.f; + } + if (_botclass == BOT_CLASS_DEATH_KNIGHT) + { + //Annihilation part 1 + if (mylevel >= 57) + value += 3.f; + } + if (_botclass == BOT_CLASS_WARLOCK) + { + //Backlash + if (mylevel >= 30) + value += 3.f; + //Demonic Tactics part 1, part 2 (me) + if (mylevel >= 45 && GetSpec() == BOT_SPEC_WARLOCK_DEMONOLOGY) + value += 10.f; + } + if (_botclass == BOT_CLASS_MAGE) + { + //Arcane Instability part 2 + if (mylevel >= 35 && GetSpec() == BOT_SPEC_MAGE_ARCANE) + value += 3.f; + } + if (_botclass == BOT_CLASS_DREADLORD) + { + value = value * 2.f; + } + if (_botclass == BOT_CLASS_DARK_RANGER) + { + value += 20.f; + } + + if (BotMgr::IsBotStatsLimitsEnabled()) + crit = std::min(value, BotMgr::GetBotStatLimitCrit()); + else + crit = value; + + if (crit < 0.0f) + crit = 0.0f; + } + else + crit = 0.0f; + + //DEFENSE + value = 0.f; + tempval = _getTotalBotStat(BOT_STAT_MOD_DEFENSE_SKILL_RATING); + tempval += me->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_RATING, (1 << CR_DEFENSE_SKILL)); + value += tempval * _getRatingMultiplier(CR_DEFENSE_SKILL); + value += me->GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_SKILL, SKILL_DEFENSE); + defense = mylevel * 5 + uint32(value); //truncate + + float defbonus = defense - mylevel * 5; //difference + + //PARRY + if (CanParry()) + { + value = 5.0f + (IAmFree() ? mylevel / 8 : 0); // +10%/+0% at 80 + + if (mylevel >= 10) + { + //67 PR = 1% parry at 80 + tempval = _getTotalBotStat(BOT_STAT_MOD_PARRY_RATING); + tempval += me->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_RATING, (1 << CR_PARRY)); + + //Forceful Deflection: 25% of strength goes to parry rating + if (_botclass == BOT_CLASS_DEATH_KNIGHT/* && mylevel >= 55*/) + tempval += _getTotalBotStat(BOT_STAT_MOD_STRENGTH) * 0.25f; + + value += tempval * _getRatingMultiplier(CR_PARRY); + //125 DR = 1% block/parry/dodge at 80 + value += defbonus * 0.04f; + } + + //Deflection (general) + if ((_botclass == BOT_CLASS_WARRIOR || _botclass == BOT_CLASS_ROGUE || _botclass == BOT_CLASS_PALADIN) && mylevel >= 10) + value += 5.0f; + if (_botclass == BOT_CLASS_HUNTER && mylevel >= 20) + value += 3.f; + + if (_botclass == BOT_CLASS_SEA_WITCH) + value += 25.f; + + if (BotMgr::IsBotStatsLimitsEnabled()) + parry = std::min(value, BotMgr::GetBotStatLimitParry()); + else + parry = value; + + if (parry < 0.0f) + parry = 0.0f; + } + else + parry = 0.0f; + + //DODGE + if (CanDodge()) + { + value = 5.0f + (IAmFree() ? mylevel / 8 : 0); // +10%/+0% at 80 + + if (GtChanceToMeleeCritEntry const* dodgeRatio = sGtChanceToMeleeCritStore.LookupEntry((GetPlayerClass()-1)*GT_MAX_LEVEL + mylevel-1)) + value += _getTotalBotStat(BOT_STAT_MOD_AGILITY) * dodgeRatio->Data * 100.0f; + + if (mylevel >= 10) + { + //53 DR = 1% dodge at 80 + tempval = _getTotalBotStat(BOT_STAT_MOD_DODGE_RATING); + tempval += me->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_RATING, (1 << CR_DODGE)); + value += tempval * _getRatingMultiplier(CR_DODGE); + //125 DR = 1% block/parry/dodge at 80 + value += defbonus * 0.04f; + } + + //evasion, anticipation (general) + if ((_botclass == BOT_CLASS_WARRIOR || _botclass == BOT_CLASS_ROGUE || _botclass == BOT_CLASS_PALADIN || + _botclass == BOT_CLASS_DEATH_KNIGHT || _botclass == BOT_CLASS_SHAMAN) && mylevel >= 15) + value += 5.0f; + + //class-specific + if (_botclass == BOT_CLASS_DRUID) + { + //Feral Swiftness + if (mylevel >= 20 && (myclass == DRUID_CAT_FORM || myclass == DRUID_BEAR_FORM)) + value += 4.f; + } + + if (_botclass == BOT_CLASS_DARK_RANGER) + { + //base dodge 30% + value += 30.f; + } + + if (_botclass == BOT_CLASS_SEA_WITCH && IsInContactWithWater()) + { + //TC_LOG_ERROR("scripts", "BOT_CLASS_SEA_WITCH dodge: {} now in water", me->GetName()); + value += 50.f; + } + + if (BotMgr::IsBotStatsLimitsEnabled()) + dodge = std::min(value, BotMgr::GetBotStatLimitDodge()); + else + dodge = value; + + if (dodge < 0.0f) + dodge = 0.0f; + } + else + dodge = 0.0f; + + //BLOCK + if (IsBlockingClass(_botclass)) + { + value = 5.0f + (IAmFree() ? mylevel / 4 : 0); // +20%/+0% at 80 + + //16.5 BR = 1% block at 80 + tempval = _getTotalBotStat(BOT_STAT_MOD_BLOCK_RATING); + tempval += me->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_RATING, (1 << CR_BLOCK)); + value += tempval * _getRatingMultiplier(CR_BLOCK); + //125 DR = 1% block/parry/dodge at 80 + value += defbonus * 0.04f; + + //base block chance is capped at 75% + if (BotMgr::IsBotStatsLimitsEnabled()) + block = std::min(value, BotMgr::GetBotStatLimitBlock()); + else + block = std::min(value, 75.0f); + + if (block < 0.0f) + block = 0.0f; + + //Spellbreaker wears tall shield so should always block + if (_botclass == BOT_CLASS_SPELLBREAKER) + block += 90.f; + + //BLOCK VALUE + //2 str = 1 block value + value = 0.5f * _getTotalBotStat(BOT_STAT_MOD_STRENGTH) - 10.f; + value += _getTotalBotStat(BOT_STAT_MOD_BLOCK_VALUE); + + //Shield Mastery part 1 + if (_botclass == BOT_CLASS_WARRIOR && mylevel >= 20 && GetSpec() == BOT_SPEC_WARRIOR_PROTECTION) + value *= 1.3f; + //Redoubt handled in passives + //if (mylevel >= 45 && _botclass == BOT_CLASS_PALADIN) + // value *= 1.3f; + + blockvalue = std::max(int32(value), 1.f); + } + //else + //{ + // block = 0.0f; + // blockvalue = 0; + //} + + //MANA + _OnManaUpdate(); + + if (IsCastingClass(_botclass)) + { + //SPELL PENETRATION + value = IAmFree() ? mylevel : 0; // 80/0 at 80 + //~1 SPPR = 1 spell penetration + value += _getTotalBotStat(BOT_STAT_MOD_SPELL_PENETRATION); + spellpen = uint32(value); + + //SPELL POWER + value = /*IAmFree() ? std::max((int8(mylevel) - 30) * 40, 0) : */0; // +2000/+0 spp at 80 + value += _getTotalBotStat(BOT_STAT_MOD_SPELL_POWER); + + //class-specified mods + if (_botclass == BOT_CLASS_PALADIN && mylevel >= 50) + { + //Touched by the Light - 60% of strength to spell power + if (GetSpec() == BOT_SPEC_PALADIN_PROTECTION) + value += 0.6f * _getTotalBotStat(BOT_STAT_MOD_STRENGTH); + //Sheath of Light - 30% attack power to spell power + if (GetSpec() == BOT_SPEC_PALADIN_RETRIBUTION) + value += 0.3f * me->GetTotalAttackPowerValue(BASE_ATTACK); + //Holy Guidance - 20% Intellect to spell power + if (GetSpec() == BOT_SPEC_PALADIN_HOLY) + value += 0.2f * _getTotalBotStat(BOT_STAT_MOD_INTELLECT); + } + if (_botclass == BOT_CLASS_PRIEST && mylevel >= 30) + { + float totalSpi = _getTotalBotStat(BOT_STAT_MOD_SPIRIT); + //Spiritual Guidance - 25% Spirit to spell power + if (GetSpec() == BOT_SPEC_PRIEST_HOLY) + value += 0.25f * totalSpi; + //Twisted Faith - 20% Spirit to spell power + else if (mylevel >= 55 && GetSpec() == BOT_SPEC_PRIEST_SHADOW) + value += 0.2f * totalSpi; + //Shadowy Insight (Glyph of Shadow) + if (mylevel >= 30 && + me->GetAuraEffect(SPELL_AURA_MOD_SPELL_DAMAGE_OF_STAT_PERCENT, SPELLFAMILY_GENERIC, 1499, 0)) + value += 0.3f * totalSpi; + } + if (_botclass == BOT_CLASS_SHAMAN && mylevel >= 50 && GetSpec() == BOT_SPEC_SHAMAN_ENHANCEMENT) + { + //Mental Quickness - 30% attack power to spell power (only enhancement) + value += 0.3f * me->GetTotalAttackPowerValue(BASE_ATTACK); + } + if (_botclass == BOT_CLASS_DRUID && mylevel >= 30) + { + //Nurturing Instinct - 70% Agility to spell power + value += 0.7f * _getTotalBotStat(BOT_STAT_MOD_AGILITY); + //Lunar Guidance - 12% Intellect to spell power + value += 0.12f * _getTotalBotStat(BOT_STAT_MOD_INTELLECT); + //Improved Moonkin Form - 30% Spirit to spell power + if (mylevel >= 40 && myclass == DRUID_MOONKIN_FORM) + value += 0.3f * _getTotalBotStat(BOT_STAT_MOD_SPIRIT); + //Improved Tree (of Life) Form - 15% Spirit to spell power + if (mylevel >= 50 && myclass == DRUID_TREE_FORM) + value += 0.15f * _getTotalBotStat(BOT_STAT_MOD_SPIRIT); + } + if (_botclass == BOT_CLASS_MAGE && mylevel >= 45 && GetSpec() == BOT_SPEC_MAGE_ARCANE) + { + //Mind Mastery - 15% Intellect to spell power + value += 0.15f * _getTotalBotStat(BOT_STAT_MOD_INTELLECT); + } + if (_botclass == BOT_CLASS_WARLOCK) + { + if (me->GetAuraEffect(SPELL_AURA_MOD_SPELL_DAMAGE_OF_STAT_PERCENT, SPELLFAMILY_WARLOCK, 0x0, 0x20000000, 0x0)) + { + //Fel Armor + Demonic Aegis - 39% Spirit to spell power + value += 0.39f * _getTotalBotStat(BOT_STAT_MOD_SPIRIT); + } + //Demonic Knowledge + if (botPet && botPet->IsAlive() && mylevel >= 40 && GetSpec() == BOT_SPEC_WARLOCK_DEMONOLOGY) + value += 0.12f * botPet->GetStat(STAT_STAMINA) + botPet->GetStat(STAT_INTELLECT); + //Glyph of Life Tap: 20% of spirit to spellpower + if (me->GetAuraEffect(SPELL_AURA_MOD_SPELL_DAMAGE_OF_STAT_PERCENT, SPELLFAMILY_WARLOCK, 208, 0)) + value += 0.2f * _getTotalBotStat(BOT_STAT_MOD_SPIRIT); + } + if (_botclass == BOT_CLASS_SPHYNX) + { + //bonus from attack power (for tank) or intellect (ranged) + value += 2.0f *_getTotalBotStat(BOT_STAT_MOD_INTELLECT); + value += 0.5f * me->GetTotalAttackPowerValue(BASE_ATTACK); + //from wands + for (uint8 i = BOT_SLOT_MAINHAND; i <= BOT_SLOT_OFFHAND; ++i) + if (ItemTemplate const* proto = _equips[i] ? _equips[i]->GetTemplate() : nullptr) + value += proto->getDPS() * 1.35f; + } + if (_botclass == BOT_CLASS_ARCHMAGE) + { + //bonus from intellect + value += _getTotalBotStat(BOT_STAT_MOD_INTELLECT); + } + if (_botclass == BOT_CLASS_DREADLORD) + { + //bonus from strength + value += 2.f * _getTotalBotStat(BOT_STAT_MOD_STRENGTH); + } + if (_botclass == BOT_CLASS_SPELLBREAKER) + { + //bonus from strength + value += 2.f * _getTotalBotStat(BOT_STAT_MOD_STRENGTH); + } + if (_botclass == BOT_CLASS_DARK_RANGER) + { + //bonus from intellect + value += 0.5f * _getTotalBotStat(BOT_STAT_MOD_INTELLECT); + } + if (_botclass == BOT_CLASS_NECROMANCER) + { + //bonus from intellect + value += _getTotalBotStat(BOT_STAT_MOD_INTELLECT); + } + if (_botclass == BOT_CLASS_SEA_WITCH) + { + //bonus from intellect + value += 2.f * _getTotalBotStat(BOT_STAT_MOD_INTELLECT); + } + if (_botclass == BOT_CLASS_CRYPT_LORD) + { + //bonus from strength + value += 2.f * _getTotalBotStat(BOT_STAT_MOD_STRENGTH); + } + + spellpower = uint32(value); + } + //else + //{ + // spellpower = 0; + //} + + //if init or levelup + if (force) + { + InitHeals(); + me->SetFullHealth(); + if (_botclass != BOT_CLASS_SPHYNX) + me->SetPower(POWER_MANA, me->GetMaxPower(POWER_MANA)); + + me->ResetPlayerDamageReq(); + } + + if (botPet) + botPet->GetBotPetAI()->SetShouldUpdateStats(); +} + +//Emotion-based action +void bot_ai::ReceiveEmote(Player* player, uint32 emote) +{ + switch (emote) + { + case TEXT_EMOTE_BONK: + _listAuras(player, me); + break; + case TEXT_EMOTE_SALUTE: + _listAuras(player, player); + break; + case TEXT_EMOTE_STAND: + if (master != player) + { + me->HandleEmoteCommand(EMOTE_ONESHOT_RUDE); + break; + } + SetBotCommandState(BOT_COMMAND_STAY); + //BotWhisper("Standing Still.", player); + break; + case TEXT_EMOTE_WAVE: + if (master != player) + { + me->HandleEmoteCommand(EMOTE_ONESHOT_RUDE); + break; + } + if (me->IsNonMeleeSpellCast(true)) + me->InterruptNonMeleeSpells(true); + SetBotCommandState(BOT_COMMAND_FOLLOW, true); + //BotWhisper("Following!", player); + break; + case TEXT_EMOTE_TICKLE: + { + if (master != player) + break; + + if ((me->HasUnitFlag(UNIT_FLAG_STUNNED) || me->HasUnitState(UNIT_STATE_STUNNED)) && + !me->HasAuraType(SPELL_AURA_MOD_STUN)) + { + me->ClearUnitState(UNIT_STATE_STUNNED); + me->RemoveUnitFlag(UNIT_FLAG_STUNNED); + } + if ((me->HasUnitFlag(UNIT_FLAG_CONFUSED) || me->HasUnitState(UNIT_STATE_CONFUSED)) && + !me->HasAuraType(SPELL_AURA_MOD_CONFUSE)) + { + me->ClearUnitState(UNIT_STATE_CONFUSED); + me->RemoveUnitFlag(UNIT_FLAG_CONFUSED); + } + if ((me->HasUnitFlag(UNIT_FLAG_FLEEING) || me->HasUnitState(UNIT_STATE_FLEEING)) && + !me->HasAuraType(SPELL_AURA_MOD_FEAR)) + { + me->ClearUnitState(UNIT_STATE_FLEEING); + me->RemoveUnitFlag(UNIT_FLAG_FLEEING); + } + me->BotStopMovement(); + + me->TextEmote(LocalizedNpcText(player, BOT_TEXT_BOT_TICKLED).c_str()); + break; + } + default: + break; + } +} + +//ISINBOTPARTY +//Returns group members (and their npcbots too) +//For now all your puppets are in your group automatically +bool bot_ai::IsInBotParty(Unit const* unit) const +{ + if (!unit) + return false; + if (unit == master || unit == me || unit == botPet) + return true; + + if (IAmFree()) + { + if (me->GetFaction() == 14 || unit->GetFaction() == 14) + return false; + + if (me->HasByteFlag(UNIT_FIELD_BYTES_2, 1, UNIT_BYTE2_FLAG_FFA_PVP) || + unit->HasByteFlag(UNIT_FIELD_BYTES_2, 1, UNIT_BYTE2_FLAG_FFA_PVP)) + return false; + + return + (unit->GetTypeId() == TYPEID_PLAYER || unit->ToCreature()->IsPet() || unit->ToCreature()->IsNPCBotOrPet()) && + (unit->GetFaction() == me->GetFaction() || (me->GetBotGroup() && me->GetBotGroup()->IsMember(unit->GetGUID())) || + (me->GetReactionTo(unit) >= REP_FRIENDLY && unit->GetReactionTo(me) >= REP_FRIENDLY)); + } + + //cheap check + if (Group const* gr = master->GetGroup()) + { + //group member case + if (gr->IsMember(unit->GetGUID())) + return true; + //pointed target case + for (uint8 i = 0; i != TARGET_ICONS_COUNT; ++i) + if ((BotMgr::GetHealTargetIconFlags() & GroupIconsFlags[i]) && + !((BotMgr::GetOffTankTargetIconFlags() | BotMgr::GetDPSTargetIconFlags() | BotMgr::GetRangedDPSTargetIconFlags()) & GroupIconsFlags[i])) + if (ObjectGuid guid = gr->GetTargetIcons()[i]) + if (guid == unit->GetGUID()) + return true; + } + + //Player-controlled creature case + if (Creature const* cre = unit->ToCreature()) + { + ObjectGuid ownerGuid = unit->GetOwnerGUID() ? unit->GetOwnerGUID() : unit->GetCreator() ? unit->GetCreator()->GetGUID() : ObjectGuid::Empty; + if (!ownerGuid && unit->IsVehicle()) + ownerGuid = unit->GetCharmerGUID(); + //controlled by master + if (ownerGuid == master->GetGUID()) + return true; + //npcbot/npcbot's pet case + if (cre->GetBotOwner() == master) + return true; + if (ownerGuid && master->GetBotMgr()->GetBot(ownerGuid)) + return true; + //controlled by group member + //pets, minions, guardians etc. + //bot pets too + if (ownerGuid) + if (Group const* gr = master->GetGroup()) + if (gr->IsMember(ownerGuid)) + return true; + } + + return false; +} + +bool bot_ai::IsInBotParty(ObjectGuid guid) const +{ + if (!guid) return false; + if (guid == master->GetGUID() || guid == me->GetGUID()) return true; + if (master->GetVehicle() && guid == master->GetCharmedGUID()) return true; + if (me->GetVehicle() && guid == me->GetCharmedGUID()) return true; + + if (IAmFree()) + { + if (me->HasByteFlag(UNIT_FIELD_BYTES_2, 1, UNIT_BYTE2_FLAG_FFA_PVP)) + return false; + + return !(me->GetVictim() && me->GetVictim()->GetGUID() == guid); + } + + //cheap check + if (Group const* gr = master->GetGroup()) + { + //group member case + if (gr->IsMember(guid)) + return true; + //pointed target case + for (uint8 i = 0; i != TARGET_ICONS_COUNT; ++i) + if ((BotMgr::GetHealTargetIconFlags() & GroupIconsFlags[i]) && + !((BotMgr::GetOffTankTargetIconFlags() | BotMgr::GetDPSTargetIconFlags()) & GroupIconsFlags[i])) + if (ObjectGuid gguid = gr->GetTargetIcons()[i]) + if (gguid == guid) + return true; + + for (GroupReference const* ref = gr->GetFirstMember(); ref != nullptr; ref = ref->next()) + { + Player const* p = ref->GetSource(); + if (p && (p->GetPetGUID() == guid || (p->GetVehicle() && p->GetCharmedGUID() == guid))) + return true; + if (p && p->HaveBot()) + { + if (Creature const* bot = p->GetBotMgr()->GetBot(guid)) + if (bot->GetGUID() == guid || (bot->GetBotsPet() && bot->GetBotsPet()->GetGUID() == guid) || + (bot->GetVehicle() && bot->GetCharmedGUID() == guid)) + return true; + } + } + } + else + { + if (master->GetPetGUID() == guid || (master->GetVehicle() && master->GetCharmedGUID() == guid)) + return true; + if (Creature const* bot = master->GetBotMgr()->GetBot(guid)) + if (bot->GetGUID() == guid || (bot->GetBotsPet() && bot->GetBotsPet()->GetGUID() == guid) || + (bot->GetVehicle() && bot->GetCharmedGUID() == guid)) + return true; + } + + return false; +} + +//REFRESHAURA +//Applies/removes/reapplies aura +void bot_ai::RefreshAura(uint32 spellId, int8 count, Unit* target) const +{ + if (count < 0 || count > 1) + { + TC_LOG_ERROR("entities.player", "bot_ai::RefreshAura(): count is out of bounds ({}) for bot {} (botclass: {}, entry: {})", + int32(count), me->GetName(), uint32(_botclass), me->GetEntry()); + return; + } + + if (!spellId) + { + TC_LOG_ERROR("entities.player", "bot_ai::RefreshAura(): spellId is 0 for bot {} (botclass: {}, entry: {})", + me->GetName(), uint32(_botclass), me->GetEntry()); + return; + } + + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); + if (!spellInfo) + { + TC_LOG_ERROR("entities.player", "bot_ai::RefreshAura(): Invalid spellInfo for spell {}! Bot - {} (botclass: {}, entry: {})", + spellId, me->GetName(), uint32(_botclass), me->GetEntry()); + return; + } + spellInfo = spellInfo->TryGetSpellInfoOverride(me); + + if (!target) + target = me; + + target->RemoveAurasDueToSpell(spellId); + + //for (int8 i = 0; i < count; ++i) + if (count) + target->AddAura(spellInfo, MAX_EFFECT_MASK, target); +} + +bool bot_ai::CanBotAttack(Unit const* target, int8 byspell, bool secondary) const +{ + if (!target) + return false; + if (HasBotCommandState(BOT_COMMAND_FULLSTOP | BOT_COMMAND_INACTION)) + return false; + if (target->HasUnitState(UNIT_STATE_EVADE | UNIT_STATE_IN_FLIGHT)) + return false; + if (target->IsCombatDisallowed()) + return false; + if (target->CanHaveThreatList() && GetEngageTimer() > lastdiff) + return false; + if (!BotMgr::IsPvPEnabled() && me->IsPvP() && target->IsControlledByPlayer()) + return false; + if (me->GetFaction() == 35 && IAmFree() && target->GetTypeId() == TYPEID_UNIT && target->GetVictim() != me) + return false; + if ((target->GetFaction() == 35 || target->GetFaction() == me->GetFaction()) && me->GetFaction() != 14) + return false; + if (!CanBotAttackOnVehicle()) + return false; + if (IsPointedNoDPSTarget(target)) + return false; + + if (IAmFree()) + { + switch (target->GetEntry()) + { + case 33229: case 33243: case 33272: // AT Training dummy targets + case 4952: case 17578: case 24792: case 30527: case 31143: case 31144: case 31146: // training dummy + case 32541: case 32542: case 32543: case 32545: case 32546: case 32547: case 32666: case 32667: // training dummy + case 7668: case 7669: case 7670: case 7671: // Blasted Lands servants + return false; + case 21416: case 21709: case 21710: case 21711: // Shadowmoon Valley Broken element corruptors + if (target->HasAuraTypeWithMiscvalue(SPELL_AURA_SCHOOL_IMMUNITY, 127)) + return false; + break; + default: + break; + } + } + + bool pulling = IsLastOrder(BOT_ORDER_PULL, 0, target->GetGUID()); + uint8 followdist = IAmFree() ? BotMgr::GetBotFollowDistDefault() : master->GetBotMgr()->GetBotFollowDist(); + float foldist = _getAttackDistance(float(followdist)); + if (!IAmFree() && IsRanged() && me->IsWithinLOSInMap(target, LINEOFSIGHT_ALL_CHECKS, VMAP::ModelIgnoreFlags::M2)) + _extendAttackRange(foldist); + + uint32 mainMask; + if (!byspell) + mainMask = SPELL_SCHOOL_MASK_NORMAL; + else + { + switch (_botclass) + { + case BOT_CLASS_PRIEST: mainMask = SPELL_SCHOOL_MASK_SHADOW; break; + case BOT_CLASS_SHAMAN: mainMask = IsMelee() ? SPELL_SCHOOL_MASK_NORMAL : (SPELL_SCHOOL_MASK_FIRE|SPELL_SCHOOL_MASK_NATURE);break; + case BOT_CLASS_MAGE: mainMask = Rand() > 50 ? SPELL_SCHOOL_MASK_FIRE : SPELL_SCHOOL_MASK_FROST; break; + case BOT_CLASS_WARLOCK: mainMask = Rand() > 50 ? SPELL_SCHOOL_MASK_SHADOW : SPELL_SCHOOL_MASK_FIRE; break; + case BOT_CLASS_DRUID: mainMask = Rand() > 50 ? SPELL_SCHOOL_MASK_ARCANE : SPELL_SCHOOL_MASK_NATURE; break; + case BOT_CLASS_SPHYNX: mainMask = SPELL_SCHOOL_MASK_NONE; break; + case BOT_CLASS_ARCHMAGE: mainMask = SPELL_SCHOOL_MASK_NONE; break; + case BOT_CLASS_DREADLORD: mainMask = SPELL_SCHOOL_MASK_NONE; break; + case BOT_CLASS_SPELLBREAKER:mainMask = SPELL_SCHOOL_MASK_NONE; break; + case BOT_CLASS_DARK_RANGER: mainMask = SPELL_SCHOOL_MASK_NONE; break; + case BOT_CLASS_NECROMANCER: mainMask = SPELL_SCHOOL_MASK_NONE; break; + case BOT_CLASS_SEA_WITCH: mainMask = SPELL_SCHOOL_MASK_NONE; break; + default: mainMask = SPELL_SCHOOL_MASK_NORMAL; break; + } + } + + return + ((master->IsInCombat() || target->IsInCombat() || IsWanderer() || (IAmFree() && me->GetFaction() == 14) || pulling) && + target->IsVisible() && target->isTargetableForAttack(false) && me->IsValidAttackTarget(target) && + (!master->IsAlive() || target->IsControlledByPlayer() || pulling || + (followdist > 0 && (master->GetDistance(target) <= foldist || HasBotCommandState(BOT_COMMAND_STAY)))) &&//if master is killed pursue to the end + !IsInBotParty(target) && (target->InSamePhase(me) || CanSeeEveryone()) && + (!HasBotCommandState(BOT_COMMAND_STAY) || + ((!IsRanged() && !secondary) ? me->IsWithinMeleeRange(target) : me->GetDistance(target) <= foldist)) &&//if stationery check own distance + (byspell == -1 || !target->IsTotem()) && + (byspell == -1 || !mainMask || !target->IsImmunedToDamage(SpellSchoolMask(mainMask)))); +} +bool bot_ai::CanBotAttackOnVehicle() const +{ + if (VehicleSeatEntry const* seat = me->GetVehicle() ? me->GetVehicle()->GetSeatForPassenger(me) : nullptr) + return seat->Flags & VEHICLE_SEAT_FLAG_CAN_ATTACK; + + return true; +} +//GETVEHICLETARGET +//Returns attack target or 'no target' +//All code above 'x = _getVehicleTarget() call must not dereference opponent since it can be invalid +Unit* bot_ai::_getVehicleTarget(BotVehicleStrats /*strat*/) const +{ + ASSERT(!IAmFree()); + Creature* veh = me->GetVehicleCreatureBase(); + Creature* masterVeh = master->GetVehicleCreatureBase(); + ASSERT(veh); + //ASSERT(masterVeh); + Unit* mmover = masterVeh ? masterVeh->ToUnit() : master->ToUnit(); + ObjectGuid curTarget = veh->GetTarget(); + + Unit* mytar = !curTarget.IsEmpty() ? ObjectAccessor::GetUnit(*veh, curTarget) : nullptr; + + if (mytar && veh->HasAuraType(SPELL_AURA_MOD_TAUNT)) + return mytar; + + Group const* gr = !IAmFree() ? master->GetGroup() : nullptr; + + if (gr && IsOffTank()) + { + Unit* tankTar = nullptr; + for (int8 i = TARGET_ICONS_COUNT - 1; i >= 0; --i) + { + if (BotMgr::GetOffTankTargetIconFlags() & GroupIconsFlags[i]) + { + if (ObjectGuid guid = gr->GetTargetIcons()[i]) + { + if (mytar && mytar->GetGUID() == guid && mytar->GetVictim() == veh) + return mytar; + + if (Unit* unit = ObjectAccessor::GetUnit(*veh, guid)) + { + if (unit->IsVisible() && unit->isTargetableForAttack(false) && veh->IsValidAttackTarget(unit) && + unit->IsInCombat() && (CanSeeEveryone() || (veh->CanSeeOrDetect(unit) && unit->InSamePhase(veh)))) + { + Unit* tempTar = tankTar ? tankTar : unit; + tankTar = unit; + Unit* tVic = unit->GetVictim(); + if (!tVic || (tVic != veh && tVic->GetVictim() == unit && IsTank(tVic) && IsInBotParty(tVic))) + { + tankTar = tempTar; + continue; + } + } + } + } + } + } + if (tankTar) + return tankTar; + } + if (gr) + { + for (int8 i = TARGET_ICONS_COUNT - 1; i >= 0; --i) + { + if (ObjectGuid guid = gr->GetTargetIcons()[i]) + { + if ((HasRole(BOT_ROLE_RANGED)|| HasVehicleRoleOverride(BOT_ROLE_RANGED)) && + (BotMgr::GetRangedDPSTargetIconFlags() & GroupIconsFlags[i])) + { + if (mytar && mytar->GetGUID() == guid) + return mytar; + + if (Unit* unit = ObjectAccessor::GetUnit(*me, guid)) + { + if (unit->IsVisible() && unit->isTargetableForAttack(false) && me->IsValidAttackTarget(unit) && + unit->IsInCombat() && (CanSeeEveryone() || (me->CanSeeOrDetect(unit) && unit->InSamePhase(me)))) + { + //TC_LOG_ERROR("entities.unit", "_getTarget: found dps icon target {}", unit->GetName()); + return unit; + } + } + } + if (BotMgr::GetDPSTargetIconFlags() & GroupIconsFlags[i]) + { + if (mytar && mytar->GetGUID() == guid) + return mytar; + + if (Unit* unit = ObjectAccessor::GetUnit(*veh, guid)) + { + if (unit->IsVisible() && unit->isTargetableForAttack(false) && veh->IsValidAttackTarget(unit) && + unit->IsInCombat() && (CanSeeEveryone() || (veh->CanSeeOrDetect(unit) && unit->InSamePhase(veh)))) + return unit; + } + } + } + } + } + + float followdist = float (master->GetBotMgr()->GetBotFollowDist() * 2); + if (float distOverride = GetVehicleAttackDistanceOverride()) + followdist = distOverride * 2.f; + if (mytar && mytar->GetTypeId() == TYPEID_UNIT && + mytar->ToCreature()->GetCreatureTemplate()->rank == CREATURE_ELITE_WORLDBOSS) + followdist *= 1.5f; + else if (mmover->isMoving() && veh->GetMapId() == 578) //oculus + followdist *= 0.5f; + + if (mytar && (veh->IsInCombat() || mytar->IsInCombat()) && + (!masterVeh || !mmover->IsAlive() || mmover->GetDistance(mytar) < followdist) && veh->IsValidAttackTarget(mytar)) + return mytar; + + if (mmover->IsAlive()) + { + if (followdist == 0 || (mytar && + (mmover->GetDistance(mytar) > followdist || (mmover->GetDistance(mytar) > followdist * 0.75f && !mytar->IsWithinLOSInMap(veh))))) + { + //if (mytar) + //{ + // TC_LOG_ERROR("scripts", "_getVehicleTarget {}'s veh is too far from master - lost target ({} > {})", + // me->GetName(), veh->GetDistance(mmover), followdist); + //} + return nullptr; + } + } + + //check targets around + float maxdist = InitAttackRange(followdist, IsRanged()); + Unit* t = nullptr; + NearbyHostileVehicleTargetCheck check(veh, maxdist, this); + Trinity::UnitSearcher searcher(veh, t, check); + Cell::VisitAllObjects(veh, searcher, maxdist); + //veh->VisitNearbyObject(maxdist, searcher); + + return t; +} +//GETTARGET +//Returns attack target or 'no target' and distant check target or 'no target' +//All code above 'x = _getTarget() call must not dereference opponent or disttarget since it can be invalid +std::tuple bot_ai::_getTargets(bool byspell, bool ranged, bool &reset) const +{ + //if (_evadeMode) //IAmFree() case only + // return { nullptr, nullptr }; + + if (!CanBotAttackOnVehicle()) + return { nullptr, nullptr }; + + Unit* mytar = me->GetVictim(); + + //check if no need to change target + //TC_LOG_ERROR("entities.player", "bot_ai::getTarget(): bot: {}", me->GetName()); + + if (mytar && me->HasAuraType(SPELL_AURA_MOD_TAUNT)) + return { mytar, mytar }; + + //Immediate targets + //orders + if (!IAmFree() && HasOrders() && HasRole(BOT_ROLE_DPS) && !me->IsInCombat() && me->getAttackers().empty()) + { + if (_orders.front()._type == BOT_ORDER_PULL) + { + ObjectGuid orderTargetGuid = ObjectGuid(_orders.front().params.pullParams.targetGuid); + if (Unit* orderTarget = mytar && mytar->GetGUID() == orderTargetGuid ? mytar : ObjectAccessor::GetUnit(*me, orderTargetGuid)) + { + if (CanBotAttack(orderTarget)) + return { orderTarget, nullptr }; + } + } + } + //maps + if (!IAmFree() && me->GetMap()->GetEntry() && !me->GetMap()->GetEntry()->IsWorldMap()) + { + static const std::array WMOAreaGroupLashlayer = { 29476u }; // Halls of Strife + static const std::array WMOAreaGroupMarrowgar = { 47833u }; // The Spire + static const std::array WMOAreaGroupSindragosa = { 48066u }; // Frost Queen's Lair + static const std::array WMOAreaGroupLichKing = { 50038u, 50040u }; // The Frozen Throne + + static auto isInWMOArea = [this](auto const& ids) { + for (auto wmoId : ids) { + if (wmoId == _lastWMOAreaId) + return true; + } + return false; + }; + + // Blackwing Lair + if (me->GetMapId() == 469 && GetBotClass() == BOT_CLASS_ROGUE && !HasRole(BOT_ROLE_DPS) && me->HasStealthAura() && isInWMOArea(WMOAreaGroupLashlayer)) // BWL - Bloodlord Lashlayer + return { nullptr, nullptr }; + + // Icecrown Citadel - Lord Marrowgar + if (me->GetMapId() == 631 && isInWMOArea(WMOAreaGroupMarrowgar) && me->IsInCombat() && HasRole(BOT_ROLE_DPS) && !IsTank()) + { + static const std::array BoneSpikeIds = { CREATURE_ICC_BONE_SPIKE1, CREATURE_ICC_BONE_SPIKE2, CREATURE_ICC_BONE_SPIKE3 }; + + auto boneSpikeCheck = [this, mydist = 50.f](Unit const* unit) mutable { + if (!unit->IsAlive()) + return false; + for (uint32 bsId : BoneSpikeIds) { + if (unit->GetEntry() == bsId) { + if (HasRole(BOT_ROLE_RANGED)) + return true; + float dist = me->GetDistance2d(unit); + if (dist < mydist) { + mydist = dist; + return true; + } + } + } + return false; + }; + + std::list cList; + Trinity::CreatureListSearcher searcher(me, cList, boneSpikeCheck); + Cell::VisitAllObjects(me, searcher, 50.f); + + if (Creature* spike = cList.empty() ? nullptr : cList.size() == 1 ? cList.front() : + Trinity::Containers::SelectRandomContainerElement(cList)) + { + // Bone Spike is always attackable - no additional checks needed + return { spike, nullptr }; + } + } + + // Icecrown Citadel - Sindragosa + if (me->GetMapId() == 631 && isInWMOArea(WMOAreaGroupSindragosa)/* && + (!mytar || (mytar->GetEntry() != CREATURE_ICC_ICE_TOMB1 && mytar->GetEntry() != CREATURE_ICC_ICE_TOMB2 && + mytar->GetEntry() != CREATURE_ICC_ICE_TOMB3 && mytar->GetEntry() != CREATURE_ICC_ICE_TOMB4))*/) + { + static const std::array IceTombIds = { CREATURE_ICC_ICE_TOMB1, CREATURE_ICC_ICE_TOMB2, CREATURE_ICC_ICE_TOMB3, CREATURE_ICC_ICE_TOMB4 }; + static const std::array SindragosaIds = { CREATURE_ICC_SINDRAGOSA1, CREATURE_ICC_SINDRAGOSA2, CREATURE_ICC_SINDRAGOSA3, CREATURE_ICC_SINDRAGOSA4 }; + + static auto SiItCheck = [=](Unit const* unit) { + if (unit->IsAlive()) + { + for (uint32 itId : IceTombIds) + if (unit->GetEntry() == itId) + return true; + for (uint32 siId : SindragosaIds) + if (unit->GetEntry() == siId) + return true; + } + return false; + }; + + std::list cList; + Trinity::CreatureListSearcher searcher(master, cList, SiItCheck); + Cell::VisitAllObjects(me, searcher, 200.f); + + if (!cList.empty()) + { + Creature* sindragosa = nullptr; + Creature* icetomb = nullptr; + for (Creature* siit : cList) + { + if (!icetomb) + { + for (uint32 itId : IceTombIds) + { + if (siit->GetEntry() == itId) + { + icetomb = siit; + break; + } + } + } + if (!sindragosa) + { + for (uint32 siId : SindragosaIds) + { + if (siit->GetEntry() == siId) + { + sindragosa = siit; + break; + } + } + } + else + break; + } + + if (icetomb) + { + bool air_phase = sindragosa && sindragosa->GetReactState() == REACT_PASSIVE; + bool above35 = GetHealthPCT(icetomb) > 35; + if (!air_phase || above35) + return { icetomb, nullptr }; + else if (mytar == icetomb || !master->GetVictim()) + { + if (botPet && botPet->GetVictim()) + botPet->AttackStop(); + return { nullptr, nullptr }; + } + } + } + } + + // Icecrown Citadel - The Lich King + if (me->GetMapId() == 631 && isInWMOArea(WMOAreaGroupLichKing) && me->IsInCombat() && HasRole(BOT_ROLE_DPS) && !IsTank()) + { + static const std::array IceSphereIds = { CREATURE_ICC_ICE_SPHERE1, CREATURE_ICC_ICE_SPHERE2, CREATURE_ICC_ICE_SPHERE3, CREATURE_ICC_ICE_SPHERE4 }; + static const std::array ValkyrShadowguardIds = { CREATURE_ICC_VALKYR_LK1, CREATURE_ICC_VALKYR_LK2, CREATURE_ICC_VALKYR_LK3, CREATURE_ICC_VALKYR_LK4 }; + + static auto valkyrCheck = [=](Unit const* unit) { + for (uint32 vsId : ValkyrShadowguardIds) { + if (unit->IsAlive() && unit->GetEntry() == vsId && !unit->HasUnitFlag(UNIT_FLAG_UNINTERACTIBLE)) + return true; + } + return false; + }; + + Creature* valkyr = nullptr; + Trinity::CreatureSearcher searcher(me, valkyr, valkyrCheck); + Cell::VisitAllObjects(me, searcher, 50.f); + + if (valkyr) + return { valkyr, nullptr }; + + Unit const* usearcher = master->IsAlive() ? master->ToUnit() : me->ToUnit(); + auto iceSphereCheck = [this, usearcher = usearcher, mydist = 30.f](Unit const* unit) mutable { + for (uint32 isId : IceSphereIds) { + if (unit->IsAlive() && unit->GetEntry() == isId) { + float dist = usearcher->GetDistance2d(unit); + if (dist < mydist && (HasRole(BOT_ROLE_RANGED) || dist < 7.f)) { + mydist = dist; + return true; + } + } + } + return false; + }; + + Creature* sphere = nullptr; + Trinity::CreatureLastSearcher searcher2(usearcher, sphere, iceSphereCheck); + Cell::VisitAllObjects(usearcher, searcher2, 30.f); + + if (sphere) + return { sphere, nullptr }; + } + } + + Group const* gr = !IAmFree() ? master->GetGroup() : nullptr; + + if (gr && IsOffTank()) + { + if (_primaryIconTank >= 0 && BotMgr::GetOffTankTargetIconFlags() & (1u << _primaryIconTank)) + { + if (ObjectGuid guid = gr->GetTargetIcons()[_primaryIconTank]) + { + if (mytar && mytar->GetGUID() == guid) + return { mytar, mytar }; + + if (Unit* unit = ObjectAccessor::GetUnit(*me, guid)) + { + if (unit->IsVisible() && unit->isTargetableForAttack(false) && me->IsValidAttackTarget(unit) && + (unit->IsInCombat() || me->IsInCombat() || master->IsInCombat()) && (CanSeeEveryone() || (me->CanSeeOrDetect(unit) && unit->InSamePhase(me)))) + { + return { unit, unit }; + } + } + } + } + + Unit* tankTar = nullptr; + for (int8 i = TARGET_ICONS_COUNT - 1; i >= 0; --i) + { + if (i == _primaryIconTank) + continue; + + if (BotMgr::GetOffTankTargetIconFlags() & GroupIconsFlags[i]) + { + if (ObjectGuid guid = gr->GetTargetIcons()[i]) + { + if (mytar && mytar->GetGUID() == guid && mytar->GetVictim() == me) + { + //TC_LOG_ERROR("entities.unit", "_getTarget: {} continues {}", me->GetName(), mytar->GetName()); + return { mytar, mytar }; + } + + if (Unit* unit = ObjectAccessor::GetUnit(*me, guid)) + { + if (unit->IsVisible() && unit->isTargetableForAttack(false) && me->IsValidAttackTarget(unit) && + unit->IsInCombat() && (CanSeeEveryone() || (me->CanSeeOrDetect(unit) && unit->InSamePhase(me)))) + { + //TC_LOG_ERROR("entities.unit", "_getTarget: {} found new offtanking icon target {}", me->GetName(), unit->GetName()); + Unit* tempTar = tankTar ? tankTar : unit; + tankTar = unit; + Unit* tVic = unit->GetVictim(); + if (!tVic || (tVic != me && tVic->GetVictim() == unit && IsTank(tVic) && IsInBotParty(tVic))) + { + //TC_LOG_ERROR("entities.unit", "_getTarget: {} skipped {} ({})", me->GetName(), unit->GetName(), tVic->GetName()); + tankTar = tempTar; + continue; + } + } + } + } + } + } + if (tankTar) + { + //TC_LOG_ERROR("entities.unit", "_getTarget: {} returning {}", me->GetName(), tankTar->GetName()); + return { tankTar, tankTar }; + } + } + if (gr && IsTank()) + { + if (_primaryIconTank >= 0 && BotMgr::GetTankTargetIconFlags() & (1u << _primaryIconTank)) + { + if (ObjectGuid guid = gr->GetTargetIcons()[_primaryIconTank]) + { + if (mytar && mytar->GetGUID() == guid) + return { mytar, mytar }; + + if (Unit* unit = ObjectAccessor::GetUnit(*me, guid)) + { + if (unit->IsVisible() && unit->isTargetableForAttack(false) && me->IsValidAttackTarget(unit) && + (unit->IsInCombat() || me->IsInCombat() || master->IsInCombat()) && (CanSeeEveryone() || (me->CanSeeOrDetect(unit) && unit->InSamePhase(me)))) + { + return { unit, unit }; + } + } + } + } + + Unit* tankTar = nullptr; + for (int8 i = TARGET_ICONS_COUNT - 1; i >= 0; --i) + { + if (i == _primaryIconTank) + continue; + + if (BotMgr::GetTankTargetIconFlags() & GroupIconsFlags[i]) + { + if (ObjectGuid guid = gr->GetTargetIcons()[i]) + { + if (mytar && mytar->GetGUID() == guid && mytar->GetVictim() == me) + { + //TC_LOG_ERROR("entities.unit", "_getTarget: {} continues {}", me->GetName(), mytar->GetName()); + return { mytar, mytar }; + } + + if (Unit* unit = ObjectAccessor::GetUnit(*me, guid)) + { + if (unit->IsVisible() && unit->isTargetableForAttack(false) && me->IsValidAttackTarget(unit) && + unit->IsInCombat() && (CanSeeEveryone() || (me->CanSeeOrDetect(unit) && unit->InSamePhase(me)))) + { + //TC_LOG_ERROR("entities.unit", "_getTarget: {} found new mtanking icon target {}", me->GetName(), unit->GetName()); + Unit* tempTar = tankTar ? tankTar : unit; + tankTar = unit; + Unit* tVic = unit->GetVictim(); + if (!tVic || (tVic != me && tVic->GetVictim() == unit && IsTank(tVic) && IsInBotParty(tVic))) + { + //TC_LOG_ERROR("entities.unit", "_getTarget: {} skipped {} ({})", me->GetName(), unit->GetName(), tVic->GetName()); + tankTar = tempTar; + continue; + } + } + } + } + } + } + if (tankTar) + { + //TC_LOG_ERROR("entities.unit", "_getTarget: {} returning {}", me->GetName(), tankTar->GetName()); + return { tankTar, tankTar }; + } + } + if (gr) + { + if (_primaryIconDamage >= 0) + { + uint32 iconMask = BotMgr::GetDPSTargetIconFlags(); + if (HasRole(BOT_ROLE_RANGED)) + iconMask |= BotMgr::GetRangedDPSTargetIconFlags(); + if (iconMask & (1u << _primaryIconDamage)) + { + if (ObjectGuid guid = gr->GetTargetIcons()[_primaryIconDamage]) + { + if (mytar && mytar->GetGUID() == guid) + return { mytar, mytar }; + + if (Unit* unit = ObjectAccessor::GetUnit(*me, guid)) + { + if (unit->IsVisible() && unit->isTargetableForAttack(false) && me->IsValidAttackTarget(unit) && + (unit->IsInCombat() || me->IsInCombat() || master->IsInCombat()) && (CanSeeEveryone() || (me->CanSeeOrDetect(unit) && unit->InSamePhase(me)))) + { + return { unit, unit }; + } + } + } + } + } + + for (int8 i = TARGET_ICONS_COUNT - 1; i >= 0; --i) + { + if (i == _primaryIconDamage) + continue; + + if (ObjectGuid guid = gr->GetTargetIcons()[i]) + { + if (HasRole(BOT_ROLE_RANGED) && (BotMgr::GetRangedDPSTargetIconFlags() & GroupIconsFlags[i])) + { + if (mytar && mytar->GetGUID() == guid) + return { mytar, mytar }; + + if (Unit* unit = ObjectAccessor::GetUnit(*me, guid)) + { + if (unit->IsVisible() && unit->isTargetableForAttack(false) && me->IsValidAttackTarget(unit) && + unit->IsInCombat() && (CanSeeEveryone() || (me->CanSeeOrDetect(unit) && unit->InSamePhase(me)))) + { + //TC_LOG_ERROR("entities.unit", "_getTarget: found rdps icon target {}", unit->GetName()); + return { unit, unit }; + } + } + } + if (BotMgr::GetDPSTargetIconFlags() & GroupIconsFlags[i]) + { + if (mytar && mytar->GetGUID() == guid) + return { mytar, mytar }; + + if (Unit* unit = ObjectAccessor::GetUnit(*me, guid)) + { + if (unit->IsVisible() && unit->isTargetableForAttack(false) && me->IsValidAttackTarget(unit) && + unit->IsInCombat() && (CanSeeEveryone() || (me->CanSeeOrDetect(unit) && unit->InSamePhase(me)))) + { + //TC_LOG_ERROR("entities.unit", "_getTarget: found dps icon target {}", unit->GetName()); + return { unit, unit }; + } + } + } + } + } + } + + Unit* u = master->GetVictim(); +//Disabled due to a bug: +//when spell cast is finished target is immideately put in combat which makes bots attack immediately +//caster must be put in combat at spell launch +//but target must be put in combat at spell hit +/* + if (!u && !IAmFree() && master->IsInCombat() && Rand() < 15) + { + for (uint8 i = CURRENT_FIRST_NON_MELEE_SPELL; i != CURRENT_MAX_SPELL; ++i) + { + if (Spell const* spell = master->GetCurrentSpell(CurrentSpellTypes(i))) + { + //if (spell->getState() == SPELL_STATE_FINISHED) + // continue; + + if (mytar && spell->m_targets.GetUnitTargetGUID() == mytar->GetGUID()) + { + u = mytar; + break; + } + + //direct damaging spells + if (!spell->GetSpellInfo()->IsPositive() && + (spell->GetSpellInfo()->HasEffect(SPELL_EFFECT_WEAPON_DAMAGE) || + spell->GetSpellInfo()->HasEffect(SPELL_EFFECT_SCHOOL_DAMAGE) || + spell->GetSpellInfo()->HasEffect(SPELL_EFFECT_WEAPON_DAMAGE_NOSCHOOL))) + { + Unit* victim = ObjectAccessor::GetUnit(*me, spell->m_targets.GetUnitTargetGUID()); + if (victim && victim->IsInCombat()) + { + u = victim; + break; + } + } + } + } + } +*/ + if (u && u == mytar && !IAmFree() && u->GetTypeId() == TYPEID_PLAYER && CanBotAttack(u, byspell)) + { + //TC_LOG_ERROR("entities.player", "bot {} continues attack common target {}", me->GetName(), u->GetName()); + return { u, u };//forced + } + //Follow if... + uint8 followdist = IAmFree() ? BotMgr::GetBotFollowDistDefault() / 2 : master->GetBotMgr()->GetBotFollowDist(); + if (IsWanderer() && me->GetMap()->GetEntry()->IsBattlegroundOrArena()) + followdist += 30; + float foldist = _getAttackDistance(float(followdist)); + if (!IAmFree() && IsRanged()) + { + _extendAttackRange(foldist); + //TC_LOG_ERROR("entities.player", "bot {} ranged foldist {} spelldist {}", me->GetName(), foldist, spelldist); + } + bool dropTarget = followdist == 0 && master->IsAlive(); + if (!dropTarget && (!u || IAmFree()) && master->IsAlive() && mytar && mytar == opponent) + { + dropTarget = IAmFree() ? + me->GetDistance(mytar) > (IsWanderer() ? float(followdist + 10) : foldist) : + HasBotCommandState(BOT_COMMAND_STAY) ? + (!IsRanged() ? !me->IsWithinMeleeRange(mytar) : me->GetDistance(mytar) > foldist) : + (master->GetDistance(mytar) > foldist || (master->GetDistance(mytar) > foldist * 0.75f && !mytar->IsWithinLOSInMap(me, LINEOFSIGHT_ALL_CHECKS, VMAP::ModelIgnoreFlags::M2))); + } + if (dropTarget) + { + //TC_LOG_ERROR("entities.player", "bot {} cannot attack target {}, too far away or not in LoS", me->GetName(), mytar ? mytar->GetName() : "unk"); + mytar = nullptr; + } + + if (u && !IAmFree() && (master->IsInCombat() || u->IsInCombat())/* && !InDuel(u)*/ && !IsInBotParty(u) && (BotMgr::IsPvPEnabled() || !u->IsControlledByPlayer()) && + (!HasBotCommandState(BOT_COMMAND_STAY) || (!IsRanged() ? me->IsWithinMeleeRange(u) : me->GetDistance(u) < foldist))) + { + //TC_LOG_ERROR("entities.player", "bot {} starts attack master's target {}", me->GetName(), u->GetName()); + return { u, u }; + } + + bool canAttack = mytar && CanBotAttack(mytar, byspell); + if (mytar && (!IAmFree() || me->GetDistance(mytar) < float(BOT_MAX_CHASE_RANGE)) && canAttack &&/* !InDuel(mytar) &&*/ + !(mytar->GetVictim() != nullptr && IsTank() && IsTank(mytar->GetVictim()))) + { + //TC_LOG_ERROR("entities.player", "bot {} continues attack its target {}", me->GetName(), mytar->GetName()); + if (me->GetDistance(mytar) > (ranged ? 20.f : 5.f) && !HasBotCommandState(BOT_COMMAND_MASK_UNCHASE)) + reset = true; + return { mytar, mytar }; + } + + //check group + if (!IAmFree()) + { + if (!gr) + { + BotMap const* map = master->GetBotMgr()->GetBotMap(); + for (BotMap::const_iterator itr = map->begin(); itr != map->end(); ++itr) + { + Creature const* bot = itr->second; + if (!bot || bot == me || !bot->InSamePhase(me)) continue; + if (IsTank() && IsTank(bot)) continue; + u = bot->GetVictim(); + if (u && (bot->IsInCombat() || u->IsInCombat()) && CanBotAttack(u, byspell)) + { + //TC_LOG_ERROR("entities.player", "bot {} hooked {}'s victim {}", me->GetName(), bot->GetName(), u->GetName()); + return { u, u }; + } + } + } + else + { + for (GroupReference const* ref = gr->GetFirstMember(); ref != nullptr; ref = ref->next()) + { + Player const* pl = ref->GetSource(); + if (!pl || !pl->IsInWorld() || pl->IsBeingTeleported()) continue; + if (me->GetMap() != pl->FindMap() || !pl->InSamePhase(me)) continue; + if (IsTank() && IsTank(pl)) continue; + u = pl->GetVictim(); + if (u && pl != master && (pl->IsInCombat() || u->IsInCombat()) && CanBotAttack(u, byspell)) + { + //TC_LOG_ERROR("entities.player", "bot {} hooked {}'s victim {}", me->GetName(), pl->GetName(), u->GetName()); + return { u, u }; + } + if (!pl->HaveBot()) continue; + BotMap const* map = pl->GetBotMgr()->GetBotMap(); + for (BotMap::const_iterator it = map->begin(); it != map->end(); ++it) + { + Creature const* bot = it->second; + if (!bot || bot == me || !bot->InSamePhase(me)) continue; + if (!bot->IsInWorld()) continue; + if (me->GetMap() != bot->FindMap()) continue; + if (IsTank() && IsTank(bot)) continue; + u = bot->GetVictim(); + if (u && (bot->IsInCombat() || u->IsInCombat()) && CanBotAttack(u, byspell)) + { + //TC_LOG_ERROR("entities.player", "bot {} hooked {}'s victim {}", me->GetName(), bot->GetName(), u->GetName()); + return { u, u }; + } + } + } + } + } + else if (!canAttack) + { + //check attackers + u = nullptr; + for (Unit* att : me->getAttackers()) + if (_canSwitchToTarget(u, att, byspell)) + u = att; + if (!u && botPet) + for (Unit* att : botPet->getAttackers()) + if (_canSwitchToTarget(u, att, byspell)) + u = att; + if (u) + return { u, u }; + } + + if (IAmFree() && IsWanderer() && !me->IsInCombat() && me->getAttackers().empty() && (evadeDelayTimer > 7500 || Feasting() || me->GetHealthPct() < 85.f)) + return { nullptr, nullptr }; + + //check targets around + float maxdist = InitAttackRange(float(followdist + 10), ranged); + std::array, 2u> ts{}; + std::list unitList; + NearestHostileUnitCheck check(me, maxdist, byspell, this); + Trinity::UnitListSearcher searcher(master->ToUnit(), unitList, check); + Cell::VisitAllObjects(HasBotCommandState(BOT_COMMAND_STAY) ? me->ToUnit() : master->ToUnit(), searcher, maxdist); + + if (IAmFree()) + { + decltype(unitList) closeList; + if (IsWanderer()) + { + //Try to prioritize flag carrier + if (me->GetMap()->IsBattlegroundOrArena()) + { + for (decltype(unitList)::iterator it = unitList.begin(); it != unitList.end(); ++it) + { + if (IsFlagCarrier(*it) && CanBotAttack(*it, byspell)) + { + closeList.push_back(*it); + break; + } + } + } + + unitList.remove_if([me = me](Unit const* unit) -> bool { + if (!unit->IsInCombatWith(me) && !(unit->IsNPCBot() && unit->ToCreature()->IsWandererBot())) + { + if (unit->IsPlayer()) + { + if (me->GetLevel() + 12 < unit->GetLevel()) + return true; + if (unit->GetLevel() + 9 < me->GetLevel()) + return true; + } + else + { + if (me->GetLevel() + (unit->ToCreature()->isElite() ? 3 : 6) < unit->GetLevel()) + return true; + if (unit->GetLevel() + (unit->ToCreature()->isElite() ? 8 : 4) < me->GetLevel()) + return true; + if (unit->IsCritter()) + return true; + } + } + return false; + }); + } + + for (decltype(unitList)::iterator it = unitList.begin(); it != unitList.end();) + { + if (!CanBotAttack(*it, byspell)) + it = unitList.erase(it); + else if (me->GetDistance(*it) < 15.f) + { + closeList.push_back(*it); + it = unitList.erase(it); + } + else + ++it; + } + + if (!closeList.empty()) + { + ts[0].first = closeList.size() == 1 ? closeList.front() : Trinity::Containers::SelectRandomContainerElement(closeList); + ts[0].second = me->GetDistance(ts[0].first); + } + else if (!unitList.empty()) + { + ts[0].first = unitList.size() == 1 ? unitList.front() : Trinity::Containers::SelectRandomContainerElement(unitList); + ts[0].second = me->GetDistance(ts[0].first); + } + } + else + { + bool checkSecondary = !IsRanged() && HasBotCommandState(BOT_COMMAND_STAY); + for (Unit* un : unitList) + { + uint32 res = !CanBotAttack(un, byspell) ? (checkSecondary && CanBotAttack(un, byspell, checkSecondary)) ? 2 : 0 : 1; + switch (res) + { + case 1: case 2: + if (!ts[res - 1].first || me->GetDistance(un) < ts[res - 1].second) + ts[res - 1] = { un, me->GetDistance(un) }; + break; + default: + break; + } + } + } + Unit* t1 = ts[0].first; + Unit* t2 = ts[1].first; + + Unit const* curtar = opponent ? opponent : disttarget ? disttarget : nullptr; + if (t1 && curtar && t1 != curtar) + reset = true; + + //Allow free bots to ignore temp invulnerabilities if no other target is present + if (IAmFree() && t1 == nullptr) + t1 = mytar; + + //if (t) + // TC_LOG_ERROR("entities.player", "bot {} has found new target {}", me->GetName(), t->GetName()); + + return { t1, t2 }; +} +//'CanAttack' function +//Only called in class ai UpdateAI function +//Side effects: opponent, disttarget +bool bot_ai::CheckAttackTarget() +{ + if (IsDuringTeleport()/* || _evadeMode*/) + { + //me->AttackStop(); //already in CombatStop() + me->CombatStop(true); + return false; + } + + if (IAmFree() && Feasting()) + return false; + + bool ranged = HasRole(BOT_ROLE_RANGED); + bool byspell = false; + bool reset = false; + + switch (_botclass) + { + case BOT_CLASS_DRUID: + switch (GetBotStance()) + { + case DRUID_CAT_FORM: + case DRUID_BEAR_FORM: + break; + case DRUID_TREE_FORM: + case DRUID_TRAVEL_FORM: + case DRUID_AQUATIC_FORM: + //case DRUID_FLIGHT_FORM: + ranged = true; + break; + case DRUID_MOONKIN_FORM: + byspell = true; + break; + case BOT_STANCE_NONE: + byspell = ranged && HasRole(BOT_ROLE_DPS); + break; + default: + TC_LOG_ERROR("entities.player", "bot_ai::CheckAttackTarget(): druid has NYI bot stance {}", uint32(GetBotStance())); + break; + } + break; + case BOT_CLASS_PRIEST: + case BOT_CLASS_MAGE: + case BOT_CLASS_WARLOCK: + case BOT_CLASS_SHAMAN: + byspell = ranged && HasRole(BOT_ROLE_DPS); + break; + case BOT_CLASS_SPHYNX: + case BOT_CLASS_ARCHMAGE: + case BOT_CLASS_NECROMANCER: + byspell = HasRole(BOT_ROLE_DPS); + break; + case BOT_CLASS_HUNTER: + case BOT_CLASS_DEATH_KNIGHT: + case BOT_CLASS_PALADIN: + case BOT_CLASS_WARRIOR: + case BOT_CLASS_ROGUE: + case BOT_CLASS_BM: + case BOT_CLASS_DREADLORD: + case BOT_CLASS_SPELLBREAKER: + case BOT_CLASS_DARK_RANGER: + case BOT_CLASS_SEA_WITCH: + case BOT_CLASS_CRYPT_LORD: + break; + default: + TC_LOG_ERROR("entities.player", "bot_ai: CheckAttackTarget() - unknown bot class {}", _botclass); + return false; + } + + std::tie(opponent, disttarget) = _getTargets(byspell, ranged, reset); + + if (!opponent && !disttarget) + { + //TC_LOG_ERROR("entities.player", "bot_ai: CheckAttackTarget() - bot {} lost target", me->GetName()); + if (me->GetVictim() || me->IsInCombat()/* || !me->GetThreatManager().isThreatListEmpty()*/) + { + //TC_LOG_ERROR("entities.player", "bot_ai: CheckAttackTarget() - bot {} Evades", me->GetName()); + if (me->GetVictim()) + me->AttackStop(); + else if (me->IsInCombat()) + Evade(); + } + + return false; + } + + Unit* mytar = opponent ? opponent : disttarget; + //boss engage phase // CanHaveThreatList checks for typeid == UNIT + if (GetEngageTimer() > lastdiff) + return false; + else if (!IsTank() && mytar != me->GetVictim() && mytar->GetVictim() && mytar->CanHaveThreatList() && + mytar->ToCreature()->GetCreatureTemplate()->rank == CREATURE_ELITE_WORLDBOSS && me->GetMap()->IsRaid()) + { + uint32 threat = uint32(mytar->ToCreature()->GetThreatManager().GetThreat(mytar->GetVictim())); + if (threat < std::min(50000, mytar->GetVictim()->GetMaxHealth() / 2)) + return false; + } + + if (reset) + SetBotCommandState(BOT_COMMAND_COMBATRESET);//reset AttackStart() + + if (mytar != me->GetVictim()) + me->Attack(mytar, !ranged); + + return true; +} +//IMMEDIATE TARGETS +bool bot_ai::ProcessImmediateNonAttackTarget() +{ + if ((me->GetMap()->GetEntry() && me->GetMap()->GetEntry()->IsWorldMap()) || IAmFree() || IsCasting()) + return false; + + static constexpr std::array WMOAreaGroupLashlayer = { 29476 }; // Halls of Strife + static constexpr std::array WMOAreaGroupMuru = { 41736, 42759 }; // Shrine of the Eclipse + static constexpr std::array WMOAreaGroupNajentus = { 41129, 41130 }; // Karabor Sewers + static constexpr std::array WMOAreaGroupVashj = { 37594 }; // Serpentshrine Cavern + static constexpr std::array WMOAreaGroupSvalna = { 48061, 48335 }; // The Frostwing Halls + + static auto isInWMOArea = [](auto lastWMO, auto const& ids) { + for (auto wmoId : ids) { + if (wmoId == lastWMO) + return true; + } + return false; + }; + + if (me->GetMapId() == 469 && GetBotClass() == BOT_CLASS_ROGUE && isInWMOArea(_lastWMOAreaId, WMOAreaGroupLashlayer)) // BWL - Bloodlord Lashlayer + { + static const uint32 SPELL_DISARM_TRAP_1 = 1842u; + + if (!IsCasting() && IsSpellReady(SPELL_DISARM_TRAP_1, lastdiff, false) && (me->HasAuraType(SPELL_AURA_MOD_STEALTH) || IsSpellReady(1784, lastdiff, false)) && Rand() < 20) // Stealth + { + SpellInfo const* disarmTrapSpellInfo = sSpellMgr->AssertSpellInfo(SPELL_DISARM_TRAP_1); + float max_range = disarmTrapSpellInfo->GetMaxRange(); + ApplyBotSpellRangeMods(disarmTrapSpellInfo, max_range); + + std::list goList; + Trinity::AllGameObjectsWithEntryInRange check(me, 179784, max_range); // Suppression Device + Trinity::GameObjectListSearcher searcher(me, goList, check); + Cell::VisitAllObjects(me, searcher, max_range); + + goList.remove_if([](GameObject const* gobject) { return gobject->HasFlag(GO_FLAG_NOT_SELECTABLE); }); + + if (GameObject* device = goList.empty() ? nullptr : goList.size() == 1u ? goList.front() : Trinity::Containers::SelectRandomContainerElement(goList)) + { + if (me->HasAuraType(SPELL_AURA_MOD_STEALTH) || doCast(me, GetSpell(1784))) + me->CastSpell(device, SPELL_DISARM_TRAP_1); + return true; + } + } + } + + if (me->GetMapId() == 580 && isInWMOArea(_lastWMOAreaId, WMOAreaGroupMuru)) // Sunwell - M'uru + { + static const uint32 SPELL_PURGE_1 = 370u; + static const uint32 SPELL_DISPEL_MAGIC_1 = 527u; + uint32 dspell = 0; + if (_botclass == BOT_CLASS_SHAMAN) + dspell = SPELL_PURGE_1; + else if (_botclass == BOT_CLASS_PRIEST) + dspell = SPELL_DISPEL_MAGIC_1; + + if (dspell && IsSpellReady(dspell, lastdiff)) + { + std::list cList; + Trinity::AllCreaturesOfEntryInRange check(me, 25744, 30.f); // Dark Fiend + Trinity::CreatureListSearcher searcher(me, cList, check); + Cell::VisitAllObjects(me, searcher, 30.f); + + //Dark Fiends do not die instantly, remove purged ones + cList.remove_if(Trinity::UnitAuraCheck(false, 45934)); // "Dark Fiend" + + if (Unit* fiend = cList.empty() ? nullptr : cList.size() == 1u ? cList.front() : + Trinity::Containers::SelectRandomContainerElement(cList)) + { + if (CheckBotCast(fiend, GetSpell(dspell)) == SPELL_CAST_OK) + if (doCast(fiend, GetSpell(dspell))) + return true; + } + } + } + if (me->GetMapId() == 564 && isInWMOArea(_lastWMOAreaId, WMOAreaGroupNajentus) && Rand() < 10) // Black Temple - High Warlord Naj'entus + { + if (Group const* gr = master->GetGroup()) + { + if (Rand() < 4) + { + InstanceScript* iscript = me->GetMap()->ToInstanceMap()->GetInstanceScript(); + Unit* najentus = iscript ? iscript->GetCreature(0) : nullptr; // boss_warlord_najentus.cpp::DATA_HIGH_WARLORD_NAJENTUS + + if (najentus && najentus->HasAuraTypeWithMiscvalue(SPELL_AURA_SCHOOL_IMMUNITY, 127)) // Tidal Shield + { + //Try to grab spines from corpses of dead players + std::vector spiners; + for (GroupReference const* itr = gr->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* pl = itr->GetSource(); + if (pl && pl->IsInWorld() && me->GetMap() == pl->FindMap() && !pl->IsAlive() && + me->GetDistance(pl) < 25.f && pl->HasItemCount(32408)) // Naj'entus Spine + spiners.push_back(pl); + } + + if (Player* pl = spiners.empty() ? nullptr : spiners.size() == 1u ? spiners.front() : + Trinity::Containers::SelectRandomContainerElement(spiners)) + { + BotWhisper("Taking 1 Naj'entus Spine from you"); + me->CastSpell(najentus, 39948, true); // Hurl Spine + pl->DestroyItemCount(32408, 1, true); // Naj'entus Spine + } + } + } + + std::vector spines; + //Find and free impaled player (player gets the spine) + for (GroupReference const* itr = gr->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* pl = itr->GetSource(); + //We don't make bots run to player to "click" the spine, so range is rather big + if (pl && pl->IsInWorld() && me->GetMap() == pl->FindMap()) + { + auto is_impaled = [this](Unit const* unit) -> bool { + return unit->IsAlive() && unit->HasUnitState(UNIT_STATE_STUNNED) && + me->GetDistance(unit) < 25.f && unit->HasAura(39837); // "Impaling Spine" + }; + + if (is_impaled(pl)) + spines.push_back(pl->ToUnit()); + if (pl->HaveBot()) + { + BotMap const* bmap = pl->GetBotMgr()->GetBotMap(); + for (BotMap::const_iterator ci = bmap->begin(); ci != bmap->end(); ++ci) + { + Creature* bot = ci->second; + if (bot && is_impaled(bot)) + spines.push_back(bot->ToUnit()); + } + } + } + } + + if (Unit* u = spines.empty() ? nullptr : spines.size() == 1u ? spines.front() : + Trinity::Containers::SelectRandomContainerElement(spines)) + { + if (GameObject const* spine = u->GetFirstGameObjectById(185584)) // Naj'entus Spine + { + Player* receiver = u->GetTypeId() == TYPEID_PLAYER ? u->ToPlayer() : master; + if (spine->AI() && spine->AI()->OnGossipHello(receiver)) + { + // Item is created by spell 39956 Create Naj'entus Spine - cannot target dead, force add item + if (!receiver->IsAlive()) + receiver->AddItem(32408, 1); // Naj'entus Spine + return true; + } + } + } + } + } + + if (me->GetMapId() == 548 && isInWMOArea(_lastWMOAreaId, WMOAreaGroupVashj) && Rand() < 15) // Serpentshrine Cavern - Lady Vashj + { + uint32 alive_players = 0; + std::vector taintPlayers; + for (MapReference const& ref : me->GetMap()->GetPlayers()) + { + if (Player* player = ref.GetSource()) + { + if (player->IsAlive()) + ++alive_players; + if (player->HasAuraType(SPELL_AURA_MOD_ROOT) && me->IsWithinDistInMap(player, 20.0f) && + player->HasItemCount(31088, 1)) // Tainted Core + taintPlayers.push_back(player); + } + } + + if (!taintPlayers.empty() && alive_players <= 1) + { +#if defined(TRINITY_COMPILER) + static const uint32 ShieldGeneratorTriggerNPC = 19870; +#elif defined(AC_COMPILER) + static const uint32 ShieldGeneratorTriggerNPC = WORLD_TRIGGER; +#endif + std::list cList; + Trinity::AllCreaturesOfEntryInRange check(me, ShieldGeneratorTriggerNPC, 100.f); // Invis KV Shield Generator + Trinity::CreatureListSearcher csearcher(me, cList, check); + Cell::VisitAllObjects(me, csearcher, 100.f); + + std::list gList; + auto is_shield_go = [](GameObject const* gobject) { + switch (gobject->GetEntry()) + { + case 185051: + case 185052: + case 185053: + case 185054: + return true; + default: + return false; + } + }; + Trinity::GameObjectListSearcher gsearcher(me, gList, is_shield_go); + Cell::VisitAllObjects(me, gsearcher, 100.f); + + static const auto get_shield_creature = [](GameObject const* gobject, std::list const& clist) { + Creature* c = nullptr; + float mindist = 10.0f; + for (Creature* creature : clist) + { + float dist = gobject->GetDistance(creature); + if (dist < mindist) + { + c = creature; + mindist = dist; + } + } + return c; + }; + + gList.remove_if([&cList](GameObject const* gobject) { + Creature const* c = get_shield_creature(gobject, cList); + return !c || c->GetCurrentSpell(CURRENT_CHANNELED_SPELL) == nullptr; + }); + cList.remove_if([](Creature const* creature) { + return creature->GetCurrentSpell(CURRENT_CHANNELED_SPELL) == nullptr; + }); + + ASSERT(cList.size() == gList.size()); + + if (!gList.empty()) + { + Player* player = taintPlayers.size() == 1u ? taintPlayers.front() : Trinity::Containers::SelectRandomContainerElement(taintPlayers); + BotWhisper("Taking Tainted Core from you"); + GameObject* go = gList.size() == 1u ? gList.front() : Trinity::Containers::SelectRandomContainerElement(gList); +#if defined(TRINITY_COMPILER) + Item* item = player->GetItemByEntry(31088); // Tainted Core + SpellCastTargets targets; + targets.SetGOTarget(go); + sScriptMgr->OnItemUse(player, item, targets); +#elif defined(AC_COMPILER) + Creature* cre = get_shield_creature(go, cList); + ASSERT(cre); + cre->DespawnOrUnsummon(1); + player->DestroyItemCount(31088, 1, true); // Tainted Core +#endif + return true; + } + } + } + + if (me->GetMapId() == 631 && isInWMOArea(_lastWMOAreaId, WMOAreaGroupSvalna) && Rand() < 10) // Icecrown Citadel - Sister Svalna + { + if (Group const* gr = master->GetGroup()) + { + if (Rand() < 4) + { + InstanceScript* iscript = me->GetMap()->ToInstanceMap()->GetInstanceScript(); + Creature* svalna = iscript ? iscript->GetCreature(9) : nullptr; // icecrown_citadel.h::DATA_SISTER_SVALNA + if (!svalna) + { + static const uint32 CREATURE_SISTER_SVALNA_N = 37126; + static const uint32 CREATURE_SISTER_SVALNA_H = 37126; //Acore - same id is used + Trinity::AllCreaturesOfEntryInRange check(master, me->GetMap()->IsHeroic() ? CREATURE_SISTER_SVALNA_H : CREATURE_SISTER_SVALNA_N, 60.f); + Trinity::CreatureSearcher searcher(master, svalna, check); + Cell::VisitAllObjects(master, searcher, 60.f); + } + + if (svalna && svalna->HasAuraTypeWithMiscvalue(SPELL_AURA_SCHOOL_IMMUNITY, 127)) // Aether Shield + { + //Try to grab spears from corpses of dead players + std::vector spearers; + for (GroupReference const* itr = gr->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* pl = itr->GetSource(); + if (pl && pl->IsInWorld() && me->GetMap() == pl->FindMap() && !pl->IsAlive() && + me->GetDistance(pl) < 25.f && pl->HasItemCount(50307)) // Infernal Spear + spearers.push_back(pl); + } + + if (Player* pl = spearers.empty() ? nullptr : spearers.size() == 1u ? spearers.front() : + Trinity::Containers::SelectRandomContainerElement(spearers)) + { + BotWhisper("Taking 1 Infernal Spear from you"); + me->CastSpell(svalna, 71466, true); // Hurl Spear + pl->DestroyItemCount(50307, 1, true); // Infernal Spear + } + } + } + + auto is_impaled = [this](Unit const* unit) -> bool { + return unit->IsAlive() && unit->HasUnitState(UNIT_STATE_STUNNED) && + me->GetDistance(unit) < 25.f && unit->HasAura(71443); // "Impaling Spear" + }; + + std::vector spears; + //Find and free impaled player (player gets the spear) + for (GroupReference const* itr = gr->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* pl = itr->GetSource(); + //We don't make bots run to player to "click" the spine, so range is rather big + if (pl && pl->IsInWorld() && me->GetMap() == pl->FindMap()) + { + + if (is_impaled(pl)) + spears.push_back(pl->ToUnit()); + if (pl->HaveBot()) + { + BotMap const* bmap = pl->GetBotMgr()->GetBotMap(); + for (BotMap::const_iterator ci = bmap->begin(); ci != bmap->end(); ++ci) + { + Creature* bot = ci->second; + if (bot && is_impaled(bot)) + spears.push_back(bot->ToUnit()); + } + } + } + } + + if (Unit* u = spears.empty() ? nullptr : spears.size() == 1u ? spears.front() : + Trinity::Containers::SelectRandomContainerElement(spears)) + { + Creature* spear = nullptr; + Trinity::NearestCreatureEntryWithLiveStateInObjectRangeCheck check(*u, 38248, true, 5.f); + Trinity::CreatureSearcher searcher(u, spear, check); + Cell::VisitAllObjects(u, searcher, 5.f); + + if (spear) + { + Player* receiver = u->GetTypeId() == TYPEID_PLAYER ? u->ToPlayer() : master; + me->CastSpell(spear, 71462, true); // "Remove Spear" + receiver->AddItem(50307, 1); // Infernal Spear + return true; + } + } + } + } + + return false; +} +//POSITION +AoeSpotsVec const& bot_ai::GetAoeSpots() const +{ + return IAmFree() ? _aoeSpots : master->GetBotMgr()->GetAoeSpots(); +} + +void bot_ai::CalculateAoeSpots(Unit const* unit, AoeSpotsVec& spots) +{ + std::list doList; + NearbyHostileAoEDynobjectCheck check(unit, 60.f); + Trinity::WorldObjectListSearcher searcher(unit, doList, check, GRID_MAP_TYPE_MASK_DYNAMICOBJECT); + //unit->VisitNearbyObject(60.f, searcher); + Cell::VisitAllObjects(unit, searcher, 60.f); + + //if (!doList.empty()) + // TC_LOG_ERROR("scripts", "CalculateAoeSpots {} aoes around {}", uint32(doList.size()), unit->GetName()); + + //filter and add to list + DynamicObject const* dObj; + SpellInfo const* spellInfo; + for (std::list::const_iterator ci = doList.begin(); ci != doList.end(); ++ci) + { + dObj = (*ci)->ToDynObject(); + ASSERT_NODEBUGINFO(dObj); + ASSERT_NODEBUGINFO(dObj->GetSpellId()); + spellInfo = sSpellMgr->GetSpellInfo(dObj->GetSpellId()); + if (IsPeriodicDynObjAOEDamage(spellInfo)) + { + //TC_LOG_ERROR("scripts", "CalculateAoeSpots found {}'s aoe {} ({}) radius {} size {}", + // dObj->GetCaster()->GetName(), spellInfo->SpellName[0], spellInfo->Id, dObj->GetRadius(), dObj->GetObjectSize()); + + float radius = dObj->GetRadius() + DEFAULT_PLAYER_BOUNDING_RADIUS; + radius += (unit->GetVehicle() ? unit->GetVehicleBase()->GetCombatReach() : DEFAULT_PLAYER_COMBAT_REACH) * 1.2f; + spots.push_back(AoeSpotsVec::value_type(*dObj, radius)); + } + } + + if (unit->IsNPCBot() && unit->ToCreature()->IsFreeBot()) + return; + + //Additional: aoe coming from spawned npcs + + //Molten Core + if (unit->GetMapId() == 409) + { + std::list gListMC; + Trinity::AllGameObjectsWithEntryInRange checkMC(unit, GAMEOBJECT_HOT_COAL, 60.f); + Trinity::GameObjectListSearcher searcherMC(unit, gListMC, checkMC); + Cell::VisitAllObjects(unit, searcherMC, 60.f); + + float radius = 15.0f + DEFAULT_PLAYER_COMBAT_REACH; + for (std::list::const_iterator ci = gListMC.cbegin(); ci != gListMC.cend(); ++ci) + spots.push_back(AoeSpotsVec::value_type(*(*ci), radius)); + } + //Aucheai Crypts + else if (unit->GetMapId() == 558) + { + Creature* creature = nullptr; + static const auto focus_fire_check = [](Creature const* c) { + return (c->GetEntry() == CREATURE_FOCUS_FIRE_N || c->GetEntry() == CREATURE_FOCUS_FIRE_H); + }; + Trinity::CreatureSearcher searcher2(unit, creature, focus_fire_check); + Cell::VisitAllObjects(unit, searcher2, 50.f); + + if (creature) + { + spellInfo = sSpellMgr->GetSpellInfo(32302); //Fiery Blast + float radius = spellInfo->_effects[0].CalcRadius() + DEFAULT_PLAYER_COMBAT_REACH * 2.0f; + spots.push_back(AoeSpotsVec::value_type(*creature, radius)); + } + } + //The Eye of Eternity + else if (unit->GetMapId() == 616 && unit->GetVehicle()) + { + std::list cList; + Trinity::AllCreaturesOfEntryInRange check2(unit->GetVehicleBase(), CREATURE_EOE_STATIC_FIELD, 60.f); + Trinity::CreatureListSearcher searcher2(unit->GetVehicleBase(), cList, check2); + //unit->GetVehicleBase()->VisitNearbyObject(60.f, searcher2); + Cell::VisitAllObjects(unit->GetVehicleBase(), searcher2, 60.f); + + spellInfo = sSpellMgr->GetSpellInfo(57429); //Static Field damage + float radius = spellInfo->_effects[0].CalcRadius() + unit->GetVehicleBase()->GetCombatReach() * 1.2f; + for (std::list::const_iterator ci = cList.begin(); ci != cList.end(); ++ci) + spots.push_back(AoeSpotsVec::value_type(*(*ci), radius)); + } + //Zul'Aman + else if (unit->GetMapId() == 568) + { + std::list cList; + Trinity::AllCreaturesOfEntryInRange check2(unit, CREATURE_ZA_FIRE_BOMB, 40.f); + Trinity::CreatureListSearcher searcher2(unit, cList, check2); + //unit->VisitNearbyObject(40.f, searcher2); + Cell::VisitAllObjects(unit, searcher2, 40.f); + + spellInfo = sSpellMgr->GetSpellInfo(42630); //Fire Bomb + float radius = spellInfo->_effects[0].CalcRadius() + DEFAULT_PLAYER_COMBAT_REACH * 1.2f; + for (std::list::const_iterator ci = cList.begin(); ci != cList.end(); ++ci) + spots.push_back(AoeSpotsVec::value_type(*(*ci), radius)); + } + //Uthgarde Keep + else if (unit->GetMapId() == 574) + { + Creature* creature = nullptr; + static const auto shadow_axe_check = [](Creature const* c) { + return (c->GetEntry() == CREATURE_UK_SHADOW_AXE_N || c->GetEntry() == CREATURE_UK_SHADOW_AXE_H); + }; + Trinity::CreatureSearcher searcher2(unit, creature, shadow_axe_check); + Cell::VisitAllObjects(unit, searcher2, 40.f); + + if (creature) + { + spellInfo = sSpellMgr->GetSpellInfo(42751); //Shadow Axe + float radius = spellInfo->_effects[0].CalcRadius() + DEFAULT_PLAYER_COMBAT_REACH * 2.0f; + spots.push_back(AoeSpotsVec::value_type(*creature, radius)); + } + } + //Icecrown Citadel + else if (unit->GetMapId() == 631) + { + std::list cList; + Trinity::AllCreaturesOfEntryInRange check2(unit, CREATURE_ICC_OOZE_PUDDLE, 50.f); + Trinity::CreatureListSearcher searcher2(unit, cList, check2); + //unit->VisitNearbyObject(50.f, searcher2); + Cell::VisitAllObjects(unit, searcher2, 50.f); + + for (std::list::const_iterator ci = cList.begin(); ci != cList.end(); ++ci) + { + float radius = (*ci)->GetObjectScale() * 2.5f + DEFAULT_PLAYER_COMBAT_REACH * 3.f; //grows + spots.push_back(AoeSpotsVec::value_type(*(*ci), radius)); + } + } + + //STUB + //if (!unit->IsPlayer() || !unit->ToPlayer()->HaveBot()) + // return; + + //switch (unit->GetMapId()) + //{ + // case 409: //Molten Core + // break; + // default: + // return; + //} + + //BotMap const* bmap = unit->ToPlayer()->GetBotMgr()->GetBotMap(); + //for (BotMap::const_iterator itr = bmap->begin(); itr != bmap->end(); ++itr) + //{ + // if (itr->second && itr->second->IsInWorld() && itr->second->IsAlive()) + // { + // // Living Bomb + // if (unit->GetMapId() == 409 && !!itr->second->GetAuraEffect(SPELL_AURA_PERIODIC_TRIGGER_SPELL, SPELLFAMILY_GENERIC, 1646, 0)) + // spots.push_back(AoeSpotsVec::value_type(itr->second->GetPosition(), 18.0)); + // } + //} +} + +void bot_ai::CalculateAoeSafeSpots(Unit* target, float maxdist, AoeSafeSpotsVec& safespots) const +{ + if (!GetAoeSpots().empty()) + { + //find 200 safe spots + Position ppos; + float distdelta = maxdist / 200.f; + float angledelta = float(M_PI) / 12.5f; + float aoedist = 0.f; + float aoeangle; + for (uint8 i = 0; i < 8; ++i) + { + aoeangle = 0.0f; + for (uint8 j = 0; j < 25; ++j) + { + aoedist += distdelta; + aoeangle += angledelta; + + ppos = target->GetFirstCollisionPosition(aoedist, Position::NormalizeOrientation(aoeangle - target->GetOrientation())); + bool toofaraway = master->GetDistance(ppos) > maxdist; + + if (!toofaraway && !IsWithinAoERadius(ppos)) + safespots.push_back(ppos); + } + } + } +} + +bool bot_ai::IsPeriodicDynObjAOEDamage(SpellInfo const* spellInfo) +{ + if (!spellInfo->IsPositive()) + { + for (uint8 i = 0; i != MAX_SPELL_EFFECTS; ++i) + { + if (spellInfo->_effects[i].Effect == SPELL_EFFECT_PERSISTENT_AREA_AURA && + spellInfo->_effects[i].ApplyAuraName != 0) + { + switch (spellInfo->_effects[i].ApplyAuraName) + { + case SPELL_AURA_PERIODIC_DAMAGE: + case SPELL_AURA_PERIODIC_DAMAGE_PERCENT: + case SPELL_AURA_POWER_BURN: + case SPELL_AURA_PERIODIC_LEECH: + //Most of these are damaging spells + case SPELL_AURA_PERIODIC_TRIGGER_SPELL: + case SPELL_AURA_PERIODIC_TRIGGER_SPELL_WITH_VALUE: + //Scripted spells (mostly, some of these are wrong or not periodic damage) + case SPELL_AURA_PERIODIC_DUMMY: + //Channeled spells with SPELL_AURA_PERIODIC_TRIGGER_SPELL -> damage on TARGET_DEST_CHANNEL_TARGET (mostly) + case SPELL_AURA_DUMMY: + return true; + default: + break; + } + } + } + } + return false; +} +bool bot_ai::IsWithinAoERadius(Position const& pos) const +{ + AoeSpotsVec const& spots = GetAoeSpots(); + if (!spots.empty()) + { + Unit const* mover = me->GetVehicle() ? me->GetVehicleBase() : me; + float cr_diff = mover->GetCombatReach() - DEFAULT_PLAYER_COMBAT_REACH; + for (AoeSpotsVec::const_iterator ci = spots.begin(); ci != spots.end(); ++ci) + if (pos.GetExactDist(&ci->first) - cr_diff < ci->second) + return true; + } + + return false; +} +//Returns attack range based on given range +//If mounted: 20% +//If ranged: 125% +//If master is dead: max range +//If wanderer: 65% max range +float bot_ai::InitAttackRange(float origRange, bool ranged) const +{ + if (IsWanderer()) + { + origRange = sWorld->GetMaxVisibleDistanceOnContinents() * 0.65f; + if (IsFlagCarrier(me)) + origRange *= 0.67f; + } + else if (!master->IsAlive()) + origRange = sWorld->GetMaxVisibleDistanceOnContinents(); + else if (me->HasAuraType(SPELL_AURA_MOUNTED)) + origRange *= 0.2f; + else if (ranged) + origRange *= 1.25f; + + return origRange; +} +void bot_ai::_extendAttackRange(float& dist) const +{ + ASSERT(!IAmFree()); + + uint8 rangeMode = master->GetBotMgr()->GetBotAttackRangeMode(); + if (master->GetBotMgr()->GetBotFollowDist() > 0) + { + float spelldist; + if (rangeMode == BOT_ATTACK_RANGE_EXACT) + spelldist = master->GetBotMgr()->GetBotExactAttackRange(); + else + spelldist = GetSpellAttackRange(rangeMode == BOT_ATTACK_RANGE_LONG); + dist = std::max(dist, spelldist * 0.5f + 4.f); + } +} +bool bot_ai::_canSwitchToTarget(Unit const* from, Unit const* newTarget, int8 byspell) const +{ + if (newTarget) + { + if (IAmFree()) + { + if (newTarget != me->GetVictim() && + (!from || me->GetDistance(newTarget) < me->GetDistance(from) - 10.0f || newTarget->GetHealth() < from->GetHealth()) && + CanBotAttack(newTarget, byspell)) + return true; + } + } + + return false; +} +//Ranged attack position +void bot_ai::CalculateAttackPos(Unit* target, Position& pos, bool& force) const +{ + uint8 followdist = IAmFree() ? BotMgr::GetBotFollowDistDefault() : master->GetBotMgr()->GetBotFollowDist(); + uint8 rangeMode = IAmFree() ? uint8(BOT_ATTACK_RANGE_LONG) : master->GetBotMgr()->GetBotAttackRangeMode(); + uint8 exactRange = rangeMode != BOT_ATTACK_RANGE_EXACT || IAmFree() ? 255 : master->GetBotMgr()->GetBotExactAttackRange(); + uint8 angleMode = IAmFree() ? uint8(BOT_ATTACK_ANGLE_NORMAL) : master->GetBotMgr()->GetBotAttackAngleMode(); + float dist = (rangeMode == BOT_ATTACK_RANGE_EXACT) ? exactRange : GetSpellAttackRange(rangeMode == BOT_ATTACK_RANGE_LONG) - 5.f; + float angle = target->GetAbsoluteAngle(me); + if (_botclass == BOT_CLASS_SPHYNX && target->GetVictim() == me && me->GetExactDist(target) < 30.0f) + dist = me->GetExactDist(target); + if ((target->m_movementInfo.GetMovementFlags() & MOVEMENTFLAG_FORWARD) && target->HasInArc(float(M_PI)/1.5f, me)) + dist = std::min(dist + 4.f, 30.f); + + //if ranged try to acquire a position in the back (will be ignored if too far away from master) + if (angleMode == BOT_ATTACK_ANGLE_AVOID_FRONTAL_AOE) + { + static const float rangedAngleDelta = float(M_PI) * 0.62f; + if (HasRole(BOT_ROLE_RANGED) && !IAmFree() && !target->IsControlledByPlayer() && target->HasInArc(float(M_PI), me) && + (IsTank(master) || master->GetDistance(target) < 2.5f || !target->HasInArc(float(M_PI), master))) + angle += (target->GetRelativeAngle(master) > 0.f) ? rangedAngleDelta : -rangedAngleDelta; + } + + float clockwise = (me->GetEntry() % 2) ? 1.f : -1.f; + float angleDelta1 = ((IsTank(master) && !IsTank(me)) ? frand(float(M_PI)*0.40f, float(M_PI)*0.60f) : frand(0.0f, float(M_PI)*0.15f)) * clockwise; + float angleDelta2 = frand(0.0f, float(M_PI)*0.08f) * clockwise; + + Position ppos; + + if (me->GetVehicle()) + { + ASSERT(!IAmFree()); + + if (float vehdist = GetVehicleAttackDistanceOverride()) + dist = vehdist + 5.f; + + uint8 posSlot = master->GetBotMgr()->GetNpcBotSlot(me); + clockwise = (posSlot % 2) ? 1.f : -1.f; + + Unit* angunit = target->GetVictim() ? target->GetVictim() : master->GetVehicle() ? master->GetVehicleBase() : master; + if (angunit == me->GetVehicleBase() || angunit == me) + angle = target->GetAbsoluteAngle(me->GetVehicleBase()); + else + angle = target->GetAbsoluteAngle(angunit) + 0.5f * M_PI / master->GetNpcBotsCount() * posSlot * clockwise; + + for (uint8 i = 0; i < 4; ++i) + { + if (me->GetVehicleBase()->CanFly()) + { + //collision point bug, distance shinked to 0, so use GetNearPoint + float &tx = ppos.m_positionX, &ty = ppos.m_positionY, &tz = ppos.m_positionZ; + target->GetNearPoint(me->GetVehicleBase(), tx, ty, tz, dist, Position::NormalizeOrientation(angle)); + if (!target->IsWithinLOS(tx, ty, tz)) + dist *= i >= 3 ? 0.2f : i >= 2 ? 0.5f : 0.75f; + } + else + ppos = target->GetFirstCollisionPosition(dist, Position::NormalizeOrientation(angle - target->GetOrientation())); + //target->GetNearPoint(me->GetVehicleBase(), x, y, z, 0.f, dist, Position::NormalizeOrientation(angle)); + + bool toofar = master->GetDistance(ppos) > (followdist > 30.f ? 60.f : followdist < 10 ? 20.f : float(followdist*2)); + bool isinaoe = (i == 0 && me->GetVehicleBase()->GetDistance(ppos) < 4.f && IsWithinAoERadius(*me->GetVehicleBase())) || IsWithinAoERadius(ppos); + if (!toofar && !isinaoe) + break; + + if (toofar) + { + if (i >= 1) + angle += -(clockwise) * angleDelta1/* * i*/; + if (i >= 2) + dist = std::max(0.f, dist - 5.f); + } + if (isinaoe) + { + //TC_LOG_ERROR("scripts", "CalculateAttackPos {} veh skipped aoe pos", me->GetName()); + if (me->GetVehicleBase()->CanFly()) + ppos.m_positionZ += (master->GetPositionZ() < target->GetPositionZ()) ? -8.f : 8.f; + else + angle += angleDelta2 * 2.f; + } + } + + pos.Relocate(ppos); + //pos.m_positionX = x; + //pos.m_positionY = y; + //pos.m_positionZ = z; + + return; + } + + AoeSpotsVec const& aoespots = GetAoeSpots(); + + bool toofaraway; + + if (!aoespots.empty()) + { + ppos.Relocate(me); + toofaraway = master->GetDistance(ppos) > (followdist > 38 ? 38.f : followdist < 20 ? 20.f : float(followdist)); + bool outoflos = !target->IsWithinLOS(ppos.m_positionX, ppos.m_positionY, ppos.m_positionZ); + bool isinaoe = IsWithinAoERadius(ppos); + bool canattack = HasRole(BOT_ROLE_RANGED) || me->IsWithinMeleeRangeAt(ppos, target); + if (!toofaraway && !outoflos && !isinaoe && canattack) + { + //if (!aoespots.empty()) + // TC_LOG_ERROR("scripts", "CalculateAttackPos {} spot is still safe", me->GetName()); + + pos.Relocate(ppos); + return; + } + } + + AoeSafeSpotsVec safespots; + CalculateAoeSafeSpots(target, float(followdist), safespots); + + bool angle_reset_to_master = false; + uint8 collision_dist_max = IAmFree() ? 30 : 38; + for (uint8 i = 0; i < 5; ++i) + { + ppos = target->GetFirstCollisionPosition(dist, Position::NormalizeOrientation(angle - target->GetOrientation())); + toofaraway = master->GetDistance(ppos) > (followdist > collision_dist_max ? float(collision_dist_max) : followdist < 20 ? 20.f : float(followdist)); + if (!toofaraway) + break; + + if (!angle_reset_to_master) + { + angle_reset_to_master = true; + angle = target->GetAbsoluteAngle(master); + } + else + angle += angleDelta1; + + if (i >= 1 && i <= 3) + dist = std::max(0.f, dist - 5.f); + } + + if (!safespots.empty()) + { + //find closest safe spot + Position const* closestPos = nullptr; + Position const* closestAttackPos = nullptr; + float minposdist = 100.f; + float minattackposdist = 100.f; + for (AoeSafeSpotsVec::const_iterator ci = safespots.begin(); ci != safespots.end(); ++ci) + { + float curdist = me->GetExactDist2d(*ci); + if (curdist < minposdist) + { + closestPos = &(*ci); + minposdist = curdist; + } + if (curdist < minattackposdist && + (HasRole(BOT_ROLE_RANGED) ? (target->GetDistance(*ci) - me->GetCombatReach() < dist) : me->IsWithinMeleeRangeAt(*ci, target))) + { + closestAttackPos = &(*ci); + minattackposdist = curdist; + } + } + + //TC_LOG_ERROR("scripts", "CalculateAttackPos {} safe spots, chosen at dist {}", uint32(safespots.size()), mindist); + pos.Relocate(closestAttackPos ? closestAttackPos : closestPos ? closestPos : me); + force = true; + return; + } + else if (!aoespots.empty() && !IAmFree()) + { + pos.Relocate(master); + force = true; + return; + } + + // Ranged bots that are being targeted should move towards a tank bot or towards the player + if (!IAmFree() && !IsTank(me) && HasRole(BOT_ROLE_RANGED) && target->GetVictim() == me && !CCed(target)) + { + std::vector safetyTargets; + if (Group const* gr = master->GetGroup()) + { + for (GroupReference const* itr = gr->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player const* pl = itr->GetSource(); + if (!pl || !pl->IsInMap(me) || pl->GetDistance(me) > VISIBILITY_DISTANCE_NORMAL) + continue; + if (pl->IsAlive() && !pl->HasUnitState(UNIT_STATE_ISOLATED) && IsTank(pl)) + safetyTargets.push_back(pl); + if (!pl->HaveBot()) + continue; + BotMap const* map = pl->GetBotMgr()->GetBotMap(); + for (BotMap::const_iterator citr = map->begin(); citr != map->end(); ++citr) + { + Creature const* c = citr->second; + if (c && c->IsInWorld() && me->GetMap() == c->FindMap() && c->IsAlive() && !c->HasUnitState(UNIT_STATE_ISOLATED) && IsTank(c) && c->GetBotAI()->HasRole(BOT_ROLE_DPS)) + safetyTargets.push_back(c); + } + } + } + else + { + BotMap const* map = master->GetBotMgr()->GetBotMap(); + for (BotMap::const_iterator citr = map->begin(); citr != map->end(); ++citr) + { + Creature const* c = citr->second; + if (c && c->IsInWorld() && me->GetMap() == c->FindMap() && c->IsAlive() && !c->HasUnitState(UNIT_STATE_ISOLATED) && IsTank(c) && c->GetBotAI()->HasRole(BOT_ROLE_DPS)) + safetyTargets.push_back(c); + } + } + if (safetyTargets.empty() && master->IsAlive()) + safetyTargets.push_back(master); + + if (!safetyTargets.empty()) + { + static const float ThresholdDistance = 1.5f; + Unit const* moveTarget = safetyTargets.size() == 1u ? safetyTargets.front() : safetyTargets[me->GetEntry() % safetyTargets.size()]; + if (moveTarget->GetDistance(target) > ThresholdDistance && me->GetDistance(moveTarget) > ThresholdDistance * 2.0f) + { + float distanceMod = moveTarget->HasInArc(float(M_PI), target) ? 0.5f : -1.5f; + pos.Relocate(moveTarget->GetFirstCollisionPosition(ThresholdDistance * distanceMod, Position::NormalizeOrientation(moveTarget->GetAbsoluteAngle(target) - moveTarget->GetOrientation()))); + force = true; + return; + } + } + } + + pos.Relocate(ppos); + if (!me->IsWithinLOSInMap(target, LINEOFSIGHT_ALL_CHECKS, VMAP::ModelIgnoreFlags::M2)) + force = true; +} +// Forces bot to chase opponent (if ranged then distance depends on follow distance) +void bot_ai::GetInPosition(bool force, Unit* newtarget, Position* mypos) +{ + Unit* mover = me->GetVehicle() ? me->GetVehicleBase() : me; + if (HasBotCommandState(BOT_COMMAND_STAY)) + return; + if (!IAmFree() && !master->GetBotMgr()->GetBotAllowCombatPositioning()) + return; + if (CCed(mover, true) || (mover == me && JumpingOrFalling())) + return; + if (!newtarget) + newtarget = me->GetVictim(); + if (!newtarget) + return; + if ((!newtarget->IsInCombat() || (mover->isMoving()/* && Rand() > 50*/)) && !force && !(_atHome && _evadeMode)) + return; + if (IsCasting(mover)) + return; + if (IsShootingWand(mover) && newtarget->GetVictim() == mover) + return; + if (UpdateImpossibleChase(newtarget)) + return; + if (AdjustTankingPosition(newtarget)) + return; + + if (!IAmFree() && master->GetBotMgr()->GetBotAttackRangeMode() == BOT_ATTACK_RANGE_EXACT && + master->GetBotMgr()->GetBotExactAttackRange() == 0) + { + attackpos.m_positionX = newtarget->GetPositionX() - frand(0.5f, 1.5f) * std::cos(me->GetAbsoluteAngle(newtarget)); + attackpos.m_positionY = newtarget->GetPositionY() - frand(0.5f, 1.5f) * std::sin(me->GetAbsoluteAngle(newtarget)); + attackpos.m_positionZ = newtarget->GetPositionZ(); + if (me->GetExactDist2d(&attackpos) > 3.5f) + BotMovement(BOT_MOVE_POINT, &attackpos); + //me->GetMotionMaster()->MovePoint(newtarget->GetMapId(), attackpos); + return; + } + + uint8 followdist = IAmFree() ? BotMgr::GetBotFollowDistDefault() : master->GetBotMgr()->GetBotFollowDist(); + if (IsRanged() || (!IAmFree() && !GetAoeSpots().empty())) + { + //do not allow constant runaway from player + if (!force && newtarget->GetTypeId() == TYPEID_PLAYER && + me->GetDistance(newtarget) < 6 + urand(followdist/4, followdist/3)) + return; + + if (!mypos) + CalculateAttackPos(newtarget, attackpos, force); + else + { + attackpos.m_positionX = mypos->m_positionX; + attackpos.m_positionY = mypos->m_positionY; + attackpos.m_positionZ = mypos->m_positionZ; + } + //TC_LOG_ERROR("scripts", "GetInPosition {} to {} dist {}, to pos {}", me->GetName(), + // newtarget->GetName(), me->GetExactDist2d(newtarget), me->GetExactDist2d(&attackpos)); + if (mover->GetExactDist2d(&attackpos) > (force ? 0.1f : 4.f)) + { + BotMovement(BOT_MOVE_POINT, &attackpos); + //me->GetMotionMaster()->MovePoint(newtarget->GetMapId(), attackpos); + if (mover == me && !me->HasUnitState(UNIT_STATE_MELEE_ATTACKING) && CanBotAttackOnVehicle()) + me->SetInFront(newtarget); + else if (!mover->HasUnitState(UNIT_STATE_MELEE_ATTACKING)) + mover->SetInFront(newtarget); + } + } + else + { + if (!JumpingOrFalling() && ((!mover->HasUnitState(UNIT_STATE_CHASE) && !mover->isMoving()) || (!mover->HasUnitState(UNIT_STATE_CHASE_MOVE) && mover->GetDistance(newtarget) > 1.5f))) + BotMovement(BOT_MOVE_CHASE, nullptr, newtarget); + //me->GetMotionMaster()->MoveChase(newtarget); + } + + if (newtarget != me->GetVictim() && (mover == me || CanBotAttackOnVehicle())) + { + if (!me->Attack(newtarget, !HasRole(BOT_ROLE_RANGED))) + me->SetInFront(newtarget); + } +} +//Bots cannot dodge/parry from behind so try to condense enemies at front +//opponent is always valid +bool bot_ai::AdjustTankingPosition(Unit const* mytarget) const +{ + //problem: chasing unit is constantly moving. Whoever the hell did that +// if (/*!IsTank() || */!me->IsInCombat() || me->isMoving() || IsCasting() || +// JumpingOrFalling() || CCed(me, true) || Rand() > 10 + 20*me->GetMap()->IsDungeon()) +// return; + if (/*!IsTank() || */!me->IsInCombat() || IsCasting() || me->GetVehicle() || + JumpingOrFalling() || CCed(me, true) || Rand() > 10 + 20*me->GetMap()->IsDungeon() || + HasBotCommandState(BOT_COMMAND_MASK_UNMOVING)) + return false; + + Unit::AttackerSet const& myattackers = me->getAttackers(); + if (myattackers.size() < 2) + return false; + + if (IsMelee()) + { + if (!me->IsWithinMeleeRange(mytarget)) + return false; + } + else + { + uint8 rangeMode = IAmFree() ? uint8(BOT_ATTACK_RANGE_LONG) : master->GetBotMgr()->GetBotAttackRangeMode(); + uint8 exactRange = rangeMode != BOT_ATTACK_RANGE_EXACT || IAmFree() ? 255 : master->GetBotMgr()->GetBotExactAttackRange(); + float dist = (rangeMode == BOT_ATTACK_RANGE_EXACT) ? exactRange : GetSpellAttackRange(rangeMode == BOT_ATTACK_RANGE_LONG); + + if (me->GetDistance(mytarget) > dist) + return false; + } + + //TC_LOG_ERROR("entities.player", "AdjustTankPosition() by {}", me->GetName()); + + uint32 bCount = 0; + for (Unit::AttackerSet::const_iterator itr = myattackers.begin(); itr != myattackers.end(); ++itr) + { + if (/*!CCed(*itr) && */(*itr)->IsWithinMeleeRange(me) && !me->HasInArc(float(M_PI), *itr)) + ++bCount; + //if (++bCount) + // break; + } + + if (bCount == 0) + return false; + + //TC_LOG_ERROR("entities.player", "AdjustTankPosition(): atts {}, behind {}", uint32(myattackers.size()), bCount); + + //calculate new position + float x = me->GetPositionX(); + float y = me->GetPositionY(); + float z = me->GetPositionZ(); + float ori = CCed(mytarget, true) ? me->GetOrientation() + 0.75f * M_PI : me->GetOrientation(); + float const moveDist = -1.f * std::max(mytarget->GetCombatReach(), 3.f); + float moveX; + float moveY; + //bool move = false; + for (uint32 i = 0; i != 6; ++i) + { + if (i) + { + ori = Position::NormalizeOrientation(ori + (i+1)*(M_PI*0.31f)); + } + + //move back + moveX = moveDist * std::cos(ori); + moveY = moveDist * std::sin(ori); + + Position ppos; + ppos.Relocate(x+moveX, y+moveY, z); + if (me->IsWithinLOS(x+moveX, y+moveY, z) && !IsWithinAoERadius(ppos)) + break; + + if (i == 2) + { + moveX *= 0.2f; + moveY *= 0.2f; + } + } + + x+= moveX; + y+= moveY; + + me->UpdateAllowedPositionZ(x, y, z); + if (me->GetPositionZ() < z) + z += 0.75f; //prevent going underground + + //if (CCed(mytarget, true)) + // me->AttackStop(); + //me->SetOrientation(ori); + Position position; + position.Relocate(x, y, z); + BotMovement(BOT_MOVE_POINT, &position); + //me->GetMotionMaster()->MovePoint(me->GetMapId(), x, y, z, false); + return true; +} + +void bot_ai::CheckAttackState() +{ + if (me->GetVictim() && me->GetVictim()->IsAlive()) + { + if (HasRole(BOT_ROLE_DPS) && !me->HasAuraType(SPELL_AURA_MOD_STEALTH) && !me->HasAuraType(SPELL_AURA_MOD_INVISIBILITY) && + !IsShootingWand()) + DoMeleeAttackIfReady(); + } +} +//Move behind current target if needed (avoid cleaves and dodges/parries, also rogues/ferals) +void bot_ai::MoveBehind(Unit const* target) const +{ + if (_moveBehindTimer > lastdiff || HasBotCommandState(BOT_COMMAND_MASK_UNMOVING) || HasRole(BOT_ROLE_RANGED) || JumpingOrFalling() || + /*(me->isMoving() && target->GetTypeId() != TYPEID_PLAYER) ||*/ + me->GetVehicle() || (IsTank() && target->GetVictim() == me) || CCed(me, true) || + !target->IsWithinCombatRange(me, ATTACK_DISTANCE) || !target->HasInArc(float(M_PI), me)) + return; + + const bool targetMe = target->GetVictim() == me; + const bool cced = CCed(target); + const bool isPlayer = target->GetTypeId() == TYPEID_PLAYER; + + if ((_botclass == BOT_CLASS_ROGUE || GetBotStance() == DRUID_CAT_FORM) ? (!targetMe || cced || isPlayer) : (!targetMe && (!cced || isPlayer))) + { + float myangle = Position::NormalizeOrientation(target->GetAbsoluteAngle(me) + float(M_PI)); + float mydist = me->GetCombatReach(); + Position position; + target->GetNearPoint(me, position.m_positionX, position.m_positionY, position.m_positionZ, mydist, myangle); + + if (IsWithinAoERadius(position)) + return; + + BotMovement(BOT_MOVE_POINT, &position); + const_cast(this)->_moveBehindTimer = urand(1000, (_botclass == BOT_CLASS_ROGUE || GetBotStance() == DRUID_CAT_FORM) ? 2000 : 4000); + } +} +//MOUNT SUPPORT +uint32 bot_ai::_selectMountSpell() const +{ + uint8 minLevel60 = BotMgr::GetNpcBotMountLevel60(); + uint8 minLevel100 = BotMgr::GetNpcBotMountLevel100(); + + if (me->GetLevel() < minLevel60) + return 0; + + uint32 myMountSpellId = 0; + uint32 masterMountSpellId = 0; + + InstanceTemplate const* instt = sObjectMgr->GetInstanceTemplate(me->GetMap()->GetId()); + bool map_allows_mount = (!me->GetMap()->IsDungeon() || me->GetMap()->IsBattlegroundOrArena()) && (!instt || instt->AllowMount); + if (!IAmFree() ? !master->IsMounted() : !(map_allows_mount && evadeDelayTimer < lastdiff * 2)) + return 0; + + Unit::AuraEffectList const& mounts = master->GetAuraEffectsByType(SPELL_AURA_MOUNTED); + int32 maxMountSpeed = !IAmFree() ? 0 : 999; + if (!IAmFree()) + { + Aura const* mountAura = nullptr; + for (AuraEffect const* meff : mounts) + { + for (uint8 i = EFFECT_0; i < MAX_SPELL_EFFECTS; ++i) + { + AuraEffect const* maeff = meff->GetBase()->GetEffect(i); + if (maeff && (maeff->GetSpellEffectInfo().IsAura(master->CanFly() ? SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED : SPELL_AURA_MOD_INCREASE_MOUNTED_SPEED)) && + maeff->GetAmount() > maxMountSpeed) + { + maxMountSpeed = maeff->GetAmount(); + mountAura = meff->GetBase(); + } + } + } + + if (maxMountSpeed < 20) + return 0; + + masterMountSpellId = mountAura ? mountAura->GetId() : 0; + } + + //Winter Veil addition + if (sGameEventMgr->IsActiveEvent(GAME_EVENT_WINTER_VEIL)) + myMountSpellId = master->CanFly() ? REINDEER_FLY : REINDEER; + if (!myMountSpellId && me->GetMapId() == 531) //Ahn'Qiraj + { + //Select AQ40 mount + static const std::array QirajiMountSpellIds = { QIRAJI_BATTLE_TANK_1, QIRAJI_BATTLE_TANK_2, QIRAJI_BATTLE_TANK_3, QIRAJI_BATTLE_TANK_4 }; + //Count Black Qiraji Battle Tank too + if (masterMountSpellId == 26656 || std::find(QirajiMountSpellIds.cbegin(), QirajiMountSpellIds.cend(), masterMountSpellId) != QirajiMountSpellIds.cend()) + myMountSpellId = QirajiMountSpellIds[me->GetEntry() % QirajiMountSpellIds.size()]; + } + + if (!myMountSpellId) + { + using MountArray = std::array; + + bool can_fly = !IAmFree() ? master->CanFly() : false; //(!instt && me->GetMap()->GetEntry()->ExpansionID > 0); + bool useSlowMount = can_fly ? (me->GetLevel() < 70 || maxMountSpeed < 220) : (me->GetLevel() < minLevel100 || maxMountSpeed < 80); + + if (!can_fly) + { + //Select by class + switch (_botclass) + { + case BOT_CLASS_DARK_RANGER: + myMountSpellId = BOT_DARK_RANGER_MOUNT; + break; + case BOT_CLASS_WARLOCK: + myMountSpellId = useSlowMount ? BOT_WARLOCK_MOUNT : BOT_WARLOCK_FAST_MOUNT; + break; + case BOT_CLASS_PALADIN: + if (me->GetRace() == RACE_BLOODELF) + myMountSpellId = useSlowMount ? BOT_BE_PALLY_MOUNT : BOT_BE_PALLY_FAST_MOUNT; + else + myMountSpellId = useSlowMount ? BOT_ALLI_PALLY_MOUNT : BOT_ALLI_PALLY_FAST_MOUNT; + break; + case BOT_CLASS_DEATH_KNIGHT: + myMountSpellId = BOT_DEATH_KNIGHT_MOUNT; + break; + default: + break; + } + //Select by race + if (!myMountSpellId) + { + static const MountArray MOUNTS_60_HUMAN = { BOT_MOUNT_HUMAN_60_1, BOT_MOUNT_HUMAN_60_2, BOT_MOUNT_HUMAN_60_3 }; + static const MountArray MOUNTS_60_ORC = { BOT_MOUNT_ORC_60_1, BOT_MOUNT_ORC_60_2, BOT_MOUNT_ORC_60_3 }; + static const MountArray MOUNTS_60_DWARF = { BOT_MOUNT_DWARF_60_1, BOT_MOUNT_DWARF_60_2, BOT_MOUNT_DWARF_60_3 }; + static const MountArray MOUNTS_60_NIGHTELF = { BOT_MOUNT_NIGHTELF_60_1, BOT_MOUNT_NIGHTELF_60_2, BOT_MOUNT_NIGHTELF_60_3 }; + static const MountArray MOUNTS_60_FORSAKEN = { BOT_MOUNT_FORSAKEN_60_1, BOT_MOUNT_FORSAKEN_60_2, BOT_MOUNT_FORSAKEN_60_3 }; + static const MountArray MOUNTS_60_TAUREN = { BOT_MOUNT_TAUREN_60_1, BOT_MOUNT_TAUREN_60_2, BOT_MOUNT_TAUREN_60_3 }; + static const MountArray MOUNTS_60_GNOME = { BOT_MOUNT_GNOME_60_1, BOT_MOUNT_GNOME_60_2, BOT_MOUNT_GNOME_60_3 }; + static const MountArray MOUNTS_60_TROLL = { BOT_MOUNT_TROLL_60_1, BOT_MOUNT_TROLL_60_2, BOT_MOUNT_TROLL_60_3 }; + static const MountArray MOUNTS_60_BLOODELF = { BOT_MOUNT_BLOODELF_60_1, BOT_MOUNT_BLOODELF_60_2, BOT_MOUNT_BLOODELF_60_3 }; + static const MountArray MOUNTS_60_DRAENEI = { BOT_MOUNT_DRAENEI_60_1, BOT_MOUNT_DRAENEI_60_2, BOT_MOUNT_DRAENEI_60_3 }; + + static const MountArray MOUNTS_100_HUMAN = { BOT_MOUNT_HUMAN_100_1, BOT_MOUNT_HUMAN_100_2, BOT_MOUNT_HUMAN_100_3 }; + static const MountArray MOUNTS_100_ORC = { BOT_MOUNT_ORC_100_1, BOT_MOUNT_ORC_100_2, BOT_MOUNT_ORC_100_3 }; + static const MountArray MOUNTS_100_DWARF = { BOT_MOUNT_DWARF_100_1, BOT_MOUNT_DWARF_100_2, BOT_MOUNT_DWARF_100_3 }; + static const MountArray MOUNTS_100_NIGHTELF = { BOT_MOUNT_NIGHTELF_100_1, BOT_MOUNT_NIGHTELF_100_2, BOT_MOUNT_NIGHTELF_100_3 }; + static const MountArray MOUNTS_100_FORSAKEN = { BOT_MOUNT_FORSAKEN_100_1, BOT_MOUNT_FORSAKEN_100_2, BOT_MOUNT_FORSAKEN_100_3 }; + static const MountArray MOUNTS_100_TAUREN = { BOT_MOUNT_TAUREN_100_1, BOT_MOUNT_TAUREN_100_2, BOT_MOUNT_TAUREN_100_3 }; + static const MountArray MOUNTS_100_GNOME = { BOT_MOUNT_GNOME_100_1, BOT_MOUNT_GNOME_100_2, BOT_MOUNT_GNOME_100_3 }; + static const MountArray MOUNTS_100_TROLL = { BOT_MOUNT_TROLL_100_1, BOT_MOUNT_TROLL_100_2, BOT_MOUNT_TROLL_100_3 }; + static const MountArray MOUNTS_100_BLOODELF = { BOT_MOUNT_BLOODELF_100_1, BOT_MOUNT_BLOODELF_100_2, BOT_MOUNT_BLOODELF_100_3 }; + static const MountArray MOUNTS_100_DRAENEI = { BOT_MOUNT_DRAENEI_100_1, BOT_MOUNT_DRAENEI_100_2, BOT_MOUNT_DRAENEI_100_3 }; + + Optional myMounts; + switch (me->GetRace()) + { + case RACE_HUMAN: myMounts = useSlowMount ? MOUNTS_60_HUMAN : MOUNTS_100_HUMAN; break; + case RACE_ORC: myMounts = useSlowMount ? MOUNTS_60_ORC : MOUNTS_100_ORC; break; + case RACE_DWARF: myMounts = useSlowMount ? MOUNTS_60_DWARF : MOUNTS_100_DWARF; break; + case RACE_NIGHTELF: myMounts = useSlowMount ? MOUNTS_60_NIGHTELF : MOUNTS_100_NIGHTELF; break; + case RACE_UNDEAD_PLAYER: myMounts = useSlowMount ? MOUNTS_60_FORSAKEN : MOUNTS_100_FORSAKEN; break; + case RACE_TAUREN: myMounts = useSlowMount ? MOUNTS_60_TAUREN : MOUNTS_100_TAUREN; break; + case RACE_GNOME: myMounts = useSlowMount ? MOUNTS_60_GNOME : MOUNTS_100_GNOME; break; + case RACE_TROLL: myMounts = useSlowMount ? MOUNTS_60_TROLL : MOUNTS_100_TROLL; break; + case RACE_BLOODELF: myMounts = useSlowMount ? MOUNTS_60_BLOODELF : MOUNTS_100_BLOODELF; break; + case RACE_DRAENEI: myMounts = useSlowMount ? MOUNTS_60_DRAENEI : MOUNTS_100_DRAENEI; break; + default: break; + } + + if (myMounts) + myMountSpellId = (*myMounts)[me->GetEntry() % myMounts->size()]; + } + } + else //if (can_fly) + { + static const MountArray MOUNTS_150_ALLIANCE = { BOT_MOUNT_FLY_ALLIANCE_150_1, BOT_MOUNT_FLY_ALLIANCE_150_2, BOT_MOUNT_FLY_ALLIANCE_150_3 }; + static const MountArray MOUNTS_150_HORDE = { BOT_MOUNT_FLY_HORDE_150_1, BOT_MOUNT_FLY_HORDE_150_2, BOT_MOUNT_FLY_HORDE_150_3 }; + static const MountArray MOUNTS_280_ALLIANCE = { BOT_MOUNT_FLY_ALLIANCE_280_1, BOT_MOUNT_FLY_ALLIANCE_280_2, BOT_MOUNT_FLY_ALLIANCE_280_3 }; + static const MountArray MOUNTS_280_HORDE = { BOT_MOUNT_FLY_HORDE_280_1, BOT_MOUNT_FLY_HORDE_280_2, BOT_MOUNT_FLY_HORDE_280_3 }; + + Optional myMounts; + if (me->GetRaceMask() & RACEMASK_ALLIANCE) + myMounts = useSlowMount ? MOUNTS_150_ALLIANCE : MOUNTS_280_ALLIANCE; + else if (me->GetRaceMask() & RACEMASK_HORDE) + myMounts = useSlowMount ? MOUNTS_150_HORDE : MOUNTS_280_HORDE; + + if (myMounts) + myMountSpellId = (*myMounts)[me->GetEntry() % myMounts->size()]; + } + } + + if (!myMountSpellId) // shouldn't happen normally + { + if (masterMountSpellId) + myMountSpellId = masterMountSpellId; + else if (!mounts.empty()) + myMountSpellId = mounts.front()->GetId(); + } + + return myMountSpellId; +} +void bot_ai::_updateMountedState() +{ + bool aura = me->HasAuraType(SPELL_AURA_MOUNTED); + bool mounted = me->IsMounted() && (_botclass != BOT_CLASS_ARCHMAGE || aura); + bool template_fly = me->GetCreatureTemplate()->Movement.Flight != CreatureFlightMovementType::None; + Unit const* victim = me->GetVictim(); + + //allow dismount + if (!CanMount() && !aura && !mounted) + return; + + if ((aura || mounted || template_fly) && + (!master->IsMounted() || aura != mounted || (!mounted && template_fly) || + (me->IsInCombat() && (opponent || disttarget)) || + (IAmFree() && victim && me->IsWithinDist(victim, IsMelee() ? 5.0f : GetSpellAttackRange(true), false)))) + { + DismountBot(); + return; + } + + if (me->IsMounted() || me->GetVehicle() || me->HasUnitMovementFlag(MOVEMENTFLAG_SWIMMING) || !IsOutdoors() || + master->IsInCombat() || me->IsInCombat() || me->GetVictim() || IsCasting() || IsFlagCarrier(me) || + (HasBotCommandState(BOT_COMMAND_STAY) && GetBG() && GetBG()->GetStatus() != STATUS_IN_PROGRESS)) + return; + + if (IAmFree()) + { + if (!IsWanderer() || me->GetLevel() < BotMgr::GetNpcBotMountLevel60() || me->HasAuraType(SPELL_AURA_PERIODIC_DAMAGE) || + Feasting() || GetHealthPCT(me) < 80 || (CanDrink() && me->GetMaxPower(POWER_MANA) > 1 && GetManaPCT(me) < 70)) + return; + } + + if (uint32 mount_spell_id = _selectMountSpell()) + { + if (me->HasAuraType(SPELL_AURA_MOUNTED)) + me->RemoveAurasByType(SPELL_AURA_MOUNTED); + + if (!((_botclass == BOT_CLASS_DRUID || _botclass == BOT_CLASS_SHAMAN) && me->GetShapeshiftForm() != FORM_NONE) || removeShapeshiftForm()) + doCast(me, mount_spell_id); + } +} +//STANDSTATE +void bot_ai::_updateStandState() const +{ + if (IAmFree()) + { + if (CanSit() && !IsWanderer()) + { + if (_atHome && !_evadeMode && !me->IsInCombat() && !me->isMoving() && + me->IsStandState() && Rand() < 15) + { + uint16 mapid; + Position pos; + GetHomePosition(mapid, &pos); + if (me->GetExactDist(&pos) < 5 && me->GetOrientation() == pos.GetOrientation()) + { + if (_botclass == BOT_CLASS_DRUID && me->GetShapeshiftForm() != FORM_NONE) + const_cast(this)->removeShapeshiftForm(); + + me->SetStandState(UNIT_STAND_STATE_SIT); + } + } + } + else if (me->IsSitState() && !me->HasInterruptFlag(AURA_INTERRUPT_FLAG_NOT_SEATED)) + me->SetStandState(UNIT_STAND_STATE_STAND); + + return; + } + + if (me->GetVehicle()) + return; + + if ((master->GetStandState() == UNIT_STAND_STATE_STAND || !CanSit()) && + me->GetStandState() == UNIT_STAND_STATE_SIT && + !me->HasInterruptFlag(AURA_INTERRUPT_FLAG_NOT_SEATED)) + me->SetStandState(UNIT_STAND_STATE_STAND); + if (CanSit() && !me->IsInCombat() && !me->isMoving() && + (master->GetStandState() == UNIT_STAND_STATE_SIT || me->HasInterruptFlag(AURA_INTERRUPT_FLAG_NOT_SEATED)) && + me->GetStandState() == UNIT_STAND_STATE_STAND) + me->SetStandState(UNIT_STAND_STATE_SIT); +} +//RATIONS +void bot_ai::_updateRations() +{ + bool noFeast = me->IsInCombat() || (BotMgr::IsFoodInterruptedByMovement() && me->isMoving()) || me->GetVictim() || CCed(me) || IsFlagCarrier(me); + + //check + if (IAmFree() || !master->IsSitState()) + { + if (feast_mana) + { + if (noFeast || me->IsStandState() || me->GetMaxPower(POWER_MANA) <= 1 || me->GetPower(POWER_MANA) >= me->GetMaxPower(POWER_MANA)) + { + std::list spellIds; + Unit::AuraApplicationMap const& aurApps = me->GetAppliedAuras(); + for (Unit::AuraApplicationMap::const_iterator ci = aurApps.begin(); ci != aurApps.end(); ++ci) + if (ci->second->GetBase()->GetSpellInfo()->GetSpellSpecific() == SPELL_SPECIFIC_DRINK && + !ci->second->GetBase()->GetSpellInfo()->HasAura(SPELL_AURA_PERIODIC_TRIGGER_SPELL)) //skip buffing food + spellIds.push_back(ci->first); + for (std::list::const_iterator cit = spellIds.begin(); cit != spellIds.end(); ++cit) + me->RemoveAurasDueToSpell(*cit); + feast_mana = false; + UpdateMana(); + } + } + if (feast_health) + { + if (noFeast || me->IsStandState() || me->GetHealth() >= me->GetMaxHealth()) + { + std::list spellIds; + Unit::AuraApplicationMap const& aurApps = me->GetAppliedAuras(); + for (Unit::AuraApplicationMap::const_iterator ci = aurApps.begin(); ci != aurApps.end(); ++ci) + if (ci->second->GetBase()->GetSpellInfo()->GetSpellSpecific() == SPELL_SPECIFIC_FOOD && + !ci->second->GetBase()->GetSpellInfo()->HasAura(SPELL_AURA_PERIODIC_TRIGGER_SPELL)) //skip buffing food + spellIds.push_back(ci->first); + for (std::list::const_iterator cit = spellIds.begin(); cit != spellIds.end(); ++cit) + me->RemoveAurasDueToSpell(*cit); + feast_health = false; + } + } + } + + if (noFeast) + return; + + //drink + if (!feast_mana && me->GetMaxPower(POWER_MANA) > 1 && !me->HasAuraType(SPELL_AURA_MOUNTED) && !me->isMoving() && CanDrink() && + !me->IsInCombat() && !me->GetVehicle() && !IsCasting() && GetManaPCT(me) < 75 && urand(0, 100) < 20) + { + me->CastSpell(me, GetRation(true), true); + } + + //eat + if (!feast_health && !me->HasAuraType(SPELL_AURA_MOUNTED) && !me->isMoving() && CanEat() && + !me->IsInCombat() && !me->GetVehicle() && !IsCasting() && GetHealthPCT(me) < 80 && urand(0, 100) < 20) + { + me->CastSpell(me, GetRation(false), true); + } +} +//Health and Powers regeneration +//Rage regen is handled inside class AI UpdateAI() +void bot_ai::Regenerate() +{ + regenTimer += lastdiff; + + //every tick + if (me->GetPowerType() == POWER_ENERGY) + RegenerateEnergy(); + + if (regenTimer >= REGEN_CD) + { + regenTimer -= REGEN_CD; + // Regen Health + int32 baseRegen = int32(_getTotalBotStat(BOT_STAT_MOD_HEALTH_REGEN)); + if ((!me->IsInCombat() || me->IsPolymorphed() || CanRegenInCombat() || baseRegen > 0 || + me->HasAuraType(SPELL_AURA_MOD_REGEN_DURING_COMBAT) || me->HasAuraType(SPELL_AURA_MOD_HEALTH_REGEN_IN_COMBAT)) && + me->GetHealth() < me->GetMaxHealth()) + { + int32 add = me->IsInCombat() ? 0 : IAmFree() && !me->GetVictim() ? me->GetMaxHealth() / 64 : 5 + me->GetCreateHealth() / 256; + if (baseRegen > 0) + add += std::max(baseRegen / 5, 1); + + //cannot eat + if (_botclass == BOT_CLASS_SPHYNX) + add += me->GetMaxHealth() / 100; //1% + + if (me->IsPolymorphed()) + add += me->GetMaxHealth() / 6; + else if (!me->IsInCombat() || me->HasAuraType(SPELL_AURA_MOD_REGEN_DURING_COMBAT)) + { + if (!me->IsInCombat()) + { + Unit::AuraEffectList const& mModHealthRegenPct = me->GetAuraEffectsByType(SPELL_AURA_MOD_HEALTH_REGEN_PERCENT); + for (Unit::AuraEffectList::const_iterator i = mModHealthRegenPct.begin(); i != mModHealthRegenPct.end(); ++i) + AddPct(add, (*i)->GetAmount()); + + add += me->GetTotalAuraModifier(SPELL_AURA_MOD_REGEN) * REGEN_CD / 5000; + } + else if (me->HasAuraType(SPELL_AURA_MOD_REGEN_DURING_COMBAT)) + ApplyPct(add, me->GetTotalAuraModifier(SPELL_AURA_MOD_REGEN_DURING_COMBAT)); + } + + add += me->GetTotalAuraModifier(SPELL_AURA_MOD_HEALTH_REGEN_IN_COMBAT); + + if (add < 0) + add = 0; + + me->ModifyHealth(add); + } + + // Regen Mana + if (me->GetMaxPower(POWER_MANA) > 1 && + (me->GetPower(POWER_MANA) < me->GetMaxPower(POWER_MANA) || _botclass == BOT_CLASS_SPHYNX)) + { + float addvalue; + if (me->IsUnderLastManaUseEffect()) + addvalue = me->GetFloatValue(UNIT_FIELD_POWER_REGEN_INTERRUPTED_FLAT_MODIFIER); + else + addvalue = me->GetFloatValue(UNIT_FIELD_POWER_REGEN_FLAT_MODIFIER); + + addvalue *= sWorld->getRate(RATE_POWER_MANA) * float(REGEN_CD) * 0.001f; //regenTimer threshold / 1000 + + if (addvalue < 0.0f) + addvalue = 0.0f; + + if (_botclass == BOT_CLASS_SPHYNX) + addvalue *= -1.f; + + me->ModifyPower(POWER_MANA, int32(addvalue)); + } + } +} + +void bot_ai::RegenerateEnergy() +{ + uint32 curValue = me->GetPower(POWER_ENERGY); + uint32 maxValue = me->GetMaxPower(POWER_ENERGY); + + if (curValue < maxValue) + { + float addvalue = 0.01f * lastdiff * sWorld->getRate(RATE_POWER_ENERGY); //10 per sec + Unit::AuraEffectList const& ModPowerRegenPCTAuras = me->GetAuraEffectsByType(SPELL_AURA_MOD_POWER_REGEN_PERCENT); + for (Unit::AuraEffectList::const_iterator i = ModPowerRegenPCTAuras.begin(); i != ModPowerRegenPCTAuras.end(); ++i) + if (Powers((*i)->GetMiscValue()) == POWER_ENERGY) + AddPct(addvalue, (*i)->GetAmount()); + + //not present in db + //addvalue += me->GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_POWER_REGEN, POWER_ENERGY) * lastdiff / 5000; + //me->ModifyPower(POWER_ENERGY, int32(addvalue)); + + addvalue += _energyFraction; + + if (addvalue == 0x0) //only if world rate for enegy is 0 + return; + + uint32 integerValue = uint32(fabs(addvalue)); + + curValue += integerValue; + + if (curValue > maxValue) + { + curValue = maxValue; + _energyFraction = 0.f; + } + else + _energyFraction = addvalue - float(integerValue); + + if (curValue == maxValue || regenTimer >= REGEN_CD) + me->SetPower(POWER_ENERGY, curValue); + else + me->UpdateUInt32Value(UNIT_FIELD_POWER1 + uint16(POWER_ENERGY), curValue); + } +} + +bool bot_ai::Feasting() const +{ + if (!me->HasInterruptFlag(AURA_INTERRUPT_FLAG_NOT_SEATED)) + return false; + + return + me->IsSitState() && + (me->HasAuraType(SPELL_AURA_MOD_REGEN) || me->HasAuraType(SPELL_AURA_OBS_MOD_HEALTH) || + me->HasAuraType(SPELL_AURA_MOD_POWER_REGEN) || me->HasAuraType(SPELL_AURA_OBS_MOD_POWER)); +} +uint32 bot_ai::GetRation(bool drink) const +{ + for (int8 i = MAX_FEAST_SPELLS - 1; i >= 0; --i) + if (me->GetLevel() >= (drink ? DrinkSpells[i][0] : EatSpells[i][0])) + return (drink ? DrinkSpells[i][1] : EatSpells[i][1]); + + return (drink ? DrinkSpells[0][1] : EatSpells[0][1]); +} + +void bot_ai::DrinkPotion(bool mana) +{ + if (IsCasting()) + return; + + me->CastSpell(me, GetPotion(mana)); +} +bool bot_ai::IsPotionReady() const +{ + return _potionTimer <= lastdiff; +} +uint32 bot_ai::GetPotion(bool mana) const +{ + for (int8 i = MAX_POTION_SPELLS - 1; i >= 0; --i) + if (me->GetLevel() >= (mana ? ManaPotionSpells[i][0] : HealingPotionSpells[i][0])) + return (mana ? ManaPotionSpells[i][1] : HealingPotionSpells[i][1]); + + return (mana ? ManaPotionSpells[0][1] : HealingPotionSpells[0][1]); +} +bool bot_ai::IsPotionSpell(uint32 spellId) const +{ + return spellId == GetPotion(true) || spellId == GetPotion(false); +} + +/*static */BotItemUseSpellTargeting SelectTargeTypetForItemSpell(uint32 spellId, Unit const* caster) +{ + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); + if (!spellInfo || spellInfo->CalcCastTime() > 1500) + return BOT_ITEM_USE_SPELL_TARGET_NONE; + + TSpellSummary const& sum = SpellSummary[spellId]; + if (sum.Effects & (1u << (SELECT_EFFECT_DAMAGE-1))) + { + if (sum.Targets & ((1u << (SELECT_TARGET_SINGLE_ENEMY-1)) | (1u << (SELECT_TARGET_ANY_ENEMY-1)))) + return BOT_ITEM_USE_SPELL_TARGET_ATTACKTARGET; + else if (sum.Targets & (1u << (SELECT_TARGET_AOE_ENEMY-1))) + return BOT_ITEM_USE_SPELL_TARGET_SELF; + } + else if (sum.Effects & (1u << (SELECT_EFFECT_HEALING-1))) + { + if (sum.Targets & ((1u << (SELECT_TARGET_SELF-1)) | (1u << (SELECT_TARGET_SINGLE_FRIEND-1)) | (1u << (SELECT_TARGET_AOE_FRIEND-1)) | (1u << (SELECT_TARGET_ANY_FRIEND-1)))) + if (caster->GetHealthPct() < 75.f) + return BOT_ITEM_USE_SPELL_TARGET_SELF; + } + else if (sum.Effects & (1u << (SELECT_EFFECT_AURA-1))) + { + if (sum.Targets & ((1u << (SELECT_TARGET_SELF-1)) | (1u << (SELECT_TARGET_AOE_FRIEND-1)) | (1u << (SELECT_TARGET_AOE_ENEMY-1)))) + return BOT_ITEM_USE_SPELL_TARGET_SELF; + else if (sum.Targets & ((1u << (SELECT_TARGET_SINGLE_FRIEND-1)) | (1u << (SELECT_TARGET_ANY_FRIEND-1)))) + return BOT_ITEM_USE_SPELL_TARGET_ALLY; + else if (sum.Targets & ((1u << (SELECT_TARGET_SINGLE_ENEMY-1)) | (1u << (SELECT_TARGET_ANY_ENEMY-1)))) + return BOT_ITEM_USE_SPELL_TARGET_ATTACKTARGET; + } + else // if (sum.Effects == 0) + { + if (sum.Targets & ((1u << (SELECT_TARGET_SINGLE_ENEMY-1)) | (1u << (SELECT_TARGET_ANY_ENEMY-1)))) + return BOT_ITEM_USE_SPELL_TARGET_ATTACKTARGET; + else if (sum.Targets & ((1u << (SELECT_TARGET_AOE_ENEMY-1)) | (1u << (SELECT_TARGET_SELF-1)) | (1u << (SELECT_TARGET_SINGLE_FRIEND-1)) | (1u << (SELECT_TARGET_AOE_FRIEND-1)) | (1u << (SELECT_TARGET_ANY_FRIEND-1)))) + return BOT_ITEM_USE_SPELL_TARGET_SELF; + else // if (sum.Targets == 0) + return BOT_ITEM_USE_SPELL_TARGET_SELF; + } + + return BOT_ITEM_USE_SPELL_TARGET_NONE; +} +bool bot_ai::IsUsableItem(Item const* item) +{ + if (ItemTemplate const* proto = item->GetTemplate()) + { + for (auto const& itemSpell : proto->Spells) + { + if (itemSpell.SpellId != 0 && itemSpell.SpellTrigger == ITEM_SPELLTRIGGER_ON_USE) + return true; + } + } + + return false; +} +uint32 bot_ai::GetItemSpellCooldown(uint32 spellId) const +{ + for (Item const* item : _equips) + { + if (item && IsUsableItem(item)) + { + ItemTemplate const* proto = item->GetTemplate(); + for (auto const& itemSpell : proto->Spells) + { + if (itemSpell.SpellId == decltype(itemSpell.SpellId)(spellId)) + return itemSpell.SpellCooldown; + } + } + } + + return 0; +} +void bot_ai::CheckUsableItems(uint32 diff) +{ + if (!_usableItemSlotsMask || itemsAutouseTimer > diff || !me->IsInCombat() || IsCasting() || (!me->GetVictim() && me->getAttackers().empty())) + return; + + itemsAutouseTimer = urand(2500, 5500); + + for (uint8 slot = BOT_SLOT_MAINHAND; slot < BOT_INVENTORY_SIZE; ++slot) + { + if (_usableItemSlotsMask & (1ul << slot)) + { + if (Item const* item = _equips[slot]) + { + bool is_spell_ready = false; + uint32 firstItemSpellId = 0; + for (auto const& itemSpell : item->GetTemplate()->Spells) + { + if (itemSpell.SpellId > 0 && itemSpell.SpellTrigger == ITEM_SPELLTRIGGER_ON_USE) + { + if (firstItemSpellId == 0) + firstItemSpellId = itemSpell.SpellId; + + if (IsSpellReady(itemSpell.SpellId, diff, false)) + is_spell_ready = true; + else + { + is_spell_ready = false; + break; + } + } + } + if (!is_spell_ready) + continue; + + uint32 targetType = SelectTargeTypetForItemSpell(firstItemSpellId, me); + Unit* castTarget = nullptr; + switch (targetType) + { + case BOT_ITEM_USE_SPELL_TARGET_SELF: + castTarget = me; + break; + case BOT_ITEM_USE_SPELL_TARGET_ATTACKTARGET: + castTarget = me->GetVictim(); + break; + case BOT_ITEM_USE_SPELL_TARGET_ALLY: + castTarget = me->GetNextRandomRaidMemberOrPet(10.f); + if (!castTarget) + castTarget = me; + break; + case BOT_ITEM_USE_SPELL_TARGET_NONE: + default: + break; + } + + //TC_LOG_ERROR("scripts", "bot_ai::CheckUsableItems(): bot {}, slot {}, spell {}, target {}", + // me->GetName(), uint32(slot), firstItemSpellId, targetType); + + if (!castTarget) + continue; + + SpellCastTargets targets; + targets.SetUnitTarget(castTarget); + _castBotItemUseSpell(item, targets); + + // do not delay next check unless all items were checked + if (slot < BOT_SLOT_TRINKET2) + itemsAutouseTimer = 0; + + break; + } + else + TC_LOG_ERROR("scripts", "bot_ai::CheckUsableItems(): slot {} is in mask but no item exists in that slot!", uint32(slot)); + } + } +} +//check if our party players are in duel. if so - ignore them, their opponents and any bots they have +//Deprecated after 4c26c85 +//bool bot_ai::InDuel(Unit const* target) const +//{ +// if (!target) return false; +// bool isbot = target->GetTypeId() == TYPEID_UNIT && target->ToCreature()->IsNPCBot(); +// Player const* player = target->GetTypeId() == TYPEID_PLAYER ? target->ToPlayer() : isbot ? target->ToCreature()->GetBotOwner()->ToPlayer() : nullptr; +// if (!player) +// { +// if (!target->IsControlledByPlayer()) +// return false; +// player = target->GetCharmerOrOwnerPlayerOrPlayerItself(); +// } +// +// return (player && player->duel && (IsInBotParty(player) || IsInBotParty(player->duel->opponent))); +//} +//////////////// +//GRID SEARCHERS +//////////////// +//Finds player or it's corpse for resurrection returned as WorldObject* +WorldObject* bot_ai::GetNearbyRezTarget(float dist) const +{ + std::list list; + + NearbyRezTargetCheck check(me, dist, this); + Trinity::WorldObjectListSearcher searcher(me, list, check); + Cell::VisitWorldObjects(me, searcher, dist); + + if (list.empty()) + return nullptr; + if (list.size() == 1) + return *list.begin(); + + return Trinity::Containers::SelectRandomContainerElement(list); +} +//Finds target for warrior's Shattering Throw +Unit* bot_ai::FindImmunityShieldDispelTarget(float dist) const +{ + //not checking range + if (me->GetVictim() && me->GetVictim()->HasAuraWithMechanic(1<GetVictim(); + + Unit* unit = nullptr; + + ImmunityShieldDispelTargetCheck check(me, dist, this); + Trinity::UnitSearcher searcher(me, unit, check); + Cell::VisitAllObjects(me, searcher, dist); + //me->VisitNearbyObject(dist, searcher); + + return unit; +} +//Used to find target for priest's dispels, mage's spellsteal and shaman's purge +//Returns dispellable/stealable 'Any Hostile Unit Attacking BotParty' +Unit* bot_ai::FindHostileDispelTarget(float dist, bool stealable) const +{ + std::list unitList; + + HostileDispelTargetCheck check(me, dist, stealable, this); + Trinity::UnitListSearcher searcher(me, unitList, check); + Cell::VisitAllObjects(me, searcher, dist); + //me->VisitNearbyObject(dist, searcher); + + if (unitList.empty()) + return nullptr; + if (unitList.size() == 1) + return *unitList.begin(); + + return Trinity::Containers::SelectRandomContainerElement(unitList); +} +//Finds single target affected by given spell (and given caster if is) +//Can check: +// hostile targets (hostile = 0) +// DEPRECATED our party players (hostile = 1) +// DEPRECATED our party members (hostile = 2) +// any friendly target (hostile = 3) +// any friendly player (hostile = 4) +// any target in range (hostile = any other value) +Unit* bot_ai::FindAffectedTarget(uint32 spellId, ObjectGuid caster, float dist, uint8 hostile) const +{ + if (!spellId) + return nullptr; + if ((hostile == 2 || hostile == 1)/* && IAmFree()*/) + { + TC_LOG_ERROR("entities.player", "bot_ai::FindAffectedTarget(): hostile = {} Setting to ALL...", hostile); + hostile = 255; + } + if (master->GetMap()->Instanceable()) + dist = DEFAULT_VISIBILITY_INSTANCE; + + Unit* unit = nullptr; + + AffectedTargetCheck check(caster, dist, spellId, master, hostile); + Trinity::UnitSearcher searcher(master, unit, check); + Cell::VisitAllObjects(me, searcher, dist); + //me->VisitNearbyObject(dist, searcher); + + return unit; +} +//Finds target for mage's polymorph or shaman's hex +Unit* bot_ai::FindPolyTarget(float dist) const +{ + std::list unitList; + + PolyUnitCheck check(me, dist); + Trinity::UnitListSearcher searcher(me, unitList, check); + Cell::VisitAllObjects(me, searcher, dist); + //me->VisitNearbyObject(dist, searcher); + + if (unitList.empty()) + return nullptr; + if (unitList.size() == 1) + return *unitList.begin(); + decltype(unitList)::const_iterator it = std::find_if(unitList.cbegin(), unitList.cend(), [this](Unit const* u) { return IsPointedNoDPSTarget(u); }); + if (it != unitList.cend()) + return *it; + + return Trinity::Containers::SelectRandomContainerElement(unitList); +} +//Finds target for direct fear (warlock, hunter) +Unit* bot_ai::FindFearTarget(float dist) const +{ + std::list unitList; + + FearUnitCheck check(me, dist); + Trinity::UnitListSearcher searcher(me, unitList, check); + Cell::VisitAllObjects(me, searcher, dist); + //me->VisitNearbyObject(dist, searcher); + + if (unitList.empty()) + return nullptr; + if (unitList.size() == 1) + return *unitList.begin(); + decltype(unitList)::const_iterator it = std::find_if(unitList.cbegin(), unitList.cend(), [this](Unit const* u) { return IsPointedNoDPSTarget(u); }); + if (it != unitList.cend()) + return *it; + + return Trinity::Containers::SelectRandomContainerElement(unitList); +} +//Finds target for CC spells with MECHANIC_STUN +Unit* bot_ai::FindStunTarget(float dist) const +{ + std::list unitList; + + StunUnitCheck check(me, dist); + Trinity::UnitListSearcher searcher(me, unitList, check); + Cell::VisitAllObjects(me, searcher, dist); + //me->VisitNearbyObject(dist, searcher); + + if (unitList.empty()) + return nullptr; + if (unitList.size() == 1) + return *unitList.begin(); + decltype(unitList)::const_iterator it = std::find_if(unitList.cbegin(), unitList.cend(), [this](Unit const* u) { return IsPointedNoDPSTarget(u); }); + if (it != unitList.cend()) + return *it; + + return Trinity::Containers::SelectRandomContainerElement(unitList); +} +//Finds target for priest's shackles +Unit* bot_ai::FindUndeadCCTarget(float dist, uint32 spellId, bool unattacked) const +{ + if (!spellId) + return nullptr; + + std::list unitList; + + UndeadCCUnitCheck check(me, dist, this, spellId, unattacked); + Trinity::UnitListSearcher searcher(me, unitList, check); + Cell::VisitAllObjects(me, searcher, dist); + //me->VisitNearbyObject(dist, searcher); + + if (unitList.empty()) + return nullptr; + if (unitList.size() == 1) + return *unitList.begin(); + decltype(unitList)::const_iterator it = std::find_if(unitList.cbegin(), unitList.cend(), [this](Unit const* u) { return IsPointedNoDPSTarget(u); }); + if (it != unitList.cend()) + return *it; + + return Trinity::Containers::SelectRandomContainerElement(unitList); +} +//Finds target for druid's Entangling Roots +Unit* bot_ai::FindRootTarget(float dist, uint32 spellId) const +{ + if (!spellId) + return nullptr; + + std::list unitList; + + RootUnitCheck check(me, dist, this, spellId); + Trinity::UnitListSearcher searcher(me, unitList, check); + Cell::VisitAllObjects(me, searcher, dist); + //me->VisitNearbyObject(dist, searcher); + + if (unitList.empty()) + return nullptr; + if (unitList.size() == 1) + return *unitList.begin(); + decltype(unitList)::const_iterator it = std::find_if(unitList.cbegin(), unitList.cend(), [this](Unit const* u) { return IsPointedNoDPSTarget(u); }); + if (it != unitList.cend()) + return *it; + + return Trinity::Containers::SelectRandomContainerElement(unitList); +} +//Finds casting target (neutral or enemy) +//Can be used to get silence/interruption/reflect/grounding check +Unit* bot_ai::FindCastingTarget(float maxdist, float mindist, uint32 spellId, uint8 minHpPct) const +{ + std::list unitList; + + CastingUnitCheck check(me, mindist, maxdist, spellId, minHpPct); + Trinity::UnitListSearcher searcher(me, unitList, check); + Cell::VisitAllObjects(me, searcher, maxdist); + //me->VisitNearbyObject(maxdist, searcher); + + if (unitList.empty()) + return nullptr; + if (unitList.size() == 1) + return *unitList.begin(); + decltype(unitList)::const_iterator it = std::find_if(unitList.cbegin(), unitList.cend(), [this](Unit const* u) { return IsPointedNoDPSTarget(u); }); + if (it != unitList.cend()) + return *it; + + return Trinity::Containers::SelectRandomContainerElement(unitList); +} +// Returns target for dest AOE spell (blizzard, hurricane, etc.) based on crowd size, movement state and direction +Unit* bot_ai::FindAOETarget(float dist, WorldObject const* src) const +{ + if (!src) + src = me; + + std::list unitList; + GetNearbyTargetsList(unitList, dist, 0); + + if (unitList.size() < 3) + return nullptr; + + Unit* unit = nullptr; + float mydist = dist; + for (std::list::const_iterator itr = unitList.begin(); itr != unitList.end(); ++itr) + { + if ((*itr)->isMoving() && (*itr)->GetVictim() && + ((*itr)->GetDistance2d((*itr)->GetVictim()->GetPositionX(), (*itr)->GetVictim()->GetPositionY()) > 7.5f || + !(*itr)->HasInArc(float(M_PI)*0.75f, (*itr)->GetVictim()))) + continue; + + if (!unit && (*itr)->GetVictim() && (*itr)->GetDistance((*itr)->GetVictim()) < dist * 0.334f) + { + unit = *itr; + continue; + } + if (!unit) + { + float destDist = src->GetDistance((*itr)->GetPositionX(), (*itr)->GetPositionY(), (*itr)->GetPositionZ()); + if (destDist < mydist) + { + mydist = destDist; + unit = *itr; + } + } + if (unit) + { + uint8 count = 0; + for (std::list::const_iterator it = unitList.begin(); it != unitList.end(); ++it) + { + if (*it != unit && (*it)->GetDistance2d(unit->GetPositionX(), unit->GetPositionY()) < 5.f) + { + if (++count > 2) + { + if (src->GetDistance(*it) < src->GetDistance(unit) && unit->HasInArc(float(M_PI)/2, src)) + unit = *it; + break; + } + } + } + if (count > 2) + break; + + unit = nullptr; + } + } + + return unit; +} +// Finds secondary target for spells like Cleave, Swipe, etc. +Unit* bot_ai::FindSplashTarget(float dist, Unit* To, float splashdist) const +{ + if (!To) + To = me->GetVictim(); + if (!To) + return nullptr; + + if (me->GetDistance(To) > dist) + return nullptr; + + Unit* unit = nullptr; + + SecondEnemyCheck check(me, dist, splashdist, To, this); + Trinity::UnitSearcher searcher(me, unit, check); + Cell::VisitAllObjects(me, searcher, dist); + //me->VisitNearbyObject(dist, searcher); + + return unit; +} +// Finds secondary target for AoE spells like Mind Sear (not damaging primary target) +Unit* bot_ai::FindSplashTarget(float dist, Unit* To, float splashdist, uint8 minTargets) const +{ + if (!To || minTargets < 1) + return nullptr; + + if (me->GetDistance(To) > dist) + return nullptr; + + std::list unitList; + + SecondEnemyCheck check(me, dist, splashdist, To, this); + Trinity::UnitListSearcher searcher(me, unitList, check); + Cell::VisitAllObjects(me, searcher, dist); + //me->VisitNearbyObject(dist, searcher); + + if (uint8(unitList.size()) < minTargets) + return nullptr; + if (unitList.size() == 1) + return *unitList.begin(); + + return Trinity::Containers::SelectRandomContainerElement(unitList); +} +//Finds target for hunter's Tranquilizing Shot (has dispellable magic or enrage effect) +Unit* bot_ai::FindTranquilTarget(float mindist, float maxdist) const +{ + Unit* unit = nullptr; + + TranquilTargetCheck check(me, mindist, maxdist, this); + Trinity::UnitSearcher searcher(me, unit, check); + Cell::VisitAllObjects(me, searcher, maxdist); + //me->VisitNearbyObject(maxdist, searcher); + + return unit; +} +//Find target to cast taunt on +//In case of paladin's Righetoous Defense returns IsInBotParty() unit +Unit* bot_ai::FindDistantTauntTarget(float maxdist, bool ally) const +{ + std::list unitList; + + FarTauntUnitCheck check(me, maxdist, ally, this); + Trinity::UnitListSearcher searcher(me, unitList, check); + Cell::VisitAllObjects(me, searcher, maxdist); + //me->VisitNearbyObject(maxdist, searcher); + + if (unitList.empty()) + return nullptr; + + Unit* unit = unitList.size() == 1 ? *unitList.begin() : Trinity::Containers::SelectRandomContainerElement(unitList); + return ally ? unit->GetVictim() : unit; +} +//Finds target for Warlock's Mana Drain +//Returns nearby CCed unit with most mana +Unit* bot_ai::FindDrainTarget(float maxdist) const +{ + Unit* unit = nullptr; + + ManaDrainUnitCheck check(me, maxdist, this); + Trinity::UnitLastSearcher searcher(me, unit, check); + Cell::VisitAllObjects(me, searcher, maxdist); + //me->VisitNearbyObject(maxdist, searcher); + + return unit; +} +//Finds all targets within given range +//used for finding targets for spells which need reasonable amount of targets (ex. Death Knight AOE spells) +//CCoption:= mask +//1 - not CCed +//2 - has no periodic damage auras (can be safely CCed) +void bot_ai::GetNearbyTargetsList(std::list &targets, float maxdist, uint8 CCoption, WorldObject const* source) const +{ + if (!source) + source = me; + + NearbyHostileUnitCheck check(me, maxdist, this, CCoption, source); + Trinity::UnitListSearcher searcher(me, targets, check); + Cell::VisitAllObjects(me, searcher, maxdist); + //me->VisitNearbyObject(maxdist, searcher); +} +//Find all targets within given range in cone in front of caster; angle is PI/2 (TC confirmed) +//used by mage Dragon's Breath and Cone of Cold spells +//also Swipe (Bear) and Swipe (Cat) +void bot_ai::GetNearbyTargetsInConeList(std::list &targets, float maxdist) const +{ + NearbyHostileUnitInConeCheck check(me, maxdist, this); + Trinity::UnitListSearcher searcher(me, targets, check); + Cell::VisitAllObjects(me, searcher, maxdist); + //me->VisitNearbyObject(maxdist, searcher); +} +//Finds all friendly targets within given range +//used for finding targets to heal/buff for uncontrolled bots +void bot_ai::GetNearbyFriendlyTargetsList(std::list &targets, float maxdist) const +{ + NearbyFriendlyUnitCheck check(me, maxdist, this); + Trinity::UnitListSearcher searcher(me, targets, check); + Cell::VisitAllObjects(me, searcher, maxdist); + //me->VisitNearbyObject(maxdist, searcher); +} +////////// +//SPELLMAP +////////// +//Using first-rank spell as source, returns spellId of max rank allowed for given caster +//If you want bot to use this spell through doCast() go InitSpellMap(uint32) instead +uint32 bot_ai::InitSpell(Unit const* caster, uint32 spell) +{ + SpellInfo const* info = sSpellMgr->GetSpellInfo(spell); + if (!info) + { + TC_LOG_ERROR("entities.player", "InitSpell(): No SpellInfo found for spell {}", spell); + return 0; //weird spell with no info, disable it + } + + uint8 lvl = caster->GetLevel(); + if (lvl < info->BaseLevel) //only 1st rank spells check + return 0; //cannot use this spell + + if (SpellInfo const* spInfo = info->GetNextRankSpell()) + { + if (lvl < spInfo->BaseLevel) + return spell; //cannot use next rank, use this one + else + return InitSpell(caster, spInfo->Id); //can use next rank, forward check + } + + return spell; //max rank, use this +} +//Using first-rank spell as source, puts spell of max rank allowed for given caster in spellmap +void bot_ai::InitSpellMap(uint32 basespell, bool forceadd, bool forwardRank) +{ + SpellInfo const* info = sSpellMgr->GetSpellInfo(basespell); + if (!info) + { + TC_LOG_ERROR("entities.player", "bot_ai::InitSpellMap(): No SpellInfo found for base spell {}", basespell); + return; //invalid spell id + } + info = info->TryGetSpellInfoOverride(me); + + uint8 lvl = me->GetLevel(); + uint32 spellId = forceadd ? basespell : 0; + + while (info != nullptr && forwardRank && (forceadd || lvl >= info->BaseLevel)) + { + spellId = info->Id; //can use this spell + info = info->GetNextRankSpell(); //check next rank + } + + BotSpell* newSpell = _spells[basespell]; + if (!newSpell) + { + newSpell = new BotSpell(); + _spells[basespell] = newSpell; + } + + newSpell->spellId = spellId; + + NpcBotData const* npcBotData = BotDataMgr::SelectNpcBotData(me->GetEntry()); + if (npcBotData && npcBotData->disabled_spells.find(basespell) != npcBotData->disabled_spells.end()) + { + newSpell->enabled = false; + //TC_LOG_ERROR("entities.player", "bot_ai::InitSpellMap(): {} ({} -> {}) is disabled for {}!", + // sSpellMgr->GetSpellInfo(basespell)->SpellName[0], basespell, spellId, me->GetName()); + } +} +//Using first-rank spell as source, return true if spell is inited +bool bot_ai::HasSpell(uint32 basespell) const +{ + BotSpellMap::const_iterator itr = _spells.find(basespell); + return itr != _spells.end() && (itr->second->spellId != 0); +} +//Using spell name as source, return first-rank spell if spell is inited +uint32 bot_ai::GetBaseSpell(std::string_view spell_name, LocaleConstant locale) const +{ + uint32 basespell = 0; + std::wstring wname; + if (Utf8toWStr(spell_name, wname)) + { + wstrToLower(wname); + for (BotSpellMap::const_iterator itr = _spells.begin(); itr != _spells.end(); ++itr) + { + //we ignore enabled state since this is exactly what we want + if (itr->second->spellId == 0) //not init'ed + continue; + spell_name = sSpellMgr->GetSpellInfo(itr->first)->SpellName[locale]; + std::wstring wcname; + if (!Utf8toWStr(spell_name, wcname)) + continue; + wstrToLower(wcname); + if (wcname == wname) + { + basespell = itr->first; + break; + } + } + } + + return basespell; +} +//Using first-rank spell as source, return current spell id if inited and enabled +uint32 bot_ai::GetSpell(uint32 basespell) const +{ + BotSpellMap::const_iterator itr = _spells.find(basespell); + return itr != _spells.end() && (itr->second->enabled == true || IAmFree()) ? itr->second->spellId : 0; +} +//Using first-rank spell as source, returns cooldown on current spell +uint32 bot_ai::GetSpellCooldown(uint32 basespell) const +{ + BotSpellMap::const_iterator itr = _spells.find(basespell); + return itr != _spells.end() ? itr->second->cooldown : 0; +} +bool bot_ai::IsSpellReady(uint32 basespell, uint32 diff, bool checkGCD) const +{ + if (checkGCD && GC_Timer > diff) + return false; + + BotSpellMap::const_iterator itr = _spells.find(basespell); + return itr == _spells.end() ? true : + ((itr->second->enabled == true || IAmFree() || IsLastOrder(BOT_ORDER_SPELLCAST, basespell)) && + itr->second->spellId != 0 && itr->second->cooldown <= diff); +} +//Using first-rank spell as source, sets cooldown for current spell +void bot_ai::SetSpellCooldown(uint32 basespell, uint32 msCooldown) +{ + //if (!msCooldown) + // return; + + BotSpellMap::const_iterator itr = _spells.find(basespell); + if (itr != _spells.end()) + { + itr->second->cooldown = msCooldown; + return; + } + //else if (!msCooldown) + // return; + + InitSpellMap(basespell, true, false); + SetSpellCooldown(basespell, msCooldown); +} +//Using first-rank spell as source, sets cooldown for spells of that category +void bot_ai::SetSpellCategoryCooldown(SpellInfo const* spellInfo, uint32 msCooldown) +{ + if (!msCooldown) + return; + + uint32 category = spellInfo->GetCategory(); + if (!category) + category = spellInfo->StartRecoveryCategory; + if (!category) + return; + + SpellInfo const* info; + for (BotSpellMap::const_iterator itr = _spells.begin(); itr != _spells.end(); ++itr) + { + //skip spell which has triggered this category cooldown + if (itr->first == spellInfo->Id && itr->second->cooldown >= msCooldown) + continue; + + info = sSpellMgr->GetSpellInfo(itr->second->spellId); + info = info ? info->TryGetSpellInfoOverride(me) : info; + if (info && itr->first == spellInfo->Id && info->GetCategory() != category && info->StartRecoveryCategory != category) + { + //if (itr->first != 7814) // Lash of Pain + { + TC_LOG_ERROR("scripts", "Warning: SetSpellCategoryCooldown: {} has baseId {} but category {}, not {}!", + info->Id, itr->first, info->GetCategory(), category); + } + } + if (info && (info->GetCategory() == category || info->StartRecoveryCategory == category || itr->first == spellInfo->Id) && itr->second->cooldown < msCooldown) + itr->second->cooldown = msCooldown; + } +} +//Handles spell cooldowns for spell with IsCooldownStartedOnEvent() == true +void bot_ai::ReleaseSpellCooldown(uint32 basespell) +{ + SpellInfo const* baseInfo = sSpellMgr->GetSpellInfo(basespell); + + baseInfo = baseInfo->TryGetSpellInfoOverride(me); + + if (!baseInfo->IsCooldownStartedOnEvent()) + { + TC_LOG_ERROR("spells", "bot_ai::ReleaseSpellCooldown is called for wrong spell {}!", basespell); + return; + } + + uint32 rec = baseInfo->RecoveryTime; + uint32 catrec = baseInfo->CategoryRecoveryTime; + + ApplyBotSpellCooldownMods(baseInfo, rec); + ApplyBotSpellCategoryCooldownMods(baseInfo, catrec); + + SetSpellCooldown(baseInfo->Id, rec > 0 ? rec : 0); + SetSpellCategoryCooldown(baseInfo, catrec > 0 && !(baseInfo->AttributesEx6 & SPELL_ATTR6_IGNORE_CATEGORY_COOLDOWN_MODS) ? catrec : 0); +} +//Using first-rank spell as source, disables certain spell for this bot +void bot_ai::RemoveSpell(uint32 basespell) +{ + BotSpell* newSpell; + BotSpellMap::iterator itr = _spells.find(basespell); + if (itr == _spells.end()) + { + newSpell = new BotSpell(); + _spells[basespell] = newSpell; + } + else + newSpell = itr->second; + + newSpell->spellId = 0; + newSpell->cooldown = 0; +} +// +//void bot_ai::RemoveAllSpells() +//{ +// for (BotSpellMap::const_iterator itr = _spells.begin(); itr != _spells.end(); ++itr) +// itr->second->spellId = 0; +//} +void bot_ai::EnableAllSpells(bool save) +{ + if (save) + { + NpcBotData* npcBotData = const_cast(BotDataMgr::SelectNpcBotData(me->GetEntry())); + npcBotData->disabled_spells.clear(); + _saveDisabledSpells = true; + } + + for (BotSpellMap::const_iterator itr = _spells.begin(); itr != _spells.end(); ++itr) + if (itr->second->enabled == false) + itr->second->enabled = true; +} +//See CommonTimers(uint32) +void bot_ai::SpellTimers(uint32 diff) +{ + // spell must be initialized!!! + for (BotSpellMap::const_iterator itr = _spells.begin(); itr != _spells.end(); ++itr) + { + if (itr->second->cooldown >= diff) + itr->second->cooldown -= diff; + else if (itr->second->cooldown > 0) + itr->second->cooldown = 0; + } +} +uint32 bot_ai::RaceSpellForClass(uint8 myrace, uint8 myclass) +{ + switch (myrace) + { + case RACE_ORC: + switch (myclass) + { + case BOT_CLASS_WARLOCK: + return RACIAL_BLOOD_FURY_WARLOCK; + case BOT_CLASS_SHAMAN: + return RACIAL_BLOOD_FURY_SHAMAN; + default: + return RACIAL_BLOOD_FURY_OTHERS; + } + break; + case RACE_BLOODELF: + switch (myclass) + { + case BOT_CLASS_DEATH_KNIGHT: + return RACIAL_ARCANE_TORRENT_DEATHKNIGHT; + case BOT_CLASS_ROGUE: + return RACIAL_ARCANE_TORRENT_ROGUE; + default: + return RACIAL_ARCANE_TORRENT_OTHERS; + } + break; + case RACE_DRAENEI: + switch (myclass) + { + case BOT_CLASS_WARRIOR: + return RACIAL_GIFT_OF_NAARU_WARRIOR; + case BOT_CLASS_PALADIN: + return RACIAL_GIFT_OF_NAARU_PALADIN; + case BOT_CLASS_HUNTER: + return RACIAL_GIFT_OF_NAARU_HUNTER; + case BOT_CLASS_PRIEST: + return RACIAL_GIFT_OF_NAARU_PRIEST; + case BOT_CLASS_DEATH_KNIGHT: + return RACIAL_GIFT_OF_NAARU_DEATHKNIGHT; + case BOT_CLASS_SHAMAN: + return RACIAL_GIFT_OF_NAARU_SHAMAN; + case BOT_CLASS_MAGE: + return RACIAL_GIFT_OF_NAARU_MAGE; + default: + TC_LOG_ERROR("entities.player", "RaceSpellForClass(): unknows race:class combo {}, {}", uint32(myrace), uint32(myclass)); + return 0; + } + break; + default: + TC_LOG_ERROR("entities.player", "RaceSpellForClass(): unknows race:class combo {}, {}", uint32(myrace), uint32(myclass)); + return 0; + } +} +//Health magement for minions +//Including health calcs, set +void bot_ai::_OnHealthUpdate() const +{ + uint8 myclass = _botclass; + uint8 mylevel = master->GetLevel(); + if (myclass == BOT_CLASS_DRUID && GetBotStance() != BOT_STANCE_NONE) + myclass = GetBotStance(); + //TC_LOG_ERROR("entities.player", "_OnHealthUpdate(): updating bot {}", me->GetName()); + bool fullhp = me->GetHealth() == me->GetMaxHealth(); + float pct = fullhp ? 100.f : me->GetHealthPct(); // needs for regeneration + uint32 m_basehp = uint32(_classinfo->basehealth * (BotMgr::IsWanderingWorldBot(me) ? BotMgr::GetBotWandererHPMod() : BotMgr::GetBotHPMod())); + //TC_LOG_ERROR("entities.player", "class base health: {}", m_basehp); + me->SetCreateHealth(m_basehp); + + float stamValue = _getTotalBotStat(BOT_STAT_MOD_STAMINA); + + stamValue -= std::min(me->GetCreateStat(STAT_STAMINA), 20.f); //not a mistake + stamValue = std::max(stamValue, 0.f); + + //TC_LOG_ERROR("entities.player", "bot's stats to health add: Stamina ({}), value: {}", stamValue, stamValue * 10.f); + float hp_add = stamValue * 10.f + 20; //20 is not a mistake; + //hp_add += IAmFree() ? mylevel * 375.f : 0; //+30000/+0 hp at 80 + hp_add += _getTotalBotStat(BOT_STAT_MOD_HEALTH); + //TC_LOG_ERROR("entities.player", "health to add after stam mod: {}", hp_add); + uint32 m_totalhp = m_basehp + int32(hp_add * (BotMgr::IsWanderingWorldBot(me) ? BotMgr::GetBotWandererHPMod() : BotMgr::GetBotHPMod())); + //TC_LOG_ERROR("entities.player", "total base health: {}", m_totalhp); + + //hp bonuses + uint8 bonuspct = 0; + //Endurance Training + if (_botclass == BOT_CLASS_HUNTER && mylevel >= 10) + bonuspct += 5; + //Fel Vitality + if (_botclass == BOT_CLASS_WARLOCK && mylevel >= 15) + bonuspct += 3; + //Sphynx bonus (some equip slots unavailable) + if (_botclass == BOT_CLASS_SPHYNX) + bonuspct += 50; + //Dreadlord's / Crypt Lord's vitality + if (_botclass == BOT_CLASS_DREADLORD || _botclass == BOT_CLASS_CRYPT_LORD) + bonuspct += 20; + if (bonuspct) + m_totalhp = (m_totalhp * (100 + bonuspct)) / 100; + + //m_totalhp = float(uint32(m_totalhp) - (uint32(m_totalhp) % 10)); + me->SetStatFlatModifier(UNIT_MOD_HEALTH, BASE_VALUE, float(m_totalhp)); //replaces base hp at max lvl + me->UpdateMaxHealth(); //will use our values we just set (update base health and buffs) + //TC_LOG_ERROR("entities.player", "overall hp: {}", me->GetMaxHealth()); + me->SetHealth(fullhp ? me->GetMaxHealth() : uint32(0.5f + float(me->GetMaxHealth()) * pct / 100.f)); //restore pct +} +//Mana management for minions +//Including calcs and set +void bot_ai::_OnManaUpdate() const +{ + if (me->GetMaxPower(POWER_MANA) <= 1) + return; + + uint8 myclass = _botclass; + uint8 mylevel = master->GetLevel(); + if (myclass == BOT_CLASS_DRUID && GetBotStance() != BOT_STANCE_NONE) + myclass = GetBotStance(); + + //TC_LOG_ERROR("entities.player", "_OnManaUpdate(): updating bot {}", me->GetName()); + bool fullmana = me->GetPower(POWER_MANA) == me->GetMaxPower(POWER_MANA); + float pct = fullmana ? 100.f : (float(me->GetPower(POWER_MANA)) * 100.f) / float(me->GetMaxPower(POWER_MANA)); + float m_basemana = _classinfo->basemana; + if (_botclass == BOT_CLASS_BM) + m_basemana = float(BASE_MANA_1_BM) + float(BASE_MANA_10_BM - BASE_MANA_1_BM) * (mylevel/81.f); + if (_botclass == BOT_CLASS_SPHYNX) + m_basemana = BASE_MANA_SPHYNX; + if (_botclass == BOT_CLASS_ARCHMAGE) + m_basemana = float(BASE_MANA_1_ARCHMAGE) + float(BASE_MANA_10_ARCHMAGE - BASE_MANA_1_ARCHMAGE) * ((mylevel - 20)/81.f); + if (_botclass == BOT_CLASS_DREADLORD) + m_basemana = float(BASE_MANA_1_DREADLORD) + float(BASE_MANA_10_DREADLORD - BASE_MANA_1_DREADLORD) * ((mylevel - 60)/83.f); + if (_botclass == BOT_CLASS_SPELLBREAKER) + m_basemana = BASE_MANA_SPELLBREAKER; + if (_botclass == BOT_CLASS_DARK_RANGER) + m_basemana = float(BASE_MANA_1_DARK_RANGER) + float(BASE_MANA_10_DARK_RANGER - BASE_MANA_1_DARK_RANGER) * ((mylevel - 40)/82.f); + if (_botclass == BOT_CLASS_NECROMANCER) + m_basemana = BASE_MANA_NECROMANCER; + if (_botclass == BOT_CLASS_SEA_WITCH) + m_basemana = float(BASE_MANA_1_SEA_WITCH) + float(BASE_MANA_10_SEA_WITCH - BASE_MANA_1_SEA_WITCH) * (mylevel/83.f); + if (_botclass == BOT_CLASS_CRYPT_LORD) + m_basemana = float(BASE_MANA_1_CRYPT_LORD) + float(BASE_MANA_10_CRYPT_LORD - BASE_MANA_1_CRYPT_LORD) * (mylevel/83.f); + //TC_LOG_ERROR("entities.player", "classinfo base mana = {}", m_basemana); + + me->SetCreateMana(uint32(m_basemana)); + + float intValue = _getTotalBotStat(BOT_STAT_MOD_INTELLECT); + + intValue -= std::min(me->GetCreateStat(STAT_INTELLECT), 20.f); //not a mistake + intValue = std::max(intValue, 0.f); + + float intMult = _botclass < BOT_CLASS_EX_START ? 15.f : IsHeroExClass(_botclass) ? 5.f : 1.5f; + + m_basemana = intValue * intMult + 20.f; //20.f is not a mistake + //m_basemana += IAmFree() ? mylevel * 50.f : 0; //+4000/+0 mana at 80 + m_basemana += _getTotalBotStat(BOT_STAT_MOD_MANA); + + //mana bonuses + uint8 bonuspct = 0; + //Fel Vitality + if (_botclass == BOT_CLASS_WARLOCK && mylevel >= 15) + bonuspct += 3; + if (bonuspct) + m_basemana = (m_basemana * (100 + bonuspct)) / 100; + + //m_basemana = float(uint32(m_basemana) - (uint32(m_basemana) % 5)); + me->SetStatFlatModifier(UNIT_MOD_MANA, BASE_VALUE, m_basemana); + me->UpdateMaxPower(POWER_MANA); + me->SetPower(POWER_MANA, fullmana ? me->GetMaxPower(POWER_MANA) : + uint32(0.5f + float(me->GetMaxPower(POWER_MANA)) * pct / 100.f)); //restore pct + + _OnManaRegenUpdate(); +} +//Mana regen for minions +void bot_ai::_OnManaRegenUpdate() const +{ + //regen_normal + uint8 mylevel = me->GetLevel(); + float value = (IAmFree() && _botclass != BOT_CLASS_SPHYNX) ? mylevel/2 : 0; //200/0 mp5 at 80 + + float power_regen_mp5; + int32 modManaRegenInterrupt; + if (_botclass < BOT_CLASS_EX_START) + { + // Mana regen from spirit and intellect + float spiregen = 0.001f; + if (GtRegenMPPerSptEntry const* moreRatio = sGtRegenMPPerSptStore.LookupEntry((_botclass-1)*GT_MAX_LEVEL + mylevel-1)) + spiregen = moreRatio->Data * _getTotalBotStat(BOT_STAT_MOD_SPIRIT); + + // PCT bonus from SPELL_AURA_MOD_POWER_REGEN_PERCENT aura on spirit base regen + value += sqrt(_getTotalBotStat(BOT_STAT_MOD_INTELLECT)) * spiregen * me->GetTotalAuraMultiplierByMiscValue(SPELL_AURA_MOD_POWER_REGEN_PERCENT, POWER_MANA); + // regen from SPELL_AURA_MOD_POWER_REGEN aura (per second) + power_regen_mp5 = 0.2f * (me->GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_POWER_REGEN, POWER_MANA) + _getTotalBotStat(BOT_STAT_MOD_MANA_REGENERATION)); + + // bonus from SPELL_AURA_MOD_MANA_REGEN_FROM_STAT aura + Unit::AuraEffectList const& regenAura = me->GetAuraEffectsByType(SPELL_AURA_MOD_MANA_REGEN_FROM_STAT); + for (Unit::AuraEffectList::const_iterator i = regenAura.begin(); i != regenAura.end(); ++i) + power_regen_mp5 += me->GetStat(Stats((*i)->GetMiscValue())) * (*i)->GetAmount() * 0.002f; //per second + + //bot also receive bonus from SPELL_AURA_MOD_POWER_REGEN_PERCENT for mp5 regen + power_regen_mp5 *= me->GetTotalAuraMultiplierByMiscValue(SPELL_AURA_MOD_POWER_REGEN_PERCENT, POWER_MANA); + + // Set regen rate in cast state apply only on spirit based regen + modManaRegenInterrupt = std::min(100, me->GetTotalAuraModifier(SPELL_AURA_MOD_MANA_REGEN_INTERRUPT)); + } + else + { + modManaRegenInterrupt = 100; + power_regen_mp5 = 0.0f; + + if (IsHeroExClass(_botclass)) + { + float basemana; + if (_botclass == BOT_CLASS_BM) + basemana = BASE_MANA_1_BM; + else if (_botclass == BOT_CLASS_ARCHMAGE) + basemana = BASE_MANA_1_ARCHMAGE; + else if (_botclass == BOT_CLASS_DREADLORD) + basemana = BASE_MANA_1_DREADLORD; + else if (_botclass == BOT_CLASS_DARK_RANGER) + basemana = BASE_MANA_1_DARK_RANGER; + else if (_botclass == BOT_CLASS_SEA_WITCH) + basemana = BASE_MANA_1_SEA_WITCH; + else if (_botclass == BOT_CLASS_CRYPT_LORD) + basemana = BASE_MANA_1_CRYPT_LORD; + else + basemana = 0.f; + + value = basemana * 0.0087f + 0.08f * GetTotalBotStat(BOT_STAT_MOD_INTELLECT); + value += 0.2f * (me->GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_POWER_REGEN, POWER_MANA) + _getTotalBotStat(BOT_STAT_MOD_MANA_REGENERATION)); + value *= me->GetTotalAuraMultiplierByMiscValue(SPELL_AURA_MOD_POWER_REGEN_PERCENT, POWER_MANA); + + //if (_botclass == BOT_CLASS_SEA_WITCH && me->HasAuraType(SPELL_AURA_MANA_SHIELD)) + // modManaRegenInterrupt *= 0.25f; + } + else if (_botclass == BOT_CLASS_SPHYNX) + { + value = CalculatePct(me->GetCreateMana(), 2); //-2% basemana/sec + } + else if (_botclass == BOT_CLASS_SPELLBREAKER) + { + value = 4.f; //base 0.8/sec + value += 0.2f * (me->GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_POWER_REGEN, POWER_MANA) + _getTotalBotStat(BOT_STAT_MOD_MANA_REGENERATION)); + value *= me->GetTotalAuraMultiplierByMiscValue(SPELL_AURA_MOD_POWER_REGEN_PERCENT, POWER_MANA); + } + else if (_botclass == BOT_CLASS_NECROMANCER) + { + value = 7.5f; //base 1.5/sec + value += 0.2f * (me->GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_POWER_REGEN, POWER_MANA) + _getTotalBotStat(BOT_STAT_MOD_MANA_REGENERATION)); + value *= me->GetTotalAuraMultiplierByMiscValue(SPELL_AURA_MOD_POWER_REGEN_PERCENT, POWER_MANA); + } + else + value = 0; + } + + //Unrelenting Storm, Dreamstate: 12% of intellect as mana regen always (divided by 5) + if ((_botclass == BOT_CLASS_SHAMAN && GetSpec() == BOT_SPEC_SHAMAN_ELEMENTAL) || + (_botclass == BOT_CLASS_DRUID && GetSpec() == BOT_SPEC_DRUID_BALANCE)) + power_regen_mp5 += 0.024f * _getTotalBotStat(BOT_STAT_MOD_INTELLECT); + + me->SetStatFloatValue(UNIT_FIELD_POWER_REGEN_INTERRUPTED_FLAT_MODIFIER, power_regen_mp5 + CalculatePct(value, modManaRegenInterrupt)); + me->SetStatFloatValue(UNIT_FIELD_POWER_REGEN_FLAT_MODIFIER, power_regen_mp5 + value); +} + +void bot_ai::_UpdateWMOArea() +{ + _wmoAreaUpdateTimer = urand(7000, 9000); + + uint32 mogpFlags; + int32 adtId, rootId, groupId; + me->GetMap()->GetAreaInfo(me->GetPhaseMask(), me->GetPositionX(), me->GetPositionY(), me->GetPositionZ(), + mogpFlags, adtId, rootId, groupId); + + if (WMOAreaTableEntry const* wmoEntry = GetWMOAreaTableEntryByTripple(rootId, adtId, groupId)) + { + _lastWMOAreaId = wmoEntry->ID; + //TC_LOG_ERROR("scripts", "_UpdateWMOArea(): bot {}: area {}, wmoarea {}", me->GetName(), _lastAreaId, _lastWMOAreaId); + } +} + +void bot_ai::_OnZoneUpdate(uint32 zoneId, uint32 areaId) +{ + ASSERT(me->IsInWorld()); + + _lastZoneId = zoneId; + + SetGroupUpdateFlag(GROUP_UPDATE_FULL); + + _OnAreaUpdate(areaId); + + if (!IAmFree()) + { + SpellAreaForAreaMapBounds saBounds = sSpellMgr->GetSpellAreaForAreaMapBounds(zoneId); + for (SpellAreaForAreaMap::const_iterator itr = saBounds.first; itr != saBounds.second; ++itr) + { + if (itr->second->autocast && itr->second->IsFitToRequirements(master, zoneId, 0)) + { + if (!me->HasAura(itr->second->spellId)) + me->CastSpell(me, itr->second->spellId, true); + if (botPet && !botPet->HasAura(itr->second->spellId)) + botPet->CastSpell(botPet, itr->second->spellId, true); + } + } + } +} + +void bot_ai::_OnAreaUpdate(uint32 areaId) +{ + ASSERT(me->IsInWorld()); + + _lastAreaId = areaId; + + if (!IAmFree()) + { + Unit::AuraMap const& ownerAuras = me->GetOwnedAuras(); + for (Unit::AuraMap::const_iterator iter = ownerAuras.cbegin(); iter != ownerAuras.cend(); ++iter) + { + if (iter->second->GetSpellInfo()->HasAura(SPELL_AURA_MOUNTED)) + continue; + + if (iter->second->GetSpellInfo()->CheckLocation(me->GetMapId(), _lastZoneId, areaId, master, false) != SPELL_CAST_OK) + { + //me->RemoveOwnedAura(iter); + //we assume 1 aura at a time at most for area (once per 1.5 sec) + uint32 spellId = iter->first; + me->RemoveAurasDueToSpell(spellId); + if (botPet) + botPet->RemoveAurasDueToSpell(spellId); + break; + } + } + + SpellAreaForAreaMapBounds saBounds = sSpellMgr->GetSpellAreaForAreaMapBounds(areaId); + for (SpellAreaForAreaMap::const_iterator itr = saBounds.first; itr != saBounds.second; ++itr) + { + if (itr->second->autocast && itr->second->IsFitToRequirements(master, _lastZoneId, 0)) + { + if (!me->HasAura(itr->second->spellId)) + me->CastSpell(me, itr->second->spellId, true); + if (botPet && !botPet->HasAura(itr->second->spellId)) + botPet->CastSpell(botPet, itr->second->spellId, true); + } + } + + for (uint8 slot = BOT_SLOT_MAINHAND; slot <= BOT_SLOT_RANGED; ++slot) + { + if (Item const* item = _equips[slot]) + if (item->IsLimitedToAnotherMapOrZone(me->GetMapId(), areaId)) + if (_resetEquipment(slot, ObjectGuid::Empty)) + continue; + } + } + + AreaTableEntry const* area = sAreaTableStore.LookupEntry(areaId); + if (area && area->IsSanctuary()) + { + if (!me->HasByteFlag(UNIT_FIELD_BYTES_2, 1, UNIT_BYTE2_FLAG_SANCTUARY)) + { + me->SetByteFlag(UNIT_FIELD_BYTES_2, 1, UNIT_BYTE2_FLAG_SANCTUARY); + me->CombatStop(); + if (botPet) + botPet->CombatStop(); + } + } + else if (me->HasByteFlag(UNIT_FIELD_BYTES_2, 1, UNIT_BYTE2_FLAG_SANCTUARY)) + me->RemoveByteFlag(UNIT_FIELD_BYTES_2, 1, UNIT_BYTE2_FLAG_SANCTUARY); +} + +bool bot_ai::IsInHeroicOrRaid() const +{ + return me->FindMap() && (me->GetMap()->IsHeroic() || me->GetMap()->IsRaid()); +} + +//SpellHit()... OnSpellHit() +void bot_ai::OnSpellHit(Unit* caster, SpellInfo const* spell) +{ + //uint32 const spellId = spell->Id; + + if (!spell->IsPositive() && spell->GetMaxDuration() > 1000 && caster->IsControlledByPlayer() && + _botclass >= BOT_CLASS_EX_START) + { + //bots of W3 classes will not be easily CCed + if (spell->HasAura(SPELL_AURA_MOD_STUN) || spell->HasAura(SPELL_AURA_MOD_CONFUSE) || + spell->HasAura(SPELL_AURA_MOD_PACIFY) || spell->HasAura(SPELL_AURA_MOD_ROOT)) + { + if (Aura* cont = me->GetAura(spell->Id, caster->GetGUID())) + { + if (AuraApplication const* aurApp = cont->GetApplicationOfTarget(me->GetGUID())) + { + if (!aurApp->IsPositive()) + { + int32 dur = std::max(cont->GetMaxDuration() / 3, 1000); + cont->SetDuration(dur); + cont->SetMaxDuration(dur); + } + } + } + } + } + + if (!HasBotCommandState(BOT_COMMAND_FULLSTOP | BOT_COMMAND_INACTION)) + { + if (spell->HasAura(SPELL_AURA_MOD_TAUNT) || spell->HasEffect(SPELL_EFFECT_ATTACK_ME)) + if (caster && me->Attack(caster, !HasRole(BOT_ROLE_RANGED))) + {}//me->GetMotionMaster()->MoveChase(caster); + } + + if (spell->GetSpellSpecific() == SPELL_SPECIFIC_DRINK) + { + feast_mana = true; + UpdateMana(); + regenTimer = 0; + } + else if (spell->GetSpellSpecific() == SPELL_SPECIFIC_FOOD) + { + feast_health = true; + regenTimer = 0; + } + + switch (spell->Id) + { + case WANDERER_HEARTHSTONE: + if (IsWanderer()) + { + Map* targetMap = (me->GetMap()->GetEntry()->IsContinent() && _travel_node_cur->GetMapId() != me->GetMap()->GetId()) ? + sMapMgr->CreateBaseMap(_travel_node_cur->GetMapId()) : me->GetMap(); + BotMgr::TeleportBot(me, targetMap, _travel_node_cur, true); + _evadeCount = 0; + } + return; + default: + break; + } + + for (uint8 i = 0; i != MAX_SPELL_EFFECTS; ++i) + { + uint32 const auraname = spell->_effects[i].ApplyAuraName; + //remove pet on mount + if (auraname == SPELL_AURA_MOUNTED) + { + //TC_LOG_ERROR("entities.unit", "OnSpellHit: mount on {}", me->GetName()); + if (master->HasAuraType(SPELL_AURA_MOD_INCREASE_VEHICLE_FLIGHT_SPEED) || + master->HasAuraType(SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED)) + { + //TC_LOG_ERROR("entities.unit", "OnSpellHit: modding flight speed"); + UnsummonAll(false); + const_cast(me->GetCreatureTemplate())->Movement.Flight = CreatureFlightMovementType::DisableGravity; + me->SetCanFly(true); + me->SetDisableGravity(true); + if (Aura* mount = me->GetAura(spell->Id)) + { + //TC_LOG_ERROR("entities.unit", "OnSpellHit: found aura"); + for (uint8 j = 0; j != MAX_SPELL_EFFECTS; ++j) + { + if (spell->_effects[j].ApplyAuraName != SPELL_AURA_MOD_INCREASE_VEHICLE_FLIGHT_SPEED && + spell->_effects[j].ApplyAuraName != SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED && + spell->_effects[j].ApplyAuraName != SPELL_AURA_MOD_INCREASE_MOUNTED_SPEED) + continue; + if (AuraEffect* meff = mount->GetEffect(j)) + { + meff->ChangeAmount(meff->GetAmount() * 3); + } + } + } + //me->SetSpeedRate(MOVE_FLIGHT, master->GetSpeedRate(MOVE_FLIGHT) * 1.37f); + //me->SetSpeedRate(MOVE_RUN, master->GetSpeedRate(MOVE_FLIGHT) * 1.37f); + } + else + me->SetSpeedRate(MOVE_RUN, master->GetSpeedRate(MOVE_RUN) * 1.1f); + } + + //update stats + if (auraname == SPELL_AURA_MOD_STAT || auraname == SPELL_AURA_MOD_PERCENT_STAT || + auraname == SPELL_AURA_MOD_TOTAL_STAT_PERCENTAGE || auraname == SPELL_AURA_MOD_SKILL || + auraname == SPELL_AURA_MOD_ATTACK_POWER || auraname == SPELL_AURA_MOD_ATTACK_POWER_PCT || + auraname == SPELL_AURA_MOD_ATTACK_POWER_OF_STAT_PERCENT || auraname == SPELL_AURA_MOD_ATTACK_POWER_OF_ARMOR || + auraname == SPELL_AURA_MOD_SPELL_DAMAGE_OF_STAT_PERCENT || + auraname == SPELL_AURA_MOD_RATING || auraname == SPELL_AURA_MOD_RATING_FROM_STAT) + shouldUpdateStats = true; + else if (auraname == SPELL_AURA_MOD_INCREASE_HEALTH || + auraname == SPELL_AURA_MOD_INCREASE_HEALTH_2 || + auraname == SPELL_AURA_230 ||//SPELL_AURA_MOD_INCREASE_HEALTH_2 blood pact, commanding shout + auraname == SPELL_AURA_MOD_INCREASE_HEALTH_PERCENT) + UpdateHealth(); + else if (auraname == SPELL_AURA_MOD_INCREASE_ENERGY || auraname == SPELL_AURA_MOD_INCREASE_ENERGY_PERCENT) + UpdateMana(); //Divine Hymn - max mana increase + + uint32 const effect = spell->_effects[i].Effect; + if (effect == SPELL_EFFECT_RESURRECT || effect == SPELL_EFFECT_RESURRECT_NEW || effect == SPELL_EFFECT_SELF_RESURRECT) + { + //resurrect effects are not handled for creatures + if (!me->IsAlive()) + { + uint32 health = 0; + uint32 mana = 0; + int32 damage = spell->_effects[i].BasePoints; + + if (effect == SPELL_EFFECT_RESURRECT_NEW) + { + //Glyph of Rebirth: resurrect with 100% health + if (spell->IsRankOf(sSpellMgr->GetSpellInfo(20484))) + health = me->GetMaxHealth(); + else + health = damage; + mana = spell->_effects[i].MiscValue; + } + else if (damage < 0) + { + health = uint32(-damage); + mana = spell->_effects[i].MiscValue; + } + else + { + health = me->CountPctFromMaxHealth(damage); + if (me->GetMaxPower(POWER_MANA) > 1) + mana = CalculatePct(me->GetMaxPower(POWER_MANA), damage); + } + + BotMgr::ReviveBot(me, caster); + _selfrez_spell_id = 0; + + me->SetHealth(health); + if (me->GetMaxPower(POWER_MANA) > 1) + me->SetPower(POWER_MANA, mana); + } + } + //ravasaur poison (EffectEnchantHeldItem) for mh and oh + if (effect == SPELL_EFFECT_ENCHANT_HELD_ITEM) + { + uint32 enchant_id = spell->_effects[i].MiscValue; + if (!enchant_id) + continue; + + EnchantmentSlot slot = TEMP_ENCHANTMENT_SLOT; + Item* weap = _equips[BOT_SLOT_MAINHAND]; + if (!weap || weap->GetEnchantmentId(slot)) + weap = _equips[BOT_SLOT_OFFHAND]; + if (!weap || weap->GetTemplate()->Class != ITEM_CLASS_WEAPON || weap->GetEnchantmentId(slot)) + continue; + + int32 duration = spell->GetDuration(); + if (!duration) + duration = 10; //10 sec default + + if (!IAmFree()) + master->GetSession()->SendEnchantmentLog(me->GetGUID(), caster->GetGUID(), weap->GetEntry(), enchant_id); + + weap->SetUInt32Value(ITEM_FIELD_ENCHANTMENT_1_1 + slot*MAX_ENCHANTMENT_OFFSET + ENCHANTMENT_ID_OFFSET, enchant_id); + weap->SetUInt32Value(ITEM_FIELD_ENCHANTMENT_1_1 + slot*MAX_ENCHANTMENT_OFFSET + ENCHANTMENT_DURATION_OFFSET, duration * IN_MILLISECONDS); + weap->SetUInt32Value(ITEM_FIELD_ENCHANTMENT_1_1 + slot*MAX_ENCHANTMENT_OFFSET + ENCHANTMENT_CHARGES_OFFSET, 0); + ApplyItemBonuses(weap == _equips[BOT_SLOT_MAINHAND] ? BOT_SLOT_MAINHAND : BOT_SLOT_OFFHAND); + } + } + + //TODO: + if (/*!(spell->AttributesEx & SPELL_ATTR1_NO_THREAT) && + !(spell->AttributesEx3 & SPELL_ATTR3_NO_INITIAL_AGGRO) && !CCed(me) && */ + !me->GetVictim() && + (me->IsHostileTo(caster) || caster->IsHostileTo(me))) + { + //_atHome = false; + if (!me->CanSeeOrDetect(caster)) + { + if (_evadeMode) + me->BotStopMovement(); + } + else if (caster->IsInCombat() || me->IsInCombat()) + this->OwnerAttackedBy(caster); + //if (_evadeMode == true && me->isMoving() && IAmFree()) + } +} +void bot_ai::OnSpellHitTarget(Unit* /*target*/, SpellInfo const* spell) +{ + if (me->GetVehicle()) + { + uint32 spellId = spell->Id; + + //Flame Spike, Revivify + if (spellId == 56091 || spellId == 57090) + { + vehcomboPoints = std::min(vehcomboPoints + 1, 5); + //TC_LOG_ERROR("scripts", "OnBotSpellGo(): veh cp spell {} now cp {}", curInfo->Id, uint32(vehcomboPoints)); + } + //Engulf in Flames, Life Burst, Flame Shield moved to globalupdate + if (spellId == 56092 || spellId == 57143 || spellId == 57108) + { + vehcomboPoints = 0; + //TC_LOG_ERROR("scripts", "OnSpellHitTarget(): veh cp waster {}", curInfo->Id); + } + } +} +//Update delay +//Skip UpdateAI cycles for randomization of bots' reaction and performance adjustments +bool bot_ai::Wait() +{ + if (waitTimer > lastdiff || !master->IsInWorld()) + return true; + + if (IAmFree()) + waitTimer = (me->IsInCombat() || me->GetVictim() || me->GetMap()->IsBattlegroundOrArena()) ? 500 : ((__rand + 100) * 20); + else if (!master->GetMap()->IsRaid()) + waitTimer = std::min(uint32(50 * (master->GetNpcBotsCount() - 1) + __rand), 500); + else + waitTimer = __rand; + + waitTimer += BotMgr::GetBaseUpdateDelay(); + + return false; +} +//Spell Mod Hooks +void bot_ai::ApplyBotDamageMultiplierMelee(uint32& damage, CalcDamageInfo& damageinfo) const +{ + //WHITE ATTACKS damage bonus + damage *= BotMgr::GetBotDamageModByClass(GetBotClass()); + damage *= BotMgr::GetBotDamageModByLevel(me->GetLevel()); + ApplyClassDamageMultiplierMelee(damage, damageinfo); +} +void bot_ai::ApplyBotDamageMultiplierMelee(int32& damage, SpellNonMeleeDamage& damageinfo, SpellInfo const* spellInfo, WeaponAttackType attackType, bool iscrit) const +{ + //MELEE ABILITIES damage bonus (DMG_CLASS != DMG_CLASS_MAGIC) + damage *= BotMgr::GetBotDamageModByClass(GetBotClass()); + damage *= BotMgr::GetBotDamageModByLevel(me->GetLevel()); + ApplyClassDamageMultiplierMeleeSpell(damage, damageinfo, spellInfo, attackType, iscrit); +} +void bot_ai::ApplyBotDamageMultiplierSpell(int32& damage, SpellNonMeleeDamage& damageinfo, SpellInfo const* spellInfo, WeaponAttackType attackType, bool iscrit) const +{ + //DAMAGE SPELLS damage bonus (DMG_CLASS_MAGIC) + damage *= BotMgr::GetBotDamageModByClass(GetBotClass()); + damage *= BotMgr::GetBotDamageModByLevel(me->GetLevel()); + ApplyClassDamageMultiplierSpell(damage, damageinfo, spellInfo, attackType, iscrit); +} +void bot_ai::ApplyBotDamageMultiplierHeal(Unit const* victim, float& heal, SpellInfo const* spellInfo, DamageEffectType damagetype, uint32 stack) const +{ + //HEALING SPELLS amount bonus + ApplyClassDamageMultiplierHeal(victim, heal, spellInfo, damagetype, stack); + heal = (heal * (BotMgr::IsWanderingWorldBot(me) ? BotMgr::GetBotWandererHealingMod() : BotMgr::GetBotHealingMod())); +} +void bot_ai::ApplyBotCritMultiplierAll(Unit const* victim, float& crit_chance, SpellInfo const* spellInfo, SpellSchoolMask schoolMask, WeaponAttackType attackType) const +{ + //ALL SPELLS crit bonus + base + ApplyClassSpellCritMultiplierAll(victim, crit_chance, spellInfo, schoolMask, attackType); + crit_chance += crit; +} +void bot_ai::ApplyBotSpellCostMods(SpellInfo const* spellInfo, int32& cost) const +{ + //ALL SPELLS power cost bonus + ApplyClassSpellCostMods(spellInfo, cost); +} +void bot_ai::ApplyBotSpellCastTimeMods(SpellInfo const* spellInfo, int32& casttime) const +{ + //ALL SPELLS cast time bonus + ApplyClassSpellCastTimeMods(spellInfo, casttime); +} +void bot_ai::ApplyBotSpellCooldownMods(SpellInfo const* spellInfo, uint32& cooldown) const +{ + //ALL SPELLS cooldown bonus + ApplyClassSpellCooldownMods(spellInfo, cooldown); +} +void bot_ai::ApplyBotSpellCategoryCooldownMods(SpellInfo const* spellInfo, uint32& cooldown) const +{ + //ALL SPELLS category cooldown bonus + ApplyClassSpellCategoryCooldownMods(spellInfo, cooldown); +} +void bot_ai::ApplyBotSpellGlobalCooldownMods(SpellInfo const* spellInfo, float& cooldown) const +{ + //ALL SPELLS global cooldown bonus + ApplyClassSpellGlobalCooldownMods(spellInfo, cooldown); +} +void bot_ai::ApplyBotSpellRadiusMods(SpellInfo const* spellInfo, float& radius) const +{ + //ALL SPELLS radius bonus (not range) + ApplyClassSpellRadiusMods(spellInfo, radius); +} +void bot_ai::ApplyBotSpellRangeMods(SpellInfo const* spellInfo, float& maxrange) const +{ + //ALL SPELLS range bonus + ApplyClassSpellRangeMods(spellInfo, maxrange); +} +void bot_ai::ApplyBotSpellMaxTargetsMods(SpellInfo const* spellInfo, uint32& targets) const +{ + //ALL SPELLS max targets bonus + ApplyClassSpellMaxTargetsMods(spellInfo, targets); +} +void bot_ai::ApplyBotSpellChanceOfSuccessMods(SpellInfo const* spellInfo, float& chance) const +{ + //ALL CLASS PROC_TRIGGER_SPELL SPELLS chance of success bonus + ApplyClassSpellChanceOfSuccessMods(spellInfo, chance); +} +void bot_ai::ApplyBotEffectMods(SpellInfo const* spellInfo, uint8 effIndex, float& value) const +{ + //ALL SPELLS SPELLMOD_EFFECT_X bonus + ApplyClassEffectMods(spellInfo, effIndex, value); +} +void bot_ai::ApplyBotThreatMods(SpellInfo const* spellInfo, float& threat) const +{ + //ALL threat mods + ApplyClassThreatMods(spellInfo, threat); +} +void bot_ai::ApplyBotEffectValueMultiplierMods(SpellInfo const* spellInfo, SpellEffIndex effIndex, float& multiplier) const +{ + //ALL SPELLMOD_VALUE_MULTIPLIER mods + ApplyClassEffectValueMultiplierMods(spellInfo, effIndex, multiplier); +} +//Spell Mod Utilities +float bot_ai::CalcSpellMaxRange(uint32 spellId, bool enemy) const +{ + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); + //ASSERT(spellInfo); + spellInfo = spellInfo->TryGetSpellInfoOverride(me); + + float maxRange = spellInfo->GetMaxRange(!enemy); + if (maxRange == 0x0) + return maxRange; + + ApplyClassSpellRangeMods(spellInfo, maxRange); + return maxRange; +} +////////// +//GOSSIP// +////////// +//GossipHello +bool bot_ai::OnGossipHello(Player* player, uint32 /*option*/) +{ + if (!BotMgr::IsNpcBotModEnabled() || !(IsWanderer() ? BotMgr::IsWanderingClassEnabled(_botclass) : BotMgr::IsClassEnabled(_botclass)) || + IsTempBot() || me->IsInCombat() || CCed(me) || IsCasting() || IsDuringTeleport() || + HasBotCommandState(BOT_COMMAND_ISSUED_ORDER | BOT_COMMAND_NOGOSSIP) || + (me->GetVehicle() && me->GetVehicle()->GetBase()->IsInCombat()) || + (!player->IsGameMaster() && IsWanderer())) + { + player->PlayerTalkClass->SendCloseGossip(); + return true; + } + + if (me->isMoving()) + me->BotStopMovement(); + + evadeDelayTimer = std::max(evadeDelayTimer, 10000); + + uint32 gossipTextId; + if (!IAmFree()) + { + if (_botclass == BOT_CLASS_SPHYNX) + gossipTextId = GOSSIP_NORMAL_CUSTOM_SPHYNX; + else if (_botclass == BOT_CLASS_DREADLORD) + gossipTextId = GOSSIP_NORMAL_CUSTOM_DREADLORD; + else if (_botclass == BOT_CLASS_DARK_RANGER) + gossipTextId = GOSSIP_NORMAL_CUSTOM_DARKRANGER; + else if (_botclass == BOT_CLASS_SEA_WITCH) + gossipTextId = GOSSIP_NORMAL_CUSTOM_SEAWITCH; + else if (_botclass == BOT_CLASS_CRYPT_LORD) + gossipTextId = GOSSIP_NORMAL_CUSTOM_CRYPTLORD; + else + gossipTextId = GOSSIP_NORMAL_SERVE_MASTER; + } + else + { + if (_botclass == BOT_CLASS_SPHYNX) + gossipTextId = GOSSIP_GREET_CUSTOM_SPHYNX; + else if (_botclass == BOT_CLASS_DREADLORD) + gossipTextId = GOSSIP_GREET_CUSTOM_DREADLORD; + else if (_botclass == BOT_CLASS_DARK_RANGER) + gossipTextId = GOSSIP_GREET_CUSTOM_DARKRANGER; + else if (_botclass == BOT_CLASS_SEA_WITCH) + gossipTextId = GOSSIP_GREET_CUSTOM_SEAWITCH; + else if (_botclass == BOT_CLASS_CRYPT_LORD) + gossipTextId = GOSSIP_GREET_CUSTOM_CRYPTLORD; + else + gossipTextId = GOSSIP_GREET_NEED_SMTH; + } + + bool menus = false; + + if (player->IsGameMaster()) + { + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_DEBUG), GOSSIP_SENDER_DEBUG, GOSSIP_ACTION_INFO_DEF + 1); + menus = true; + } + + if (player->GetGUID().GetCounter() != _ownerGuid) + { + if (IAmFree() && !IsWanderer()) + { + uint32 cost = BotMgr::GetNpcBotCost(player->GetLevel(), _botclass); + + int8 reason = 0; + if (me->HasAura(BERSERK)) + reason = -1; + if (!reason && _ownerGuid) + reason = 1; + if (!reason && BotDataMgr::GetOwnedBotsCount(player->GetGUID()) >= BotMgr::GetMaxNpcBots(player->GetLevel())) + reason = 2; + if (!reason && !player->HasEnoughMoney(cost)) + reason = 3; + if (!reason && BotMgr::GetMaxClassBots() && BotDataMgr::GetOwnedBotsCount(player->GetGUID(), me->GetClassMask()) >= BotMgr::GetMaxClassBots()) + reason = 4; + + std::ostringstream message1; + std::ostringstream message2; + if (_botclass == BOT_CLASS_SPHYNX) + { + message1 << LocalizedNpcText(player, BOT_TEXT_HIREWARN_SPHYNX_1) << me->GetName() << LocalizedNpcText(player, BOT_TEXT_HIREWARN_SPHYNX_2); + message2 << LocalizedNpcText(player, BOT_TEXT_HIREOPTION_SPHYNX); + } + else if (_botclass == BOT_CLASS_DREADLORD) + { + message1 << LocalizedNpcText(player, BOT_TEXT_HIREWARN_DREADLORD) << me->GetName() << '?'; + message2 << LocalizedNpcText(player, BOT_TEXT_HIREOPTION_DREADLORD); + } + else if (_botclass == BOT_CLASS_SEA_WITCH) + { + message1 << LocalizedNpcText(player, BOT_TEXT_HIREWARN_SEAWITCH); + message2 << LocalizedNpcText(player, BOT_TEXT_HIREOPTION_SEAWITCH); + } + else if (_botclass == BOT_CLASS_CRYPT_LORD) + { + message1 << LocalizedNpcText(player, BOT_TEXT_HIREWARN_CRYPTLORD); + message2 << LocalizedNpcText(player, BOT_TEXT_HIREOPTION_CRYPTLORD); + } + else + { + message1 << LocalizedNpcText(player, BOT_TEXT_HIREWARN_DEFAULT) << me->GetName() << '?'; + message2 << LocalizedNpcText(player, BOT_TEXT_HIREOPTION_DEFAULT); + } + + if (!reason) + { + player->PlayerTalkClass->GetGossipMenu().AddMenuItem(-1, GOSSIP_ICON_TAXI, message2.str().c_str(), + GOSSIP_SENDER_HIRE, GOSSIP_ACTION_INFO_DEF + 0, message1.str().c_str(), cost, false); + } + else + AddGossipItemFor(player, GOSSIP_ICON_TAXI, message2.str().c_str(), GOSSIP_SENDER_HIRE, GOSSIP_ACTION_INFO_DEF + reason); + + menus = true; + } + } + + if (_ownerGuid) + { + Group const* gr = player->GetGroup(); + + if (player == master) + { + menus = true; + + //general: equips, roles, distance, abilities, comsumables, group + AddGossipItemFor(player, GOSSIP_ICON_TALK, LocalizedNpcText(player, BOT_TEXT_MANAGE_EQUIPMENT), GOSSIP_SENDER_EQUIPMENT, GOSSIP_ACTION_INFO_DEF + 1); + AddGossipItemFor(player, GOSSIP_ICON_TALK, LocalizedNpcText(player, BOT_TEXT_MANAGE_ROLES), GOSSIP_SENDER_ROLES_MAIN, GOSSIP_ACTION_INFO_DEF + 1); + AddGossipItemFor(player, GOSSIP_ICON_TALK, LocalizedNpcText(player, BOT_TEXT_MANAGE_FORMATION), GOSSIP_SENDER_FORMATION, GOSSIP_ACTION_INFO_DEF + 1); + AddGossipItemFor(player, GOSSIP_ICON_TALK, LocalizedNpcText(player, BOT_TEXT_MANAGE_ABILITIES), GOSSIP_SENDER_ABILITIES, GOSSIP_ACTION_INFO_DEF + 1); + if (_botclass < BOT_CLASS_EX_START) + { + if (me->GetLevel() >= 10) + AddGossipItemFor(player, GOSSIP_ICON_TALK, LocalizedNpcText(player, BOT_TEXT_MANAGE_TALENTS), GOSSIP_SENDER_SPEC, GOSSIP_ACTION_INFO_DEF + 1); + AddGossipItemFor(player, GOSSIP_ICON_TALK, LocalizedNpcText(player, BOT_TEXT_GIVE_CONSUMABLE), GOSSIP_SENDER_USEITEM, GOSSIP_ACTION_INFO_DEF + 1); + } + + if (!gr) + { + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_CREATE_GROUP), GOSSIP_SENDER_JOIN_GROUP, GOSSIP_ACTION_INFO_DEF + 1); + if (player->GetNpcBotsCount() > 1) + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_CREATE_GROUP_ALL), GOSSIP_SENDER_JOIN_GROUP, GOSSIP_ACTION_INFO_DEF + 2); + } + else if (!gr->IsMember(me->GetGUID())) + { + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_ADD_TO_GROUP), GOSSIP_SENDER_JOIN_GROUP, GOSSIP_ACTION_INFO_DEF + 1); + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_ADD_TO_GROUP_ALL), GOSSIP_SENDER_JOIN_GROUP, GOSSIP_ACTION_INFO_DEF + 2); + } + else + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_REMOVE_FROM_GROUP), GOSSIP_SENDER_LEAVE_GROUP, GOSSIP_ACTION_INFO_DEF + 1); + + //movement toggle + if (HasBotCommandState(BOT_COMMAND_MASK_UNMOVING)) + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_FOLLOW_ME), GOSSIP_SENDER_FOLLOWME, GOSSIP_ACTION_INFO_DEF + 1); + if (!HasBotCommandState(BOT_COMMAND_STAY)) + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_HOLD_POSITION), GOSSIP_SENDER_HOLDPOSITION, GOSSIP_ACTION_INFO_DEF + 1); + if (!HasBotCommandState(BOT_COMMAND_FULLSTOP)) + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_STAY_HERE), GOSSIP_SENDER_DONOTHING, GOSSIP_ACTION_INFO_DEF + 1); + } + if (player == master || (gr && gr->IsMember(master->GetGUID()))) + { + //class-specific for party: mage rations, rogue lockpicking etc. + //TODO: priest lightwell (manual only) maybe move into abilities + switch (_botclass) + { + case BOT_CLASS_MAGE: + { + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_MAGE_FOOD), GOSSIP_SENDER_CLASS, GOSSIP_ACTION_INFO_DEF + 1); + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_MAGE_DRINK), GOSSIP_SENDER_CLASS, GOSSIP_ACTION_INFO_DEF + 2); + if (me->GetLevel() >= 70) + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_MAGE_TABLE), GOSSIP_SENDER_CLASS, GOSSIP_ACTION_INFO_DEF + 3); + menus = true; + break; + } + case BOT_CLASS_ROGUE: + { + //Learned at 16 + //Allow rogues to gain skill with bot's help + if (me->GetLevel() >= 16/* && !player->HasSkill(SKILL_LOCKPICKING)*/) + { + std::ostringstream msg; + msg << LocalizedNpcText(player, BOT_TEXT_ROGUE_PICKLOCK) << " (" << uint32(me->GetLevel() * 5) << ")"; + AddGossipItemFor(player, GOSSIP_ICON_CHAT, msg.str().c_str(), GOSSIP_SENDER_CLASS, GOSSIP_ACTION_INFO_DEF + 1); + menus = true; + } + break; + } + case BOT_CLASS_WARLOCK: + { + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_WARLOCK_HEALTHSTONE), GOSSIP_SENDER_CLASS, GOSSIP_ACTION_INFO_DEF + 1); + if (me->GetLevel() >= 68) + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_WARLOCK_SOULWELL), GOSSIP_SENDER_CLASS, GOSSIP_ACTION_INFO_DEF + 3); + menus = true; + break; + } + default: + break; + } + } + if (player == master) + { + //class-specific for owner: poisons, enchants, etc. + switch (_botclass) + { + case BOT_CLASS_MAGE: + { + if (me->GetLevel() >= 40) + AddGossipItemFor(player, GOSSIP_ICON_TALK, LocalizedNpcText(player, BOT_TEXT_I_NEED_A_PORTAL), GOSSIP_SENDER_CLASS, GOSSIP_ACTION_INFO_DEF + 4); + break; + } + case BOT_CLASS_ROGUE: + { + if (me->GetLevel() >= 20) + { + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_ROGUE_POISON_REFRESH), GOSSIP_SENDER_CLASS, GOSSIP_ACTION_INFO_DEF + 2); + AddGossipItemFor(player, GOSSIP_ICON_TALK, LocalizedNpcText(player, BOT_TEXT_ROGUE_POISON_MH), GOSSIP_SENDER_CLASS, GOSSIP_ACTION_INFO_DEF + 3); + Item const* oweap = _equips[BOT_SLOT_OFFHAND]; + if (oweap && oweap->GetTemplate()->Class == ITEM_CLASS_WEAPON) + AddGossipItemFor(player, GOSSIP_ICON_TALK, LocalizedNpcText(player, BOT_TEXT_ROGUE_POISON_OH), GOSSIP_SENDER_CLASS, GOSSIP_ACTION_INFO_DEF + 4); + } + break; + } + case BOT_CLASS_SHAMAN: + { + if (me->GetLevel() >= 10) + { + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_SHAMAN_ENCH_REFRESH), GOSSIP_SENDER_CLASS, GOSSIP_ACTION_INFO_DEF + 2); + AddGossipItemFor(player, GOSSIP_ICON_TALK, LocalizedNpcText(player, BOT_TEXT_SHAMAN_ENCH_MH), GOSSIP_SENDER_CLASS, GOSSIP_ACTION_INFO_DEF + 3); + Item const* oweap = _equips[BOT_SLOT_OFFHAND]; + if (oweap && oweap->GetTemplate()->Class == ITEM_CLASS_WEAPON) + AddGossipItemFor(player, GOSSIP_ICON_TALK, LocalizedNpcText(player, BOT_TEXT_SHAMAN_ENCH_OH), GOSSIP_SENDER_CLASS, GOSSIP_ACTION_INFO_DEF + 4); + } + if (me->GetShapeshiftForm() != FORM_NONE) + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_REMOVE_SHAPESHIFT), GOSSIP_SENDER_CLASS, GOSSIP_ACTION_INFO_DEF + 5); + break; + } + case BOT_CLASS_DRUID: + { + if (me->GetShapeshiftForm() != FORM_NONE) + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_REMOVE_SHAPESHIFT), GOSSIP_SENDER_CLASS, GOSSIP_ACTION_INFO_DEF + 1); + break; + } + case BOT_CLASS_HUNTER: + { + if (me->GetLevel() >= 10) + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_CHOOSE_PET_TYPE), GOSSIP_SENDER_CLASS, GOSSIP_ACTION_INFO_DEF + 2); + + break; + } + case BOT_CLASS_WARLOCK: + { + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_CHOOSE_PET_TYPE), GOSSIP_SENDER_CLASS, GOSSIP_ACTION_INFO_DEF + 2); + break; + } + default: + break; + } + + std::ostringstream astr; + astr << LocalizedNpcText(player, BOT_TEXT_ABANDON_WARN_1) << me->GetName() << "? " << (BotMgr::IsEnrageOnDimissEnabled() ? LocalizedNpcText(player, BOT_TEXT_ABANDON_WARN_2) : ""); + player->PlayerTalkClass->GetGossipMenu().AddMenuItem(-1, GOSSIP_ICON_TAXI, LocalizedNpcText(player, BOT_TEXT_UR_DISMISSED), + GOSSIP_SENDER_DISMISS, GOSSIP_ACTION_INFO_DEF + 1, astr.str().c_str(), 0, false); + + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_PULL_URSELF), GOSSIP_SENDER_TROUBLESHOOTING, GOSSIP_ACTION_INFO_DEF + 1); + } + } + + if (_botclass >= BOT_CLASS_EX_START) + { + menus = true; + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_STUDY_CREATURE), GOSSIP_SENDER_SCAN, GOSSIP_ACTION_INFO_DEF + 1); + } + + if (!menus) + { + player->PlayerTalkClass->SendCloseGossip(); + return true; + } + + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_NEVERMIND), 0, GOSSIP_ACTION_INFO_DEF + 1); + player->PlayerTalkClass->SendGossipMenu(gossipTextId, me->GetGUID()); + return true; +} + +//GossipSelect +bool bot_ai::OnGossipSelect(Player* player, Creature* creature/* == me*/, uint32 sender, uint32 action) +{ + if (!BotMgr::IsNpcBotModEnabled() || me->HasUnitState(UNIT_STATE_CASTING) || CCed(me) || HasBotCommandState(BOT_COMMAND_ISSUED_ORDER) || + (me->GetVehicle() && me->GetVehicle()->GetBase()->IsInCombat())) + { + player->PlayerTalkClass->SendCloseGossip(); + return true; + } + + if (me->isMoving()) + me->BotStopMovement(); + + evadeDelayTimer = std::max(evadeDelayTimer, 10000); + + uint32 gossipTextId; + if (!IAmFree()) + { + if (_botclass == BOT_CLASS_SPHYNX) + gossipTextId = GOSSIP_NORMAL_CUSTOM_SPHYNX; + else if (_botclass == BOT_CLASS_DREADLORD) + gossipTextId = GOSSIP_NORMAL_CUSTOM_DREADLORD; + else if (_botclass == BOT_CLASS_DARK_RANGER) + gossipTextId = GOSSIP_NORMAL_CUSTOM_DARKRANGER; + else if (_botclass == BOT_CLASS_SEA_WITCH) + gossipTextId = GOSSIP_NORMAL_CUSTOM_SEAWITCH; + else if (_botclass == BOT_CLASS_CRYPT_LORD) + gossipTextId = GOSSIP_NORMAL_CUSTOM_CRYPTLORD; + else + gossipTextId = GOSSIP_NORMAL_SERVE_MASTER; + } + else + { + if (_botclass == BOT_CLASS_SPHYNX) + gossipTextId = GOSSIP_GREET_CUSTOM_SPHYNX; + else if (_botclass == BOT_CLASS_DREADLORD) + gossipTextId = GOSSIP_GREET_CUSTOM_DREADLORD; + else if (_botclass == BOT_CLASS_DARK_RANGER) + gossipTextId = GOSSIP_GREET_CUSTOM_DARKRANGER; + else if (_botclass == BOT_CLASS_SEA_WITCH) + gossipTextId = GOSSIP_GREET_CUSTOM_SEAWITCH; + else if (_botclass == BOT_CLASS_CRYPT_LORD) + gossipTextId = GOSSIP_GREET_CUSTOM_CRYPTLORD; + else + gossipTextId = GOSSIP_GREET_NEED_SMTH; + } + + player->PlayerTalkClass->ClearMenus(); + bool subMenu = false; + + switch (sender) + { + case 0: //any kind of fail + { + BotSay("...", player); + break; + } + case 1: //BACK: return to main menu + { + return bot_ai::OnGossipHello(player, 0); + } + case GOSSIP_SENDER_CLASS: + { + switch (_botclass) + { + case BOT_CLASS_MAGE: + { + if (IsCasting()) + { + player->SendEquipError(EQUIP_ERR_OBJECT_IS_BUSY, nullptr); + break; + } + + uint32 option = action - GOSSIP_ACTION_INFO_DEF; + if (option == 1 || option == 2) //food, water + { + //Prevent high-leveled consumables for low-level characters + Unit* checker; + if (player->GetLevel() < me->GetLevel()) + checker = player; + else + checker = me; + + // Conjure Refreshment rank 1 + uint32 food = InitSpell(checker, 42955); + bool iswater = (option == 2); + if (!food) + { + if (!iswater)// Conjure Food rank 1 + food = InitSpell(checker, 587); + else// Conjure Water rank 1 + food = InitSpell(checker, 5504); + } + if (!food) + { + BotWhisper(LocalizedNpcText(player, iswater ? BOT_TEXT_CANT_CONJURE_WATER_YET : BOT_TEXT_CANT_CONJURE_FOOD_YET), player); + break; + } + SpellInfo const* Info = sSpellMgr->GetSpellInfo(food); + Spell* foodspell = new Spell(me, Info, TRIGGERED_NONE, player->GetGUID()); + SpellCastTargets targets; + targets.SetUnitTarget(player); + SpellCastResult result = me->IsMounted() || CCed(me) ? SPELL_FAILED_CUSTOM_ERROR : foodspell->CheckPetCast(player); + if (result != SPELL_CAST_OK) + { + foodspell->finish(false); + delete foodspell; + BotWhisper(LocalizedNpcText(player, BOT_TEXT_CANT_RIGHT_NOW), player); + } + else + { + aftercastTargetGuid = player->GetGUID(); + foodspell->prepare(targets); + BotWhisper(LocalizedNpcText(player, BOT_TEXT_HERE_YOU_GO), player); + } + break; + } + else if (option == 3) //refreshment table + { + uint32 tableSpellId = GetSpell(43987); //Ritual of Refreshment + if (!tableSpellId) + { + BotWhisper(LocalizedNpcText(player, BOT_TEXT_DISABLED), player); + break; + } + if (!IsSpellReady(43987, GetLastDiff(), false)) + { + BotWhisper(LocalizedNpcText(player, BOT_TEXT_NOT_READY_YET), player); + break; + } + uint32 tableGOForSpell = (tableSpellId == 43987 ? GO_REFRESHMENT_TABLE_1 : GO_REFRESHMENT_TABLE_2); + GameObjectTemplate const* goInfo = sObjectMgr->GetGameObjectTemplate(tableGOForSpell); + if (!goInfo) + { + BotWhisper(LocalizedNpcText(player, BOT_TEXT_INVALID_OBJECT_TYPE), player); + break; + } + float x,y,z; + me->GetClosePoint(x, y, z, me->GetCombatReach(), 0.f, 0.f); + QuaternionData rot = QuaternionData::fromEulerAnglesZYX(me->GetOrientation(), 0.f, 0.f); + + GameObject* table = new GameObject; + if (!table->Create(me->GetMap()->GenerateLowGuid(), tableGOForSpell, me->GetMap(), + me->GetPhaseMask(), Position(x,y,z,me->GetOrientation()), rot, 255, GO_STATE_READY)) + { + delete table; + BotWhisper(LocalizedNpcText(player, BOT_TEXT_FAILED), player); + break; + } + + SetSpellCooldown(43987, 300000); + + table->SetRespawnTime(180); + //table->SetOwnerGUID(master->GetGUID()); + master->AddGameObject(table); + table->SetSpellId(tableSpellId); + me->GetMap()->AddToMap(table); + + BotWhisper(LocalizedNpcText(player, BOT_TEXT_DONE), player); + break; + } + else if (option == 4) // portal + { + subMenu = true; + + if (player->GetTeamId() == TEAM_ALLIANCE) + { + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_STORMWIND), GOSSIP_SENDER_CLASS_ACTION1, GOSSIP_ACTION_INFO_DEF + uint32(PORTAL_STORMWIND)); + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_IRONFORGE), GOSSIP_SENDER_CLASS_ACTION1, GOSSIP_ACTION_INFO_DEF + uint32(PORTAL_IRONFORGE)); + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_DARNASSUS), GOSSIP_SENDER_CLASS_ACTION1, GOSSIP_ACTION_INFO_DEF + uint32(PORTAL_DARNASSUS)); + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_EXORDAR), GOSSIP_SENDER_CLASS_ACTION1, GOSSIP_ACTION_INFO_DEF + uint32(PORTAL_EXODAR)); + if (me->GetLevel() >= 65) + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_SHATTRATH), GOSSIP_SENDER_CLASS_ACTION1, GOSSIP_ACTION_INFO_DEF + uint32(PORTAL_SHATTRATH_A)); + } + else + { + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_ORGRIMMAR), GOSSIP_SENDER_CLASS_ACTION1, GOSSIP_ACTION_INFO_DEF + uint32(PORTAL_ORGRIMMAR)); + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_UNDERCITY), GOSSIP_SENDER_CLASS_ACTION1, GOSSIP_ACTION_INFO_DEF + uint32(PORTAL_UNDERCITY)); + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_THUNDER_BLUFF), GOSSIP_SENDER_CLASS_ACTION1, GOSSIP_ACTION_INFO_DEF + uint32(PORTAL_THUNDERBLUFF)); + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_SILVERMOON), GOSSIP_SENDER_CLASS_ACTION1, GOSSIP_ACTION_INFO_DEF + uint32(PORTAL_SILVERMOON)); + if (me->GetLevel() >= 65) + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_SHATTRATH), GOSSIP_SENDER_CLASS_ACTION1, GOSSIP_ACTION_INFO_DEF + uint32(PORTAL_SHATTRATH_H)); + } + if (me->GetLevel() >= 74) + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_DALARAN), GOSSIP_SENDER_CLASS_ACTION1, GOSSIP_ACTION_INFO_DEF + uint32(PORTAL_DALARAN)); + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_BACK), 1, GOSSIP_ACTION_INFO_DEF + 7); + } + break; + } + case BOT_CLASS_ROGUE: + { + action -= GOSSIP_ACTION_INFO_DEF; + + if (action == 1) + { + // Pick Lock + subMenu = true; + + uint32 count = 0; + uint32 maxcounter = BOT_GOSSIP_MAX_ITEMS - 1; //BACK + + //1 Nearest gameobject + GameObject* obj = nullptr; + NearestLockedGameObjectInRangeCheck check(player, 4.f); + Trinity::GameObjectLastSearcher searcher(player, obj, check); + Cell::VisitAllObjects(player, searcher, 4.f); + //player->VisitNearbyGridObject(4.f, searcher); + if (obj) + { + std::ostringstream msg; + msg << obj->GetGOInfo()->name << " (" << LocalizedNpcText(player, BOT_TEXT_DISTANCE_SHORT) << " = " << player->GetExactDist(obj) << ")"; + AddGossipItemFor(player, GOSSIP_ICON_CHAT, msg.str().c_str(), GOSSIP_SENDER_CLASS_ACTION1, GOSSIP_ACTION_INFO_DEF + ++count); + } + + //2 Inventory + Item* item = nullptr; + LockEntry const* lockInfo; + + //backpack + for (uint8 i = INVENTORY_SLOT_ITEM_START; i != INVENTORY_SLOT_ITEM_END; ++i) + { + item = player->GetItemByPos(INVENTORY_SLOT_BAG_0, i); + if (item && item->IsLocked() && item->GetTemplate()->LockID) + { + lockInfo = sLockStore.LookupEntry(item->GetTemplate()->LockID); + if (!lockInfo) + continue; + + for (uint8 j = 0; j != MAX_LOCK_CASE; ++j) + { + if (lockInfo->Type[j] == LOCK_KEY_SKILL && lockInfo->Index[j] == LOCKTYPE_PICKLOCK && + lockInfo->Skill[j] <= uint32(15 + creature->GetLevel() * 5)) + { + std::ostringstream name; + _AddItemLink(player, item, name, false); + AddGossipItemFor(player, GOSSIP_ICON_CHAT, name.str().c_str(), GOSSIP_SENDER_CLASS_ACTION1, GOSSIP_ACTION_INFO_DEF + item->GetGUID().GetCounter()); + break; + } + } + } + } + //bags + for (uint8 i = INVENTORY_SLOT_BAG_START; i != INVENTORY_SLOT_BAG_END; ++i) + { + if (Bag const* bag = player->GetBagByPos(i)) + { + for (uint32 j = 0; j != bag->GetBagSize() && count <= maxcounter; ++j) + { + item = player->GetItemByPos(i, j); + if (item && item->IsLocked() && item->GetTemplate()->LockID) + { + lockInfo = sLockStore.LookupEntry(item->GetTemplate()->LockID); + if (!lockInfo) + continue; + + for (uint8 k = 0; k != MAX_LOCK_CASE; ++k) + { + if (lockInfo->Type[k] == LOCK_KEY_SKILL && lockInfo->Index[k] == LOCKTYPE_PICKLOCK && + lockInfo->Skill[k] <= uint32(15 + creature->GetLevel() * 5)) + { + std::ostringstream name; + _AddItemLink(player, item, name, false); + AddGossipItemFor(player, GOSSIP_ICON_CHAT, name.str().c_str(), GOSSIP_SENDER_CLASS_ACTION1, GOSSIP_ACTION_INFO_DEF + item->GetGUID().GetCounter()); + ++count; + break; + } + } + } + } + } + } + + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_BACK), 1, GOSSIP_ACTION_INFO_DEF + ++count); + } + else if (action == 2) + { + //Clear poisons (autorefresh is in class ai DoNonCombatActions + RemoveItemClassEnchantments(); + } + else if (action == 3) + { + subMenu = true; + bool isauto = GetAIMiscValue(BOTAI_MISC_ENCHANT_IS_AUTO_MH); + //Send list of available poisons on MH + for (uint32 i = BOTAI_MISC_ENCHANT_AVAILABLE_1; i <= BOTAI_MISC_ENCHANT_AVAILABLE_6; ++i) + { + uint32 possiblePoison = GetAIMiscValue(i); + if (uint32 possiblePoisonMaxRank = GetSpell(possiblePoison)) + { + SpellInfo const* availableInfo = sSpellMgr->GetSpellInfo(possiblePoisonMaxRank); + uint32 curMHId = GetAIMiscValue(BOTAI_MISC_ENCHANT_CURRENT_MH); + bool same = possiblePoison == curMHId; + std::string spellName; + _LocalizeSpell(player, spellName, availableInfo->Id); + AddGossipItemFor(player, same ? GOSSIP_ICON_BATTLE : GOSSIP_ICON_CHAT, spellName, GOSSIP_SENDER_CLASS_ACTION2, GOSSIP_ACTION_INFO_DEF + possiblePoison); + } + } + AddGossipItemFor(player, isauto ? GOSSIP_ICON_BATTLE : GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_AUTO), GOSSIP_SENDER_CLASS_ACTION2, GOSSIP_ACTION_INFO_DEF + 0); + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_BACK), 1, GOSSIP_ACTION_INFO_DEF + 1); + } + else if (action == 4) + { + subMenu = true; + bool isauto = GetAIMiscValue(BOTAI_MISC_ENCHANT_IS_AUTO_OH); + //Send list of available poisons on OH + for (uint32 i = BOTAI_MISC_ENCHANT_AVAILABLE_1; i <= BOTAI_MISC_ENCHANT_AVAILABLE_6; ++i) + { + uint32 possiblePoison = GetAIMiscValue(i); + if (uint32 possiblePoisonMaxRank = GetSpell(possiblePoison)) + { + SpellInfo const* availableInfo = sSpellMgr->GetSpellInfo(possiblePoisonMaxRank); + uint32 curOHId = GetAIMiscValue(BOTAI_MISC_ENCHANT_CURRENT_OH); + bool same = possiblePoison == curOHId; + std::string spellName; + _LocalizeSpell(player, spellName, availableInfo->Id); + AddGossipItemFor(player, same ? GOSSIP_ICON_BATTLE : GOSSIP_ICON_CHAT, spellName, GOSSIP_SENDER_CLASS_ACTION3, GOSSIP_ACTION_INFO_DEF + possiblePoison); + } + } + AddGossipItemFor(player, isauto ? GOSSIP_ICON_BATTLE : GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_AUTO), GOSSIP_SENDER_CLASS_ACTION3, GOSSIP_ACTION_INFO_DEF + 0); + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_BACK), 1, GOSSIP_ACTION_INFO_DEF + 1); + } + + break; + } + case BOT_CLASS_SHAMAN: + { + action -= GOSSIP_ACTION_INFO_DEF; + + if (action == 2) + { + //Clear enchants (autorefresh is in class ai DoNonCombatActions + RemoveItemClassEnchantments(); + } + else if (action == 3) + { + subMenu = true; + bool isauto = GetAIMiscValue(BOTAI_MISC_ENCHANT_IS_AUTO_MH); + //Send list of available enchants on MH + for (uint32 i = BOTAI_MISC_ENCHANT_AVAILABLE_1; i <= BOTAI_MISC_ENCHANT_AVAILABLE_5; ++i) + { + uint32 possibleEnchant = GetAIMiscValue(i); + if (uint32 possibleEcnhantMaxRank = GetSpell(possibleEnchant)) + { + SpellInfo const* availableInfo = sSpellMgr->GetSpellInfo(possibleEcnhantMaxRank); + uint32 curMHId = GetAIMiscValue(BOTAI_MISC_ENCHANT_CURRENT_MH); + bool same = possibleEnchant == curMHId; + std::string spellName; + _LocalizeSpell(player, spellName, availableInfo->Id); + AddGossipItemFor(player, same ? GOSSIP_ICON_BATTLE : GOSSIP_ICON_CHAT, spellName, GOSSIP_SENDER_CLASS_ACTION2, GOSSIP_ACTION_INFO_DEF + possibleEnchant); + } + } + AddGossipItemFor(player, isauto ? GOSSIP_ICON_BATTLE : GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_AUTO), GOSSIP_SENDER_CLASS_ACTION2, GOSSIP_ACTION_INFO_DEF + 0); + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_BACK), 1, GOSSIP_ACTION_INFO_DEF + 1); + } + else if (action == 4) + { + subMenu = true; + bool isauto = GetAIMiscValue(BOTAI_MISC_ENCHANT_IS_AUTO_OH); + //Send list of available enchants on OH + for (uint32 i = BOTAI_MISC_ENCHANT_AVAILABLE_1; i <= BOTAI_MISC_ENCHANT_AVAILABLE_5; ++i) + { + uint32 possibleEnchant = GetAIMiscValue(i); + if (uint32 possibleEcnhantMaxRank = GetSpell(possibleEnchant)) + { + SpellInfo const* availableInfo = sSpellMgr->GetSpellInfo(possibleEcnhantMaxRank); + uint32 curOHId = GetAIMiscValue(BOTAI_MISC_ENCHANT_CURRENT_OH); + bool same = possibleEnchant == curOHId; + std::string spellName; + _LocalizeSpell(player, spellName, availableInfo->Id); + AddGossipItemFor(player, same ? GOSSIP_ICON_BATTLE : GOSSIP_ICON_CHAT, spellName, GOSSIP_SENDER_CLASS_ACTION3, GOSSIP_ACTION_INFO_DEF + possibleEnchant); + } + } + AddGossipItemFor(player, isauto ? GOSSIP_ICON_BATTLE : GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_AUTO), GOSSIP_SENDER_CLASS_ACTION3, GOSSIP_ACTION_INFO_DEF + 0); + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_BACK), 1, GOSSIP_ACTION_INFO_DEF + 1); + } + else if (action == 5) + { + if (me->GetShapeshiftForm() == FORM_NONE) + { + BotWhisper(LocalizedNpcText(player, BOT_TEXT_NOT_SHAPESHIFTED), player); + break; + } + removeShapeshiftForm(); + } + + break; + } + case BOT_CLASS_HUNTER: + { + if (IsCasting()) + { + player->SendEquipError(EQUIP_ERR_OBJECT_IS_BUSY, nullptr); + break; + } + + action -= GOSSIP_ACTION_INFO_DEF; + + if (action == 2) + { + subMenu = true; + uint32 curType = GetAIMiscValue(BOTAI_MISC_PET_TYPE); + for (uint32 i = BOTAI_MISC_PET_AVAILABLE_1; i <= BOTAI_MISC_PET_AVAILABLE_11; ++i) + { + if (uint32 possibleType = GetAIMiscValue(i)) + { + std::string name; + if (possibleType == BOT_PET_CUNNING_START) + { + name = LocalizedNpcText(player, BOT_TEXT_RANDOMPET_CUNNING); + possibleType = urand(BOT_PET_CUNNING_START, BOT_PET_CUNNING_END); + } + else if (possibleType == BOT_PET_FEROCITY_START) + { + name = LocalizedNpcText(player, BOT_TEXT_RANDOMPET_FEROCITY); + possibleType = urand(BOT_PET_FEROCITY_START, BOT_PET_FEROCITY_END); + } + else if (possibleType == BOT_PET_TENACITY_START) + { + name = LocalizedNpcText(player, BOT_TEXT_RANDOMPET_TENACITY); + possibleType = urand(BOT_PET_TENACITY_START, BOT_PET_TENACITY_END); + } + else + { + CreatureTemplate const* cinfo = sObjectMgr->GetCreatureTemplate(possibleType); + ASSERT(cinfo); + name = cinfo->Name; + } + bool same = possibleType == curType; + AddGossipItemFor(player, same ? GOSSIP_ICON_BATTLE : GOSSIP_ICON_CHAT, name.c_str(), GOSSIP_SENDER_CLASS_ACTION4, GOSSIP_ACTION_INFO_DEF + possibleType); + } + } + bool noPet = curType == BOT_PET_INVALID; + AddGossipItemFor(player, noPet ? GOSSIP_ICON_BATTLE : GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_NONE2), GOSSIP_SENDER_CLASS_ACTION4, GOSSIP_ACTION_INFO_DEF + uint32(BOT_PET_INVALID)); + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_AUTO), GOSSIP_SENDER_CLASS_ACTION4, GOSSIP_ACTION_INFO_DEF + 0); + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_BACK), 1, GOSSIP_ACTION_INFO_DEF + 1); + } + break; + } + case BOT_CLASS_WARLOCK: + { + if (IsCasting()) + { + player->SendEquipError(EQUIP_ERR_OBJECT_IS_BUSY, nullptr); + break; + } + + action -= GOSSIP_ACTION_INFO_DEF; + + if (action == 2) + { + subMenu = true; + uint32 curType = GetAIMiscValue(BOTAI_MISC_PET_TYPE); + for (uint32 i = BOTAI_MISC_PET_AVAILABLE_1; i <= BOTAI_MISC_PET_AVAILABLE_5; ++i) + { + if (uint32 possibleType = GetAIMiscValue(i)) + { + CreatureTemplate const* cinfo = sObjectMgr->GetCreatureTemplate(possibleType); + ASSERT(cinfo); + bool same = possibleType == curType; + AddGossipItemFor(player, same ? GOSSIP_ICON_BATTLE : GOSSIP_ICON_CHAT, cinfo->Name.c_str(), GOSSIP_SENDER_CLASS_ACTION4, GOSSIP_ACTION_INFO_DEF + possibleType); + } + } + bool noPet = curType == BOT_PET_INVALID; + AddGossipItemFor(player, noPet ? GOSSIP_ICON_BATTLE : GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_NONE2), GOSSIP_SENDER_CLASS_ACTION4, GOSSIP_ACTION_INFO_DEF + uint32(BOT_PET_INVALID)); + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_AUTO), GOSSIP_SENDER_CLASS_ACTION4, GOSSIP_ACTION_INFO_DEF + 0); + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_BACK), 1, GOSSIP_ACTION_INFO_DEF + 1); + } + else if (action == 1) + { + if (GetAIMiscValue(6201) == 0) + { + BotWhisper(LocalizedNpcText(player, BOT_TEXT_NO_HEALTHSTONE), player); + break; + } + + //Prevent high-leveled healthstone for low-level characters + Unit* checker; + if (player->GetLevel() < me->GetLevel()) + checker = player; + else + checker = me; + + static uint32 HealthStones[8] = { 19005,19007,19009,19011,19013,22105,36891,36894 }; + uint32 spellId = InitSpell(me, 6201); //Create Healthstone Rank 1 + SpellInfo const* spellInfo = spellId ? sSpellMgr->GetSpellInfo(spellId) : nullptr; + if (!spellInfo) + { + BotWhisper(LocalizedNpcText(player, BOT_TEXT_CANT_CREATE_HEALTHSTONE), player); + break; + } + + int8 i = spellInfo->GetRank() - 1; + for (; i != 0; --i) + if (ItemTemplate const* stone = sObjectMgr->GetItemTemplate(HealthStones[i])) + if (stone->RequiredLevel <= checker->GetLevel()) + break; + + //at least rank 1 (even if player is level 1) + ItemPosCountVec dest; + InventoryResult msg = player->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, HealthStones[i], 1); + if (msg != EQUIP_ERR_OK) + { + player->SendEquipError(msg, nullptr, nullptr, HealthStones[i]); + break; + } + Item* item = player->StoreNewItem(dest, HealthStones[i], true, 0); + if (!item) + { + player->SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, nullptr, nullptr); + break; + } + + //remove healthstone + SetAIMiscValue(6201, 0); + + player->SendNewItem(item, 1, true, false, true); + } + else if (action == 3) //soulwell + { + uint32 wellSpellId = GetSpell(29893); //Ritual of Souls + if (!wellSpellId) + { + BotWhisper(LocalizedNpcText(player, BOT_TEXT_DISABLED), player); + break; + } + if (!IsSpellReady(29893, GetLastDiff(), false)) + { + BotWhisper(LocalizedNpcText(player, BOT_TEXT_NOT_READY_YET), player); + break; + } + uint32 wellGOForSpell = (wellSpellId == 29893 ? GO_SOULWELL_1 : GO_SOULWELL_2); + GameObjectTemplate const* goInfo = sObjectMgr->GetGameObjectTemplate(wellGOForSpell); + if (!goInfo) + { + BotWhisper(LocalizedNpcText(player, BOT_TEXT_INVALID_OBJECT_TYPE), player); + break; + } + float x,y,z; + me->GetClosePoint(x, y, z, me->GetCombatReach(), 0.f, 0.f); + QuaternionData rot = QuaternionData::fromEulerAnglesZYX(me->GetOrientation(), 0.f, 0.f); + + GameObject* soulwell = new GameObject; + if (!soulwell->Create(me->GetMap()->GenerateLowGuid(), wellGOForSpell, me->GetMap(), + me->GetPhaseMask(), Position(x,y,z,me->GetOrientation()), rot, 255, GO_STATE_READY)) + { + delete soulwell; + BotWhisper(LocalizedNpcText(player, BOT_TEXT_FAILED), player); + break; + } + + SetSpellCooldown(29893, 300000); + + soulwell->SetRespawnTime(180); + //soulwell->SetOwnerGUID(master->GetGUID()); + master->AddGameObject(soulwell); + soulwell->SetSpellId(wellSpellId); + me->GetMap()->AddToMap(soulwell); + + BotWhisper(LocalizedNpcText(player, BOT_TEXT_DONE), player); + break; + } + break; + } + case BOT_CLASS_DRUID: + { + if (IsCasting()) + { + player->SendEquipError(EQUIP_ERR_OBJECT_IS_BUSY, nullptr); + break; + } + if (me->GetShapeshiftForm() == FORM_NONE) + { + BotWhisper(LocalizedNpcText(player, BOT_TEXT_NOT_SHAPESHIFTED), player); + break; + } + + removeShapeshiftForm(); + break; + } + + default: + break; + } + break; + } + case GOSSIP_SENDER_CLASS_ACTION1: + { + switch (_botclass) + { + case BOT_CLASS_MAGE: + { + if (!IsCasting()) + { + uint32 portal_spell_id = action - GOSSIP_ACTION_INFO_DEF; + if (!portal_spell_id) + break; + + if (!IsSpellReady(portal_spell_id, lastdiff, false)) + { + BotWhisper(LocalizedNpcText(player, BOT_TEXT_NOT_READY_YET), player); + return OnGossipSelect(player, creature, GOSSIP_SENDER_CLASS, GOSSIP_ACTION_INFO_DEF + 4); + } + + CastSpellExtraArgs args; + args.SetOriginalCaster(player->GetGUID()); + me->CastSpell(me, portal_spell_id, args); + } + break; + } + case BOT_CLASS_ROGUE: + { + if (!IsCasting()) + { + // Pick Lock + uint32 picklock = InitSpell(me, 1804); + if (!picklock) + { + BotWhisper(LocalizedNpcText(player, BOT_TEXT_NO_LOCKPICKING), player); + break; + } + + SpellInfo const* Info = sSpellMgr->GetSpellInfo(picklock); + Spell* lockpickspell = new Spell(player, Info, TRIGGERED_NONE, me->GetGUID()); + SpellCastTargets targets; + + if (action == GOSSIP_ACTION_INFO_DEF + 1) + { + //1 Nearest gameobject + GameObject* obj = nullptr; + NearestLockedGameObjectInRangeCheck check(player, 4.f); + Trinity::GameObjectLastSearcher searcher(player, obj, check); + Cell::VisitAllObjects(player, searcher, 4.f); + //player->VisitNearbyGridObject(4.f, searcher); + if (obj) + { + targets.SetGOTarget(obj); + lockpickspell->m_targets.SetGOTarget(obj); //for checkCast only + } + } + else + { + //2 Inventory + Item* item = nullptr; + uint32 guidLow = action - GOSSIP_ACTION_INFO_DEF; + + bool found = false; + //backpack + for (uint8 i = INVENTORY_SLOT_ITEM_START; i != INVENTORY_SLOT_ITEM_END; ++i) + { + item = player->GetItemByPos(INVENTORY_SLOT_BAG_0, i); + if (item && item->GetGUID().GetCounter() == guidLow) + { + targets.SetItemTarget(item); + lockpickspell->m_targets.SetItemTarget(item); //for checkCast only + found = true; + break; + } + } + //bags + if (!found) + { + for (uint8 i = INVENTORY_SLOT_BAG_START; i != INVENTORY_SLOT_BAG_END; ++i) + { + if (Bag const* bag = player->GetBagByPos(i)) + { + for (uint32 j = 0; j != bag->GetBagSize(); ++j) + { + item = player->GetItemByPos(i, j); + if (item && item->GetGUID().GetCounter() == guidLow) + { + targets.SetItemTarget(item); + lockpickspell->m_targets.SetItemTarget(item); //for checkCast only + found = true; + break; + } + } + } + + if (found) + break; + } + } + } + + SpellCastResult result = me->IsMounted() || CCed(me) ? SPELL_FAILED_CUSTOM_ERROR : lockpickspell->CheckCast(false); + if (result != SPELL_CAST_OK) + { + lockpickspell->finish(false); + delete lockpickspell; + if (result == SPELL_FAILED_LOW_CASTLEVEL) + BotWhisper(LocalizedNpcText(player, BOT_TEXT_SKILL_LEVEL_TOO_LOW), player); + else + BotWhisper(LocalizedNpcText(player, BOT_TEXT_FAILED), player); + } + else + { + lockpickspell->prepare(targets); + //BotWhisper("Here...", player); + } + } + return OnGossipSelect(player, creature, GOSSIP_SENDER_CLASS, GOSSIP_ACTION_INFO_DEF + 1); + //break; + } + default: + break; + } + break; + } + case GOSSIP_SENDER_CLASS_ACTION2: //set cur MH enchant + { + switch (_botclass) + { + case BOT_CLASS_ROGUE: + { + uint32 baseId = action - GOSSIP_ACTION_INFO_DEF; + SetAIMiscValue(BOTAI_MISC_ENCHANT_CURRENT_MH, baseId); + break; + } + case BOT_CLASS_SHAMAN: + { + uint32 baseId = action - GOSSIP_ACTION_INFO_DEF; + SetAIMiscValue(BOTAI_MISC_ENCHANT_CURRENT_MH, baseId); + break; + } + } + return OnGossipHello(player, 0); + } + case GOSSIP_SENDER_CLASS_ACTION3: //set cur OH enchant + { + switch (_botclass) + { + case BOT_CLASS_ROGUE: + { + uint32 baseId = action - GOSSIP_ACTION_INFO_DEF; + SetAIMiscValue(BOTAI_MISC_ENCHANT_CURRENT_OH, baseId); + break; + } + case BOT_CLASS_SHAMAN: + { + uint32 baseId = action - GOSSIP_ACTION_INFO_DEF; + SetAIMiscValue(BOTAI_MISC_ENCHANT_CURRENT_OH, baseId); + break; + } + } + return OnGossipHello(player, 0); + } + case GOSSIP_SENDER_CLASS_ACTION4: //set pet type + { + switch (_botclass) + { + case BOT_CLASS_HUNTER: + { + uint32 petType = action - GOSSIP_ACTION_INFO_DEF; + SetAIMiscValue(BOTAI_MISC_PET_TYPE, petType); + break; + } + case BOT_CLASS_WARLOCK: + { + uint32 petType = action - GOSSIP_ACTION_INFO_DEF; + SetAIMiscValue(BOTAI_MISC_PET_TYPE, petType); + break; + } + } + return OnGossipHello(player, 0); + } + case GOSSIP_SENDER_MODEL_UPDATE: + { + if (Aura* trans = me->AddAura(MODEL_TRANSITION, me)) + { + me->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + uint32(BOT_SLOT_OFFHAND), 0); //debug: remove offhand visuals + trans->SetDuration(500); + trans->SetMaxDuration(500); + } + break; + } + case GOSSIP_SENDER_EQUIPMENT: //equips change s1: send what slots we can use + { + subMenu = true; + + //general + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_SHOW_INVENTORY), GOSSIP_SENDER_EQUIPMENT_LIST, GOSSIP_ACTION_INFO_DEF + 1); + if (BotMgr::IsGearBankEnabled()) + AddGossipItemFor(player, GOSSIP_ICON_TALK, LocalizedNpcText(player, BOT_TEXT_BOT_GEAR_BANK), GOSSIP_SENDER_EQUIPMENT_BANK_MENU, GOSSIP_ACTION_INFO_DEF + 1); + + //auto-equip + AddGossipItemFor(player, GOSSIP_ICON_TALK, LocalizedNpcText(player, BOT_TEXT_AUTOEQUIP) + "...", GOSSIP_SENDER_EQUIP_AUTOEQUIP, GOSSIP_ACTION_INFO_DEF + 1); + + //weapons + AddGossipItemFor(player, GOSSIP_ICON_TALK, LocalizedNpcText(player, BOT_TEXT_SLOT_MH) + "...", GOSSIP_SENDER_EQUIPMENT_SHOW, GOSSIP_ACTION_INFO_DEF + uint32(BOT_SLOT_MAINHAND)); + if (_canUseOffHand()) + AddGossipItemFor(player, GOSSIP_ICON_TALK, LocalizedNpcText(player, BOT_TEXT_SLOT_OH) + "...", GOSSIP_SENDER_EQUIPMENT_SHOW, GOSSIP_ACTION_INFO_DEF + uint32(BOT_SLOT_OFFHAND)); + if (_canUseRanged()) + AddGossipItemFor(player, GOSSIP_ICON_TALK, LocalizedNpcText(player, BOT_TEXT_SLOT_RH) + "...", GOSSIP_SENDER_EQUIPMENT_SHOW, GOSSIP_ACTION_INFO_DEF + uint32(BOT_SLOT_RANGED)); + if (_canUseRelic()) + AddGossipItemFor(player, GOSSIP_ICON_TALK, LocalizedNpcText(player, BOT_TEXT_SLOT_RELIC) + "...", GOSSIP_SENDER_EQUIPMENT_SHOW, GOSSIP_ACTION_INFO_DEF + uint32(BOT_SLOT_RANGED)); + + //armor + AddGossipItemFor(player, GOSSIP_ICON_TALK, LocalizedNpcText(player, BOT_TEXT_SLOT_HEAD) + "...", GOSSIP_SENDER_EQUIPMENT_SHOW, GOSSIP_ACTION_INFO_DEF + uint32(BOT_SLOT_HEAD)); + AddGossipItemFor(player, GOSSIP_ICON_TALK, LocalizedNpcText(player, BOT_TEXT_SLOT_SHOULDERS) + "...", GOSSIP_SENDER_EQUIPMENT_SHOW, GOSSIP_ACTION_INFO_DEF + uint32(BOT_SLOT_SHOULDERS)); + AddGossipItemFor(player, GOSSIP_ICON_TALK, LocalizedNpcText(player, BOT_TEXT_SLOT_CHEST) + "...", GOSSIP_SENDER_EQUIPMENT_SHOW, GOSSIP_ACTION_INFO_DEF + uint32(BOT_SLOT_CHEST)); + AddGossipItemFor(player, GOSSIP_ICON_TALK, LocalizedNpcText(player, BOT_TEXT_SLOT_WAIST) + "...", GOSSIP_SENDER_EQUIPMENT_SHOW, GOSSIP_ACTION_INFO_DEF + uint32(BOT_SLOT_WAIST)); + AddGossipItemFor(player, GOSSIP_ICON_TALK, LocalizedNpcText(player, BOT_TEXT_SLOT_LEGS) + "...", GOSSIP_SENDER_EQUIPMENT_SHOW, GOSSIP_ACTION_INFO_DEF + uint32(BOT_SLOT_LEGS)); + AddGossipItemFor(player, GOSSIP_ICON_TALK, LocalizedNpcText(player, BOT_TEXT_SLOT_FEET) + "...", GOSSIP_SENDER_EQUIPMENT_SHOW, GOSSIP_ACTION_INFO_DEF + uint32(BOT_SLOT_FEET)); + AddGossipItemFor(player, GOSSIP_ICON_TALK, LocalizedNpcText(player, BOT_TEXT_SLOT_WRIST) + "...", GOSSIP_SENDER_EQUIPMENT_SHOW, GOSSIP_ACTION_INFO_DEF + uint32(BOT_SLOT_WRIST)); + AddGossipItemFor(player, GOSSIP_ICON_TALK, LocalizedNpcText(player, BOT_TEXT_SLOT_HANDS) + "...", GOSSIP_SENDER_EQUIPMENT_SHOW, GOSSIP_ACTION_INFO_DEF + uint32(BOT_SLOT_HANDS)); + + if (IsHumanoidClass(_botclass)) + { + AddGossipItemFor(player, GOSSIP_ICON_TALK, LocalizedNpcText(player, BOT_TEXT_SLOT_BACK) + "...", GOSSIP_SENDER_EQUIPMENT_SHOW, GOSSIP_ACTION_INFO_DEF + uint32(BOT_SLOT_BACK)); + AddGossipItemFor(player, GOSSIP_ICON_TALK, LocalizedNpcText(player, BOT_TEXT_SLOT_SHIRT) + "...", GOSSIP_SENDER_EQUIPMENT_SHOW, GOSSIP_ACTION_INFO_DEF + uint32(BOT_SLOT_BODY)); + AddGossipItemFor(player, GOSSIP_ICON_TALK, LocalizedNpcText(player, BOT_TEXT_SLOT_FINGER1) + "...", GOSSIP_SENDER_EQUIPMENT_SHOW, GOSSIP_ACTION_INFO_DEF + uint32(BOT_SLOT_FINGER1)); + AddGossipItemFor(player, GOSSIP_ICON_TALK, LocalizedNpcText(player, BOT_TEXT_SLOT_FINGER2) + "...", GOSSIP_SENDER_EQUIPMENT_SHOW, GOSSIP_ACTION_INFO_DEF + uint32(BOT_SLOT_FINGER2)); + AddGossipItemFor(player, GOSSIP_ICON_TALK, LocalizedNpcText(player, BOT_TEXT_SLOT_TRINKET1) + "...", GOSSIP_SENDER_EQUIPMENT_SHOW, GOSSIP_ACTION_INFO_DEF + uint32(BOT_SLOT_TRINKET1)); + AddGossipItemFor(player, GOSSIP_ICON_TALK, LocalizedNpcText(player, BOT_TEXT_SLOT_TRINKET2) + "...", GOSSIP_SENDER_EQUIPMENT_SHOW, GOSSIP_ACTION_INFO_DEF + uint32(BOT_SLOT_TRINKET2)); + AddGossipItemFor(player, GOSSIP_ICON_TALK, LocalizedNpcText(player, BOT_TEXT_SLOT_NECK) + "...", GOSSIP_SENDER_EQUIPMENT_SHOW, GOSSIP_ACTION_INFO_DEF + uint32(BOT_SLOT_NECK)); + } + + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_UNEQUIP_ALL), GOSSIP_SENDER_UNEQUIP_ALL, GOSSIP_ACTION_INFO_DEF + 1, LocalizedNpcText(player, BOT_TEXT_UNEQUIP_ALL) + "?", 0, false); + if (creature->GetCreatureTemplate()->unit_flags2 & UNIT_FLAG2_MIRROR_IMAGE) + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_UPDATE_VISUAL), GOSSIP_SENDER_MODEL_UPDATE, GOSSIP_ACTION_INFO_DEF + 1); + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_BACK), 1, GOSSIP_ACTION_INFO_DEF + 1); + + break; + } + case GOSSIP_SENDER_EQUIPMENT_LIST: //list inventory + { + //if (action - GOSSIP_ACTION_INFO_DEF != BOT_SLOT_NONE) + // break; + + EquipmentInfo const* einfo = BotDataMgr::GetBotEquipmentInfo(me->GetEntry()); + ASSERT(einfo, "Trying to send equipment list for bot with no equip info!"); + + for (uint8 i = BOT_SLOT_MAINHAND; i != BOT_INVENTORY_SIZE; ++i) + { + Item const* item = _equips[i]; + if (!item) continue; + std::ostringstream msg; + _AddItemLink(player, item, msg/*, false*/); + //uncomment if needed + //msg << " in slot " << uint32(i) << " (" << _getNameForSlot(i + 1) << ')'; + if (i <= BOT_SLOT_RANGED && einfo->ItemEntry[i] == item->GetEntry()) + msg << " |cffe6cc80|h[!" << LocalizedNpcText(player, BOT_TEXT_VISUALONLY) << "!]|h|r"; + BotWhisper(msg.str(), player); + } + + std::ostringstream msg2; + msg2 << "GS: " << uint32(GetBotGearScores().first); + BotWhisper(msg2.str(), player); + + break; + } + case GOSSIP_SENDER_EQUIP_TRANSMOGRIFY_MHAND: //0 - 1 main hand + case GOSSIP_SENDER_EQUIP_TRANSMOGRIFY_OHAND: //1 - 1 off hand + case GOSSIP_SENDER_EQUIP_TRANSMOGRIFY_RANGED: //2 - 1 ranged + case GOSSIP_SENDER_EQUIP_TRANSMOGRIFY_HEAD: //3 - 1 head + case GOSSIP_SENDER_EQUIP_TRANSMOGRIFY_SHOULDERS: //4 - 1 shoulders + case GOSSIP_SENDER_EQUIP_TRANSMOGRIFY_CHEST: //5 - 1 chest + case GOSSIP_SENDER_EQUIP_TRANSMOGRIFY_WAIST: //6 - 1 waist + case GOSSIP_SENDER_EQUIP_TRANSMOGRIFY_LEGS: //7 - 1 legs + case GOSSIP_SENDER_EQUIP_TRANSMOGRIFY_FEET: //8 - 1 feet + case GOSSIP_SENDER_EQUIP_TRANSMOGRIFY_WRIST: //9 - 1 wrist + case GOSSIP_SENDER_EQUIP_TRANSMOGRIFY_HANDS: //10 - 1 hands + case GOSSIP_SENDER_EQUIP_TRANSMOGRIFY_BACK: //11 - 1 back + case GOSSIP_SENDER_EQUIP_TRANSMOGRIFY_BODY: //12 - 1 body + { + uint8 slot = sender - GOSSIP_SENDER_EQUIP_TRANSMOGRIFY; + int32 itemId = (action == std::numeric_limits::max()) ? -1 : int32(action); + uint32 itemId_u = uint32(std::max(itemId, 0)); + + Item const* item = _equips[slot]; + ASSERT(item); + + BotDataMgr::UpdateNpcBotTransmogData(me->GetEntry(), slot, item->GetEntry(), itemId); + + if (slot <= BOT_SLOT_RANGED) + me->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + slot, itemId_u ? itemId_u : item->GetEntry()); + + return OnGossipSelect(player, creature, GOSSIP_SENDER_EQUIPMENT, GOSSIP_ACTION_INFO_DEF + 1); + } + case GOSSIP_SENDER_EQUIP_TRANSMOG_INFO: + { + uint8 slot = action - GOSSIP_ACTION_INFO_DEF; + + NpcBotTransmogData const* tramsmogData = BotDataMgr::SelectNpcBotTransmogs(me->GetEntry()); + ASSERT(tramsmogData); + ASSERT(tramsmogData->transmogs[slot].second >= 0); + + uint32 item_id = uint32(tramsmogData->transmogs[slot].second); + ItemTemplate const* proto = item_id ? sObjectMgr->GetItemTemplate(item_id) : nullptr; + if (proto) + { + std::ostringstream msg; + _AddItemTemplateLink(player, proto, msg); + + BotWhisper(msg.str(), player); + } + + //break; //no break here - return to menu + } + [[fallthrough]]; + case GOSSIP_SENDER_EQUIP_TRANSMOGS: + { + subMenu = true; + + uint8 slot = action - GOSSIP_ACTION_INFO_DEF; + Item const* item = _equips[slot]; + ASSERT(item); + + std::set itemList, idsList; + + //s5.1: build list + //s5.1.1: backpack + for (uint8 i = INVENTORY_SLOT_ITEM_START; i != INVENTORY_SLOT_ITEM_END; ++i) + { + if (Item const* pItem = player->GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + { + if (IsValidTransmog(slot, pItem->GetTemplate()) && idsList.find(pItem->GetEntry()) == idsList.end()) + { + itemList.insert(pItem->GetGUID().GetCounter()); + idsList.insert(pItem->GetEntry()); + } + } + } + + //s5.1.2: other bags + for (uint8 i = INVENTORY_SLOT_BAG_START; i != INVENTORY_SLOT_BAG_END; ++i) + { + if (Bag const* pBag = player->GetBagByPos(i)) + { + for (uint32 j = 0; j != pBag->GetBagSize(); ++j) + { + if (Item const* pItem = player->GetItemByPos(i, j)) + { + if (IsValidTransmog(slot, pItem->GetTemplate()) && idsList.find(pItem->GetEntry()) == idsList.end()) + { + itemList.insert(pItem->GetGUID().GetCounter()); + idsList.insert(pItem->GetEntry()); + } + } + } + } + } + + //s5.1.3: inventory + for (uint8 i = EQUIPMENT_SLOT_START; i != EQUIPMENT_SLOT_END; ++i) + { + if (Item const* pItem = player->GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + { + if (IsValidTransmog(slot, pItem->GetTemplate()) && idsList.find(pItem->GetEntry()) == idsList.end()) + { + itemList.insert(pItem->GetGUID().GetCounter()); + idsList.insert(pItem->GetEntry()); + } + } + } + + //s5.2: add gossips + NpcBotTransmogData const* tramsmogData = BotDataMgr::SelectNpcBotTransmogs(me->GetEntry()); + if (tramsmogData && tramsmogData->transmogs[slot].first) + { + int32 item_id = tramsmogData->transmogs[slot].second; + if (item_id >= 0) + { + //s5.2.1.1: current + std::ostringstream msg; + if (item_id == 0) + msg << LocalizedNpcText(player, BOT_TEXT_HIDDEN); + else if (ItemTemplate const* proto = sObjectMgr->GetItemTemplate(uint32(item_id))) + _AddItemTemplateLink(player, proto, msg); + else + msg << '<' << LocalizedNpcText(player, BOT_TEXT_UNKNOWN) << "(" << item_id << ")>"; + + AddGossipItemFor(player, GOSSIP_ICON_BATTLE, msg.str(), GOSSIP_SENDER_EQUIP_TRANSMOG_INFO, GOSSIP_ACTION_INFO_DEF + slot); + + //s5.2.1.2a: reset + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_NONE), GOSSIP_SENDER_EQUIP_TRANSMOGRIFY + slot, std::numeric_limits::max()); + } + else + { + //s5.2.1.2b: None + AddGossipItemFor(player, GOSSIP_ICON_BATTLE, LocalizedNpcText(player, BOT_TEXT_NONE), GOSSIP_SENDER_EQUIP_TRANSMOGS, action); + } + } + + //s5.2.1.2c: hide + if (slot > BOT_SLOT_RANGED && + !(tramsmogData && tramsmogData->transmogs[slot].first && tramsmogData->transmogs[slot].second == 0)) + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_HIDDEN), GOSSIP_SENDER_EQUIP_TRANSMOGRIFY + slot, 0); + + if (!itemList.empty()) + { + uint32 counter = 0; + uint32 maxcounter = BOT_GOSSIP_MAX_ITEMS - 3; //current, reset, back + //s5.2.2: add items as gossip options + for (std::set::const_iterator itr = itemList.begin(); itr != itemList.end() && counter < maxcounter; ++itr) + { + bool found = false; + for (uint8 i = INVENTORY_SLOT_ITEM_START; i != INVENTORY_SLOT_ITEM_END; ++i) + { + item = player->GetItemByPos(INVENTORY_SLOT_BAG_0, i); + if (item && item->GetGUID().GetCounter() == (*itr)) + { + std::ostringstream name; + _AddItemLink(player, item, name); + AddGossipItemFor(player, GOSSIP_ICON_CHAT, name.str(), GOSSIP_SENDER_EQUIP_TRANSMOGRIFY + slot, item->GetEntry()); + ++counter; + found = true; + break; + } + } + + if (found) + continue; + + for (uint8 i = EQUIPMENT_SLOT_START; i != EQUIPMENT_SLOT_END; ++i) + { + item = player->GetItemByPos(INVENTORY_SLOT_BAG_0, i); + if (item && item->GetGUID().GetCounter() == (*itr)) + { + std::ostringstream name; + _AddItemLink(player, item, name); + AddGossipItemFor(player, GOSSIP_ICON_CHAT, name.str(), GOSSIP_SENDER_EQUIP_TRANSMOGRIFY + slot, item->GetEntry()); + ++counter; + found = true; + break; + } + } + + if (found) + continue; + + for (uint8 i = INVENTORY_SLOT_BAG_START; i != INVENTORY_SLOT_BAG_END; ++i) + { + if (Bag const* pBag = player->GetBagByPos(i)) + { + for (uint32 j = 0; j != pBag->GetBagSize(); ++j) + { + item = player->GetItemByPos(i, j); + if (item && item->GetGUID().GetCounter() == (*itr)) + { + std::ostringstream name; + _AddItemLink(player, item, name); + AddGossipItemFor(player, GOSSIP_ICON_CHAT, name.str(), GOSSIP_SENDER_EQUIP_TRANSMOGRIFY + slot, item->GetEntry()); + ++counter; + found = true; + break; + } + } + } + + if (found) + break; + } + + if (found) + continue; + } + } + + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_BACK), GOSSIP_SENDER_EQUIPMENT, GOSSIP_ACTION_INFO_DEF + 2); + + break; + } + case GOSSIP_SENDER_EQUIPMENT_INFO: //request equip item info + { + //GOSSIP ITEMS RESTRICTED + //subMenu = true; //needed for return + + EquipmentInfo const* einfo = BotDataMgr::GetBotEquipmentInfo(me->GetEntry()); + ASSERT(einfo, "Trying to send equipment info for bot with no equip info!"); + + uint8 slot = action - GOSSIP_ACTION_INFO_DEF; + Item const* item = _equips[slot]; + ASSERT(item); + + std::ostringstream msg; + _AddItemLink(player, item, msg, false); + + if (slot <= BOT_SLOT_RANGED && einfo->ItemEntry[slot] == item->GetEntry()) + msg << " |cffe6cc80|h[!" << LocalizedNpcText(player, BOT_TEXT_VISUALONLY) << "!]|h|r"; + + msg << " GS: " << uint32(CalculateItemGearScore(me->GetEntry(), me->GetLevel(), GetBotClass(), GetSpec(), slot, item->GetTemplate())); + + BotWhisper(msg.str(), player); + + //break; //no break here - return to menu + } + [[fallthrough]]; + case GOSSIP_SENDER_EQUIPMENT_SHOW: //equips change s2: send list of equippable items + { + subMenu = true; + + EquipmentInfo const* einfo = BotDataMgr::GetBotEquipmentInfo(me->GetEntry()); + ASSERT(einfo, "Trying to send equipment show for bot with no equip info!"); + + std::set itemList, idsList; + + //s2.1: build list + //s2.1.1: backpack + for (uint8 i = INVENTORY_SLOT_ITEM_START; i != INVENTORY_SLOT_ITEM_END; ++i) + { + if (Item const* pItem = player->GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + { + bool standard = false; + for (uint8 j = 0; j != MAX_EQUIPMENT_ITEMS; ++j) + { + if (einfo->ItemEntry[j] == pItem->GetEntry()) + { + standard = true; + break; + } + } + if (standard) + continue; + if (_canEquip(pItem->GetTemplate(), action - GOSSIP_ACTION_INFO_DEF, true, pItem) && + /*itemList.find(pItem->GetGUID().GetCounter()) == itemList.end() &&*/ + (pItem->GetItemRandomPropertyId() == 0 ? idsList.find(pItem->GetEntry()) == idsList.end() : true)) + { + itemList.insert(pItem->GetGUID().GetCounter()); + idsList.insert(pItem->GetEntry()); + } + } + } + + //s2.1.2: other bags + for (uint8 i = INVENTORY_SLOT_BAG_START; i != INVENTORY_SLOT_BAG_END; ++i) + { + if (Bag const* pBag = player->GetBagByPos(i)) + { + for (uint32 j = 0; j != pBag->GetBagSize(); ++j) + { + if (Item const* pItem = player->GetItemByPos(i, j)) + { + bool standard = false; + for (uint8 k = 0; k != MAX_EQUIPMENT_ITEMS; ++k) + { + if (einfo->ItemEntry[k] == pItem->GetEntry()) + { + standard = true; + break; + } + } + if (standard) + continue; + if (_canEquip(pItem->GetTemplate(), action - GOSSIP_ACTION_INFO_DEF, true, pItem) && + /*itemList.find(pItem->GetGUID().GetCounter()) == itemList.end() &&*/ + (pItem->GetItemRandomPropertyId() == 0 ? idsList.find(pItem->GetEntry()) == idsList.end() : true)) + { + itemList.insert(pItem->GetGUID().GetCounter()); + idsList.insert(pItem->GetEntry()); + } + } + } + } + } + + //s2.2: add gossips + + //s2.2.0 add current item (with return) + uint8 const slot = action - GOSSIP_ACTION_INFO_DEF; + std::ostringstream str; + str << LocalizedNpcText(player, BOT_TEXT_EQUIPPED) << ": "; + if (Item const* item = _equips[slot]) + { + bool visual_only = slot <= BOT_SLOT_RANGED && einfo->ItemEntry[slot] == item->GetEntry(); + + _AddItemLink(player, item, str); + if (visual_only) + str << " |cffe6cc80|h[!" << LocalizedNpcText(player, BOT_TEXT_VISUALONLY) << "!]|h|r"; + + str << " GS: " << uint32(CalculateItemGearScore(me->GetEntry(), me->GetLevel(), GetBotClass(), GetSpec(), slot, item->GetTemplate())); + + AddGossipItemFor(player, GOSSIP_ICON_CHAT, str.str().c_str(), GOSSIP_SENDER_EQUIPMENT_INFO, action); + + if (!visual_only && BotMgr::DisplayEquipment() && BotMgr::IsTransmogEnabled() && slot < BOT_TRANSMOG_INVENTORY_SIZE && CanDisplayNonWeaponEquipmentChanges()) + AddGossipItemFor(player, GOSSIP_ICON_TALK, LocalizedNpcText(player, BOT_TEXT_TRANSMOGRIFICATION), GOSSIP_SENDER_EQUIP_TRANSMOGS, action); + } + else + { + str << LocalizedNpcText(player, BOT_TEXT_NOTHING); + AddGossipItemFor(player, GOSSIP_ICON_CHAT, str.str().c_str(), GOSSIP_SENDER_EQUIPMENT_SHOW, action); + } + + if (_equips[slot]) + { + //s2.2.1 add unequip option if have weapon (GMs only) + if (slot <= BOT_SLOT_RANGED) + { + if (einfo->ItemEntry[slot] != 0) + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_USE_OLD_EQUIPMENT), GOSSIP_SENDER_EQUIP_RESET, action); + else + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_UNEQUIP), GOSSIP_SENDER_UNEQUIP, action); + } + + //s2.2.2 add unequip option for non-weapons + if (slot > BOT_SLOT_RANGED) + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_UNEQUIP), GOSSIP_SENDER_UNEQUIP, action); + } + + //s2.2.3a: add an empty submenu with info if no items are found + if (itemList.empty()) + { + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_NOTHING_TO_GIVE), 0, GOSSIP_ACTION_INFO_DEF + 1); + } + else + { + uint32 counter = 0; + uint32 maxcounter = BOT_GOSSIP_MAX_ITEMS - 5; //unequip, reset, current, transmog, back + Item const* item; + //s2.2.3b: add items as gossip options + for (std::set::const_iterator itr = itemList.begin(); itr != itemList.end() && counter < maxcounter; ++itr) + { + bool found = false; + for (uint8 i = INVENTORY_SLOT_ITEM_START; i != INVENTORY_SLOT_ITEM_END; ++i) + { + item = player->GetItemByPos(INVENTORY_SLOT_BAG_0, i); + if (item && item->GetGUID().GetCounter() == (*itr)) + { + std::ostringstream name; + _AddItemLink(player, item, name); + name << " GS: " << uint32(CalculateItemGearScore(me->GetEntry(), me->GetLevel(), GetBotClass(), GetSpec(), slot, item->GetTemplate())); + if (BotMgr::SendEquipListItems()) + BotWhisper(name.str(), player); + AddGossipItemFor(player, GOSSIP_ICON_CHAT, name.str().c_str(), GOSSIP_SENDER_EQUIP + slot, GOSSIP_ACTION_INFO_DEF + item->GetGUID().GetCounter()); + ++counter; + found = true; + break; + } + } + + if (found) + continue; + + for (uint8 i = INVENTORY_SLOT_BAG_START; i != INVENTORY_SLOT_BAG_END; ++i) + { + if (Bag const* pBag = player->GetBagByPos(i)) + { + for (uint32 j = 0; j != pBag->GetBagSize(); ++j) + { + item = player->GetItemByPos(i, j); + if (item && item->GetGUID().GetCounter() == (*itr)) + { + std::ostringstream name; + _AddItemLink(player, item, name); + name << " GS: " << uint32(CalculateItemGearScore(me->GetEntry(), me->GetLevel(), GetBotClass(), GetSpec(), slot, item->GetTemplate())); + if (BotMgr::SendEquipListItems()) + BotWhisper(name.str(), player); + AddGossipItemFor(player, GOSSIP_ICON_CHAT, name.str().c_str(), GOSSIP_SENDER_EQUIP + slot, GOSSIP_ACTION_INFO_DEF + item->GetGUID().GetCounter()); + ++counter; + found = true; + break; + } + } + } + + if (found) + break; + } + + if (found) + continue; + } + } + + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_BACK), GOSSIP_SENDER_EQUIPMENT, GOSSIP_ACTION_INFO_DEF + 2); + + //TC_LOG_ERROR("entities.player", "OnGossipSelect(bot): added {} item(s) to list of {} (requester: {})", + // counter, me->GetName(), player->GetName()); + + break; + } + case GOSSIP_SENDER_UNEQUIP: //equips change s3: Unequip DEPRECATED + { + if (!_unequip(action - GOSSIP_ACTION_INFO_DEF, player->GetGUID())) + {} //BotWhisper("Impossible...", player); + return OnGossipSelect(player, creature, GOSSIP_SENDER_EQUIPMENT, GOSSIP_ACTION_INFO_DEF + 1); + } + case GOSSIP_SENDER_UNEQUIP_ALL: + { + bool suc = true; + for (uint8 i = BOT_SLOT_MAINHAND; i != BOT_INVENTORY_SIZE; ++i) + { + if (!(i <= BOT_SLOT_RANGED ? _resetEquipment(i, player->GetGUID()) : _unequip(i, player->GetGUID()))) + { + suc = false; + //std::ostringstream estr; + //estr << "Cannot reset equipment in slot " << uint32(i) << " (" << _getNameForSlot(i) << ")!"; + //BotWhisper(estr.str().c_str(), player); + } + } + + if (suc) + me->HandleEmoteCommand(EMOTE_ONESHOT_CRY); + + break; + } + //autoequips change s5b: AtoEquip item + //base is GOSSIP_SENDER_EQUIP_AUTOEQUIP + 0...1...2... etc. + case GOSSIP_SENDER_EQUIP_AUTOEQUIP_MHAND: //0 - 1 main hand + case GOSSIP_SENDER_EQUIP_AUTOEQUIP_OHAND: //1 - 1 off hand + case GOSSIP_SENDER_EQUIP_AUTOEQUIP_RANGED: //2 - 1 ranged + case GOSSIP_SENDER_EQUIP_AUTOEQUIP_HEAD: //3 - 1 head + case GOSSIP_SENDER_EQUIP_AUTOEQUIP_SHOULDERS: //4 - 1 shoulders + case GOSSIP_SENDER_EQUIP_AUTOEQUIP_CHEST: //5 - 1 chest + case GOSSIP_SENDER_EQUIP_AUTOEQUIP_WAIST: //6 - 1 waist + case GOSSIP_SENDER_EQUIP_AUTOEQUIP_LEGS: //7 - 1 legs + case GOSSIP_SENDER_EQUIP_AUTOEQUIP_FEET: //8 - 1 feet + case GOSSIP_SENDER_EQUIP_AUTOEQUIP_WRIST: //9 - 1 wrist + case GOSSIP_SENDER_EQUIP_AUTOEQUIP_HANDS: //10 - 1 hands + case GOSSIP_SENDER_EQUIP_AUTOEQUIP_BACK: //11 - 1 back + case GOSSIP_SENDER_EQUIP_AUTOEQUIP_BODY: //12 - 1 body + case GOSSIP_SENDER_EQUIP_AUTOEQUIP_FINGER1: //13 - 1 finger + case GOSSIP_SENDER_EQUIP_AUTOEQUIP_FINGER2: //14 - 2 finger + case GOSSIP_SENDER_EQUIP_AUTOEQUIP_TRINKET1: //15 - 1 trinket + case GOSSIP_SENDER_EQUIP_AUTOEQUIP_TRINKET2: //16 - 2 trinket + case GOSSIP_SENDER_EQUIP_AUTOEQUIP_NECK: //17 - 1 neck + { + Item* item = nullptr; + uint32 guidLow = action - GOSSIP_ACTION_INFO_DEF; + + bool found = false; + for (uint8 i = INVENTORY_SLOT_ITEM_START; i != INVENTORY_SLOT_ITEM_END; ++i) + { + item = player->GetItemByPos(INVENTORY_SLOT_BAG_0, i); + if (item && item->GetGUID().GetCounter() == guidLow) + { + found = true; + break; + } + } + + if (!found) + { + for (uint8 i = INVENTORY_SLOT_BAG_START; i != INVENTORY_SLOT_BAG_END; ++i) + { + if (Bag const* pBag = player->GetBagByPos(i)) + { + for (uint32 j = 0; j != pBag->GetBagSize(); ++j) + { + item = player->GetItemByPos(i, j); + if (item && item->GetGUID().GetCounter() == guidLow) + { + found = true; + break; + } + } + } + + if (found) + break; + } + } + + if (found && _equip(sender - GOSSIP_SENDER_EQUIP_AUTOEQUIP_EQUIP, item, player->GetGUID())){} + + //break; //no break: update list + } + [[fallthrough]]; + case GOSSIP_SENDER_EQUIP_AUTOEQUIP: + { + subMenu = true; + + EquipmentInfo const* einfo = BotDataMgr::GetBotEquipmentInfo(me->GetEntry()); + ASSERT(einfo, "Trying to send auto-equip for bot with no equip info!"); + + std::set itemList, idsList; + + //1: build list + //1.1: backpack + for (uint8 i = INVENTORY_SLOT_ITEM_START; i != INVENTORY_SLOT_ITEM_END; ++i) + { + if (Item const* pItem = player->GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + { + bool standard = false; + for (uint8 j = 0; j != MAX_EQUIPMENT_ITEMS; ++j) + { + if (einfo->ItemEntry[j] == pItem->GetEntry()) + { + standard = true; + break; + } + } + if (standard) + continue; + + bool canEquip = false; + + for (uint8 k = BOT_SLOT_MAINHAND; k != BOT_INVENTORY_SIZE; ++k) + { + if (_canEquip(pItem->GetTemplate(), k, false, pItem)) + { + canEquip = true; + break; + } + } + + if (canEquip &&/* itemList.find(pItem->GetGUID().GetCounter()) == itemList.end() &&*/ + (pItem->GetItemRandomPropertyId() == 0 ? idsList.find(pItem->GetEntry()) == idsList.end() : true)) + { + itemList.insert(pItem->GetGUID().GetCounter()); + idsList.insert(pItem->GetEntry()); + } + } + } + + //1.2: other bags + for (uint8 i = INVENTORY_SLOT_BAG_START; i != INVENTORY_SLOT_BAG_END; ++i) + { + if (Bag const* pBag = player->GetBagByPos(i)) + { + for (uint32 j = 0; j != pBag->GetBagSize(); ++j) + { + if (Item const* pItem = player->GetItemByPos(i, j)) + { + bool standard = false; + for (uint8 k = 0; k != MAX_EQUIPMENT_ITEMS; ++k) + { + if (einfo->ItemEntry[k] == pItem->GetEntry()) + { + standard = true; + break; + } + } + if (standard) + continue; + + bool canEquip = false; + + for (uint8 k = BOT_SLOT_MAINHAND; k != BOT_INVENTORY_SIZE; ++k) + { + if (_canEquip(pItem->GetTemplate(), k, false, pItem)) + { + canEquip = true; + break; + } + } + + if (canEquip &&/* itemList.find(pItem->GetGUID().GetCounter()) == itemList.end() &&*/ + (pItem->GetItemRandomPropertyId() == 0 ? idsList.find(pItem->GetEntry()) == idsList.end() : true)) + { + itemList.insert(pItem->GetGUID().GetCounter()); + idsList.insert(pItem->GetEntry()); + } + } + } + } + } + + //2: add gossips + + if (itemList.empty()) + { + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_NOTHING_TO_GIVE), 0, GOSSIP_ACTION_INFO_DEF + 1); + } + else + { + uint32 counter = 0; + uint32 maxcounter = BOT_GOSSIP_MAX_ITEMS - 1; // back + Item const* item; + //add items as gossip options + for (std::set::const_iterator itr = itemList.begin(); itr != itemList.end() && counter < maxcounter; ++itr) + { + bool found = false; + for (uint8 i = INVENTORY_SLOT_ITEM_START; i != INVENTORY_SLOT_ITEM_END; ++i) + { + item = player->GetItemByPos(INVENTORY_SLOT_BAG_0, i); + if (item && item->GetGUID().GetCounter() == (*itr)) + { + uint8 k = 0; + for (; k != BOT_INVENTORY_SIZE; ++k) + { + if (_canEquip(item->GetTemplate(), k, false, item)) + { + //workaround for double slots + //if first slot is occupied and second slot is vacant use second slot + if (k == BOT_SLOT_FINGER1 || k == BOT_SLOT_TRINKET1) + if (_equips[k] != nullptr && _canEquip(item->GetTemplate(), k + 1, false, item)) + ++k; + break; + } + } + + std::ostringstream name; + _AddItemLink(player, item, name); + if (BotMgr::SendEquipListItems()) + BotWhisper(name.str(), player); + AddGossipItemFor(player, GOSSIP_ICON_CHAT, name.str().c_str(), GOSSIP_SENDER_EQUIP_AUTOEQUIP_EQUIP + k, GOSSIP_ACTION_INFO_DEF + item->GetGUID().GetCounter()); + ++counter; + found = true; + break; + } + } + + if (found) + continue; + + for (uint8 i = INVENTORY_SLOT_BAG_START; i != INVENTORY_SLOT_BAG_END; ++i) + { + if (Bag const* pBag = player->GetBagByPos(i)) + { + for (uint32 j = 0; j != pBag->GetBagSize(); ++j) + { + item = player->GetItemByPos(i, j); + if (item && item->GetGUID().GetCounter() == (*itr)) + { + uint8 k = 0; + for (; k != BOT_INVENTORY_SIZE; ++k) + { + if (_canEquip(item->GetTemplate(), k, false, item)) + { + //workaround for double slots + //if first slot is occupied and second slot is vacant use second slot + if (k == BOT_SLOT_FINGER1 || k == BOT_SLOT_TRINKET1) + if (_equips[k] != nullptr && _canEquip(item->GetTemplate(), k + 1, false, item)) + ++k; + break; + } + } + + std::ostringstream name; + _AddItemLink(player, item, name); + if (BotMgr::SendEquipListItems()) + BotWhisper(name.str(), player); + AddGossipItemFor(player, GOSSIP_ICON_CHAT, name.str().c_str(), GOSSIP_SENDER_EQUIP_AUTOEQUIP_EQUIP + k, GOSSIP_ACTION_INFO_DEF + item->GetGUID().GetCounter()); + ++counter; + found = true; + break; + } + } + } + + if (found) + break; + } + + if (found) + continue; + } + } + + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_BACK), GOSSIP_SENDER_EQUIPMENT, GOSSIP_ACTION_INFO_DEF + 2); + break; + } + case GOSSIP_SENDER_EQUIP_RESET: //equips change s4a: reset equipment + { + if (_resetEquipment(action - GOSSIP_ACTION_INFO_DEF, player->GetGUID())){} + return OnGossipSelect(player, creature, GOSSIP_SENDER_EQUIPMENT, GOSSIP_ACTION_INFO_DEF + 1); + } + //equips change s4b: Equip item + //base is GOSSIP_SENDER_EQUIP + 0...1...2... etc. + case GOSSIP_SENDER_EQUIP_MHAND: //0 - 1 main hand + case GOSSIP_SENDER_EQUIP_OHAND: //1 - 1 off hand + case GOSSIP_SENDER_EQUIP_RANGED: //2 - 1 ranged + case GOSSIP_SENDER_EQUIP_HEAD: //3 - 1 head + case GOSSIP_SENDER_EQUIP_SHOULDERS: //4 - 1 shoulders + case GOSSIP_SENDER_EQUIP_CHEST: //5 - 1 chest + case GOSSIP_SENDER_EQUIP_WAIST: //6 - 1 waist + case GOSSIP_SENDER_EQUIP_LEGS: //7 - 1 legs + case GOSSIP_SENDER_EQUIP_FEET: //8 - 1 feet + case GOSSIP_SENDER_EQUIP_WRIST: //9 - 1 wrist + case GOSSIP_SENDER_EQUIP_HANDS: //10 - 1 hands + case GOSSIP_SENDER_EQUIP_BACK: //11 - 1 back + case GOSSIP_SENDER_EQUIP_BODY: //12 - 1 body + case GOSSIP_SENDER_EQUIP_FINGER1: //13 - 1 finger + case GOSSIP_SENDER_EQUIP_FINGER2: //14 - 1 finger + case GOSSIP_SENDER_EQUIP_TRINKET1: //15 - 1 trinket + case GOSSIP_SENDER_EQUIP_TRINKET2: //16 - 1 trinket + case GOSSIP_SENDER_EQUIP_NECK: //17 - 1 neck + { + Item* item = nullptr; + uint32 guidLow = action - GOSSIP_ACTION_INFO_DEF; + + bool found = false; + for (uint8 i = INVENTORY_SLOT_ITEM_START; i != INVENTORY_SLOT_ITEM_END; ++i) + { + item = player->GetItemByPos(INVENTORY_SLOT_BAG_0, i); + if (item && item->GetGUID().GetCounter() == guidLow) + { + found = true; + break; + } + } + + if (!found) + { + for (uint8 i = INVENTORY_SLOT_BAG_START; i != INVENTORY_SLOT_BAG_END; ++i) + { + if (Bag const* pBag = player->GetBagByPos(i)) + { + for (uint32 j = 0; j != pBag->GetBagSize(); ++j) + { + item = player->GetItemByPos(i, j); + if (item && item->GetGUID().GetCounter() == guidLow) + { + found = true; + break; + } + } + } + + if (found) + break; + } + } + + if (found && _equip(sender - GOSSIP_SENDER_EQUIP, item, player->GetGUID())){} + return OnGossipSelect(player, creature, GOSSIP_SENDER_EQUIPMENT, GOSSIP_ACTION_INFO_DEF + 1); + } + case GOSSIP_SENDER_EQUIPMENT_BANK_DEPOSIT_ITEM: + { + ObjectGuid::LowType itemGuidLow = action - GOSSIP_ACTION_INFO_DEF; + Item* item = player->GetItemByGuid(ObjectGuid::Create(itemGuidLow)); + if (!item) + { + TC_LOG_ERROR("npcbots", "GOSSIP_SENDER_EQUIPMENT_BANK_DEPOSIT_ITEM: item {} not found on player {}! Cheater?", + itemGuidLow, player->GetName()); + break; + } + + BotDataMgr::DepositBotBankItem(player->GetGUID(), item); + player->MoveItemFromInventory(item->GetBagSlot(), item->GetSlot(), true); + + action = GOSSIP_ACTION_INFO_DEF; //return to page 0 + //break; + [[fallthrough]]; + } + case GOSSIP_SENDER_EQUIPMENT_BANK_DEPOSIT: + { + subMenu = true; + uint32 page = action - GOSSIP_ACTION_INFO_DEF; + uint32 items_per_page = BOT_GOSSIP_MAX_ITEMS - 3; // prev page, back, next page + uint32 counter = 0; + uint32 can_add_count = 0; + uint32 k = 0; + + static const auto is_bot_equippable_item = [](ItemTemplate const* proto) { + switch (proto->InventoryType) + { + case INVTYPE_NON_EQUIP: case INVTYPE_BAG: case INVTYPE_TABARD: case INVTYPE_AMMO: case INVTYPE_QUIVER: + return false; + default: + return true; + } + }; + + //backpack + for (uint8 i = INVENTORY_SLOT_ITEM_START; i != INVENTORY_SLOT_ITEM_END && can_add_count <= items_per_page; ++i) + { + if (Item const* pItem = player->GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + { + if (is_bot_equippable_item(pItem->GetTemplate())) + { + ++k; + if (k <= page * items_per_page) + continue; + ++can_add_count; + if (counter >= items_per_page) + continue; + ++counter; + std::ostringstream name; + _AddItemLink(player, pItem, name); + AddGossipItemFor(player, GOSSIP_ICON_CHAT, name.str(), GOSSIP_SENDER_EQUIPMENT_BANK_DEPOSIT_ITEM, GOSSIP_ACTION_INFO_DEF + pItem->GetGUID().GetCounter()); + } + } + } + + //other bags + for (uint8 i = INVENTORY_SLOT_BAG_START; i != INVENTORY_SLOT_BAG_END && can_add_count <= items_per_page; ++i) + { + Bag const* pBag = player->GetBagByPos(i); + if (!pBag) + continue; + for (uint32 j = 0; j != pBag->GetBagSize() && can_add_count <= items_per_page; ++j) + { + Item const* pItem = player->GetItemByPos(i, j); + if (!pItem) + continue; + if (is_bot_equippable_item(pItem->GetTemplate())) + { + ++k; + if (k <= page * items_per_page) + continue; + ++can_add_count; + if (counter >= items_per_page) + continue; + ++counter; + std::ostringstream name; + _AddItemLink(player, pItem, name); + AddGossipItemFor(player, GOSSIP_ICON_CHAT, name.str(), GOSSIP_SENDER_EQUIPMENT_BANK_DEPOSIT_ITEM, GOSSIP_ACTION_INFO_DEF + pItem->GetGUID().GetCounter()); + } + } + } + + if (page > 0) + AddGossipItemFor(player, GOSSIP_ICON_TALK, LocalizedNpcText(player, BOT_TEXT_PREVIOUS_PAGE), GOSSIP_SENDER_EQUIPMENT_BANK_DEPOSIT, action - 1); + if (can_add_count > items_per_page) + AddGossipItemFor(player, GOSSIP_ICON_TALK, LocalizedNpcText(player, BOT_TEXT_NEXT_PAGE), GOSSIP_SENDER_EQUIPMENT_BANK_DEPOSIT, action + 1); + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_BACK), GOSSIP_SENDER_EQUIPMENT_BANK_MENU, GOSSIP_ACTION_INFO_DEF + 1); + break; + } + case GOSSIP_SENDER_EQUIPMENT_BANK_WITHDRAW_ITEM: + { + ObjectGuid::LowType itemGuidLow = action - GOSSIP_ACTION_INFO_DEF; + + //BotBankItemContainer const& botBankItems = BotDataMgr::GetBotBankItems(player->GetGUID()); + //Item const* item = std::find_if(std::cbegin(botBankItems), std::cend(botBankItems), [guidLow = itemGuidLow](Item const* item) { + // return item->GetGUID().GetCounter() == guidLow; + //}); + Item* item = BotDataMgr::WithdrawBotBankItem(player->GetGUID(), itemGuidLow); + if (!item) + { + TC_LOG_ERROR("npcbots", "GOSSIP_SENDER_EQUIPMENT_BANK_WITHDRAW_ITEM: item {} not found on player {}! Cheater?", + itemGuidLow, player->GetName()); + break; + } + + ItemPosCountVec dest; + uint32 no_space = 0; + InventoryResult msg = player->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, item->GetEntry(), 1, &no_space); + if (msg != EQUIP_ERR_OK) + { + std::ostringstream istr; + _AddItemLink(player, item, istr, false); + ChatHandler ch(player->GetSession()); + ch.PSendSysMessage(LocalizedNpcText(player, BOT_TEXT_CANT_UNEQUIP_MAILING).c_str(), istr.str().c_str()); + + item->SetOwnerGUID(player->GetGUID()); + + CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); + item->FSetState(ITEM_CHANGED); + item->SaveToDB(trans); + MailDraft(istr.str(), "").AddItem(item).SendMailTo(trans, MailReceiver(player), MailSender(me)); + CharacterDatabase.CommitTransaction(trans); + + player->SendEquipError(msg, nullptr, nullptr, item->GetEntry()); + } + else + { + Item* pItem = player->StoreItem(dest, item, true); + player->SendNewItem(pItem, 1, true, false, false); + } + + action = GOSSIP_ACTION_INFO_DEF; //return to page 0 + //break; + [[fallthrough]]; + } + case GOSSIP_SENDER_EQUIPMENT_BANK_WITHDRAW: + { + uint32 page = action - GOSSIP_ACTION_INFO_DEF; + uint32 items_per_page = BOT_GOSSIP_MAX_ITEMS - 3; // page prev, page next, back + uint32 counter = 0; + + BotBankItemContainer const* botBankItems = BotDataMgr::GetBotBankItems(player->GetGUID()); + if (!botBankItems || botBankItems->empty()) + { + BotWhisper(LocalizedNpcText(player, BOT_TEXT_BANK_IS_EMPTY), player); + return OnGossipSelect(player, me, GOSSIP_SENDER_EQUIPMENT_BANK_MENU, action); + break; + } + + subMenu = true; + + BotBankItemContainer::const_iterator bcit = botBankItems->cbegin(); + size_t i = 0; + for (; i < page * items_per_page && i < botBankItems->size(); ++i, ++bcit); + for (; i < botBankItems->size() && counter < items_per_page; ++i, ++bcit) + { + Item const* item = *bcit; + ++counter; + std::ostringstream name; + _AddItemLink(player, item, name); + ItemTemplate const* proto = item->GetTemplate(); + uint8 slot = BOT_SLOT_BODY; + if (GetBotClass() == BOT_CLASS_HUNTER) + { + if (_canEquip(proto, BOT_SLOT_RANGED, true)) + slot = BOT_SLOT_RANGED; + else if (_canEquip(proto, BOT_SLOT_MAINHAND, true)) + slot = BOT_SLOT_MAINHAND; + else if (_canEquip(proto, BOT_SLOT_OFFHAND, true)) + slot = BOT_SLOT_OFFHAND; + } + else if (GetBotClass() == BOT_CLASS_WARRIOR && _canEquip(proto, BOT_SLOT_MAINHAND, true)) + slot = BOT_SLOT_MAINHAND; + + name << " GS: " << uint32(CalculateItemGearScore(me->GetEntry(), me->GetLevel(), GetBotClass(), GetSpec(), slot, proto)); + AddGossipItemFor(player, GOSSIP_ICON_CHAT, name.str(), GOSSIP_SENDER_EQUIPMENT_BANK_WITHDRAW_ITEM, GOSSIP_ACTION_INFO_DEF + item->GetGUID().GetCounter()); + } + + if (page > 0) + AddGossipItemFor(player, GOSSIP_ICON_TALK, LocalizedNpcText(player, BOT_TEXT_PREVIOUS_PAGE), GOSSIP_SENDER_EQUIPMENT_BANK_WITHDRAW, action - 1); + if (botBankItems->size() > (page + 1) * items_per_page) + AddGossipItemFor(player, GOSSIP_ICON_TALK, LocalizedNpcText(player, BOT_TEXT_NEXT_PAGE), GOSSIP_SENDER_EQUIPMENT_BANK_WITHDRAW, action + 1); + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_BACK), GOSSIP_SENDER_EQUIPMENT_BANK_MENU, GOSSIP_ACTION_INFO_DEF + 1); + break; + } + case GOSSIP_SENDER_EQUIPMENT_BANK_MENU: + { + subMenu = true; + + AddGossipItemFor(player, GOSSIP_ICON_TALK, LocalizedNpcText(player, BOT_TEXT_DEPOSIT_ITEMS), GOSSIP_SENDER_EQUIPMENT_BANK_DEPOSIT, GOSSIP_ACTION_INFO_DEF + 0); + AddGossipItemFor(player, GOSSIP_ICON_TALK, LocalizedNpcText(player, BOT_TEXT_WITHDRAW_ITEMS), GOSSIP_SENDER_EQUIPMENT_BANK_WITHDRAW, GOSSIP_ACTION_INFO_DEF + 0); + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_BACK), 1, GOSSIP_ACTION_INFO_DEF + 1); + + break; + } + case GOSSIP_SENDER_ROLES_MAIN_TOGGLE: //ROLES 2: set/unset + { + ToggleRole(action - GOSSIP_ACTION_INFO_DEF, false); + + [[fallthrough]]; + //break; + } + case GOSSIP_SENDER_ROLES_MAIN: //ROLES 1: list + { + subMenu = true; + + if (IsHumanoidClass(_botclass)) + AddGossipItemFor(player, GOSSIP_ICON_TALK, LocalizedNpcText(player, BOT_TEXT_GATHERING) + "...", GOSSIP_SENDER_ROLES_GATHERING, GOSSIP_ACTION_INFO_DEF + 1); + AddGossipItemFor(player, GOSSIP_ICON_TALK, LocalizedNpcText(player, BOT_TEXT_LOOTING) + "...", GOSSIP_SENDER_ROLES_LOOTING, GOSSIP_ACTION_INFO_DEF + 2); + + uint32 role = BOT_ROLE_TANK; + for (; role != BOT_MAX_ROLE; role <<= 1) + { + if (!(role & BOT_ROLE_MASK_MAIN)) //hidden + continue; + if (role == BOT_ROLE_HEAL && !IsHealingClass(_botclass)) + continue; + + AddGossipItemFor(player, GetRoleIcon(role), LocalizedNpcText(player, GetRoleString(role)), GOSSIP_SENDER_ROLES_MAIN_TOGGLE, GOSSIP_ACTION_INFO_DEF + role); + } + + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_BACK), 1, GOSSIP_ACTION_INFO_DEF + 1); + + break; + } + case GOSSIP_SENDER_ROLES_GATHERING_TOGGLE: + { + ToggleRole(action - GOSSIP_ACTION_INFO_DEF, false); + + [[fallthrough]]; + //break; + } + case GOSSIP_SENDER_ROLES_GATHERING: + { + subMenu = true; + + uint32 role = BOT_ROLE_GATHERING_MINING; + for (; role != BOT_MAX_ROLE; role <<= 1) + { + if (!(role & BOT_ROLE_MASK_GATHERING)) //hidden + continue; + + AddGossipItemFor(player, GetRoleIcon(role), LocalizedNpcText(player, GetRoleString(role)), GOSSIP_SENDER_ROLES_GATHERING_TOGGLE, GOSSIP_ACTION_INFO_DEF + role); + } + + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_BACK), GOSSIP_SENDER_ROLES_MAIN, GOSSIP_ACTION_INFO_DEF + 1); + + break; + } + case GOSSIP_SENDER_ROLES_LOOTING_TOGGLE: + { + ToggleRole(action - GOSSIP_ACTION_INFO_DEF, false); + + [[fallthrough]]; + //break; + } + case GOSSIP_SENDER_ROLES_LOOTING: + { + subMenu = true; + + uint32 role = BOT_ROLE_AUTOLOOT; + for (; role != BOT_MAX_ROLE; role <<= 1) + { + if (!(role & (BOT_ROLE_AUTOLOOT | BOT_ROLE_MASK_LOOTING))) + continue; + + AddGossipItemFor(player, GetRoleIcon(role), LocalizedNpcText(player, GetRoleString(role)), GOSSIP_SENDER_ROLES_LOOTING_TOGGLE, GOSSIP_ACTION_INFO_DEF + role); + } + + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_BACK), GOSSIP_SENDER_ROLES_MAIN, GOSSIP_ACTION_INFO_DEF + 1); + + break; + } + case GOSSIP_SENDER_ABILITIES_USE: + { + if (uint32 basespell = action - GOSSIP_ACTION_INFO_DEF) + //if (CheckBotCast(me, basespell, me->GetBotClass()) == SPELL_CAST_OK) + if (IsSpellReady(basespell, lastdiff, true)) + doCast(player, GetSpell(basespell)); + + //break; + action = GOSSIP_ACTION_INFO_DEF; + [[fallthrough]]; + } + case GOSSIP_SENDER_ABILITIES: + { + subMenu = true; + + if (HasAbilitiesSpecifics()) + AddGossipItemFor(player, GOSSIP_ICON_TALK, LocalizedNpcText(player, BOT_TEXT_ABILITIES_STATUS) + "...", GOSSIP_SENDER_ABILITIES_SPECIFICS_LIST, GOSSIP_ACTION_INFO_DEF + 1); + AddGossipItemFor(player, GOSSIP_ICON_TALK, LocalizedNpcText(player, BOT_TEXT_ALLOWED_ABILITIES) + "...", GOSSIP_SENDER_ABILITIES_USAGE_LIST, GOSSIP_ACTION_INFO_DEF + 2); + + uint32 basespell; + SpellInfo const* spellInfo; + BotSpellMap const& myspells = GetSpellMap(); + for (BotSpellMap::const_iterator itr = myspells.begin(); itr != myspells.end(); ++itr) + { + basespell = itr->first; //always valid + if (!CanUseManually(basespell)) continue; + if (!IsSpellReady(basespell, lastdiff, false)) continue; + spellInfo = sSpellMgr->GetSpellInfo(basespell); //always valid + + std::ostringstream name; + name << LocalizedNpcText(player, BOT_TEXT_USE_); + _AddSpellLink(player, spellInfo, name); + AddGossipItemFor(player, GOSSIP_ICON_TRAINER, name.str().c_str(), GOSSIP_SENDER_ABILITIES_USE, GOSSIP_ACTION_INFO_DEF + basespell); + } + + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_UPDATE), sender, action); + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_BACK), 1, GOSSIP_ACTION_INFO_DEF + 2); + + break; + } + case GOSSIP_SENDER_ABILITIES_SPECIFICS_LIST: + { + subMenu = true; + + std::list specList; + FillAbilitiesSpecifics(player, specList); + for (std::list::const_iterator itr = specList.begin(); itr != specList.end(); ++itr) + AddGossipItemFor(player, GOSSIP_ICON_CHAT, *itr, GOSSIP_SENDER_ABILITIES, GOSSIP_ACTION_INFO_DEF); + + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_BACK), GOSSIP_SENDER_ABILITIES, GOSSIP_ACTION_INFO_DEF + 1); + + break; + } + case GOSSIP_SENDER_ABILITIES_USAGE_TOGGLE_DAMAGE: + case GOSSIP_SENDER_ABILITIES_USAGE_TOGGLE_CC: + case GOSSIP_SENDER_ABILITIES_USAGE_TOGGLE_HEAL: + case GOSSIP_SENDER_ABILITIES_USAGE_TOGGLE_SUPPORT: + { + NpcBotData* npcBotData = const_cast(BotDataMgr::SelectNpcBotData(me->GetEntry())); + + uint32 basespell = action - GOSSIP_ACTION_INFO_DEF; + BotSpellMap const& myspells = GetSpellMap(); + for (BotSpellMap::const_iterator itr = myspells.begin(); itr != myspells.end(); ++itr) + { + if (itr->first == basespell) + { + itr->second->enabled = !itr->second->enabled; + if (itr->second->enabled) + npcBotData->disabled_spells.erase(basespell); + else + npcBotData->disabled_spells.insert(basespell); + + _saveDisabledSpells = true; + break; + } + } + + uint32 newSender; + switch (sender) + { + case GOSSIP_SENDER_ABILITIES_USAGE_TOGGLE_DAMAGE: + newSender = GOSSIP_SENDER_ABILITIES_USAGE_LIST_DAMAGE; break; + case GOSSIP_SENDER_ABILITIES_USAGE_TOGGLE_CC: + newSender = GOSSIP_SENDER_ABILITIES_USAGE_LIST_CC; break; + case GOSSIP_SENDER_ABILITIES_USAGE_TOGGLE_HEAL: + newSender = GOSSIP_SENDER_ABILITIES_USAGE_LIST_HEAL; break; + case GOSSIP_SENDER_ABILITIES_USAGE_TOGGLE_SUPPORT: + newSender = GOSSIP_SENDER_ABILITIES_USAGE_LIST_SUPPORT; break; + default: + ASSERT(false); + } + sender = newSender; + } + [[fallthrough]]; + case GOSSIP_SENDER_ABILITIES_USAGE_LIST_DAMAGE: + case GOSSIP_SENDER_ABILITIES_USAGE_LIST_CC: + case GOSSIP_SENDER_ABILITIES_USAGE_LIST_HEAL: + case GOSSIP_SENDER_ABILITIES_USAGE_LIST_SUPPORT: + { + subMenu = true; + + uint32 toggleSender; + std::vector const* myspells; + switch (sender) + { + case GOSSIP_SENDER_ABILITIES_USAGE_LIST_DAMAGE: + toggleSender = GOSSIP_SENDER_ABILITIES_USAGE_TOGGLE_DAMAGE; + myspells = GetDamagingSpellsList(); + break; + case GOSSIP_SENDER_ABILITIES_USAGE_LIST_CC: + toggleSender = GOSSIP_SENDER_ABILITIES_USAGE_TOGGLE_CC; + myspells = GetCCSpellsList(); + break; + case GOSSIP_SENDER_ABILITIES_USAGE_LIST_HEAL: + toggleSender = GOSSIP_SENDER_ABILITIES_USAGE_TOGGLE_HEAL; + myspells = GetHealingSpellsList(); + break; + case GOSSIP_SENDER_ABILITIES_USAGE_LIST_SUPPORT: + toggleSender = GOSSIP_SENDER_ABILITIES_USAGE_TOGGLE_SUPPORT; + myspells = GetSupportSpellsList(); + break; + default: + ASSERT(false); + } + + ASSERT(myspells); + + uint32 counter = 0; + SpellInfo const* spellInfo; + for (std::vector::const_iterator itr = myspells->begin(); itr != myspells->end(); ++itr) + { + if (!HasSpell(*itr)) //not init'ed (cannot be used) + continue; + + spellInfo = sSpellMgr->GetSpellInfo(*itr); //always valid + std::ostringstream name; + _AddSpellLink(player, spellInfo, name); + + GossipOptionIcon icon = (GetSpell(*itr) != 0) ? BOT_ICON_ON : BOT_ICON_OFF; + AddGossipItemFor(player, icon, name.str().c_str(), toggleSender, GOSSIP_ACTION_INFO_DEF + *itr); + if (++counter >= BOT_GOSSIP_MAX_ITEMS - 1) //back + { + TC_LOG_ERROR("scripts", "bot_ai: gossip abilities list overflow with sender {} for bot class {}!", + sender, uint32(_botclass)); + break; + } + } + + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_BACK), GOSSIP_SENDER_ABILITIES_USAGE_LIST, GOSSIP_ACTION_INFO_DEF + 2); + break; + } + case GOSSIP_SENDER_ABILITIES_USAGE_LIST: + { + subMenu = true; + + if (GetDamagingSpellsList()) + AddGossipItemFor(player, GOSSIP_ICON_TALK, LocalizedNpcText(player, BOT_TEXT_DAMAGE) + "...", GOSSIP_SENDER_ABILITIES_USAGE_LIST_DAMAGE, GOSSIP_ACTION_INFO_DEF + 1); + if (GetCCSpellsList()) + AddGossipItemFor(player, GOSSIP_ICON_TALK, LocalizedNpcText(player, BOT_TEXT_CONTROL) + "...", GOSSIP_SENDER_ABILITIES_USAGE_LIST_CC, GOSSIP_ACTION_INFO_DEF + 2); + if (GetHealingSpellsList()) + AddGossipItemFor(player, GOSSIP_ICON_TALK, LocalizedNpcText(player, BOT_TEXT_HEAL) + "...", GOSSIP_SENDER_ABILITIES_USAGE_LIST_HEAL, GOSSIP_ACTION_INFO_DEF + 3); + if (GetSupportSpellsList()) + AddGossipItemFor(player, GOSSIP_ICON_TALK, LocalizedNpcText(player, BOT_TEXT_OTHER) + "...", GOSSIP_SENDER_ABILITIES_USAGE_LIST_SUPPORT, GOSSIP_ACTION_INFO_DEF + 4); + + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_BACK), GOSSIP_SENDER_ABILITIES, GOSSIP_ACTION_INFO_DEF + 5); + break; + } + case GOSSIP_SENDER_SPEC_SET: + { + uint8 newSpec = action - GOSSIP_ACTION_INFO_DEF; + + if (newSpec != _spec && newSpec >= BOT_SPEC_BEGIN && newSpec <= BOT_SPEC_END) + { + _newspec = newSpec; + me->CastSpell(me, ACTIVATE_SPEC, false); + BotWhisper(LocalizedNpcText(player, BOT_TEXT_CHANGING_MY_SPEC_TO_) + LocalizedNpcText(player, TextForSpec(_newspec))); + break; + } + } + [[fallthrough]]; + case GOSSIP_SENDER_SPEC: + { + subMenu = true; + + uint8 specIndex; + switch (_botclass) + { + case BOT_CLASS_WARRIOR: specIndex = BOT_SPEC_WARRIOR_ARMS; break; + case BOT_CLASS_PALADIN: specIndex = BOT_SPEC_PALADIN_HOLY; break; + case BOT_CLASS_HUNTER: specIndex = BOT_SPEC_HUNTER_BEASTMASTERY; break; + case BOT_CLASS_ROGUE: specIndex = BOT_SPEC_ROGUE_ASSASINATION; break; + case BOT_CLASS_PRIEST: specIndex = BOT_SPEC_PRIEST_DISCIPLINE; break; + case BOT_CLASS_DEATH_KNIGHT: specIndex = BOT_SPEC_DK_BLOOD; break; + case BOT_CLASS_SHAMAN: specIndex = BOT_SPEC_SHAMAN_ELEMENTAL; break; + case BOT_CLASS_MAGE: specIndex = BOT_SPEC_MAGE_ARCANE; break; + case BOT_CLASS_WARLOCK: specIndex = BOT_SPEC_WARLOCK_AFFLICTION; break; + case BOT_CLASS_DRUID: specIndex = BOT_SPEC_DRUID_BALANCE; break; + default: + TC_LOG_ERROR("entities.unit", "bot_ai:GOSSIP_SENDER_SPEC called for class {} with no specs!", uint32(_botclass)); + return true; + } + + for (uint8 i = specIndex; i < specIndex + 3; ++i) + { + GossipOptionIcon icon = (GetSpec() == i) ? BOT_ICON_ON : BOT_ICON_OFF; + AddGossipItemFor(player, icon, LocalizedNpcText(player, TextForSpec(i)), GOSSIP_SENDER_SPEC_SET, GOSSIP_ACTION_INFO_DEF + i); + } + + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_BACK), 1, GOSSIP_ACTION_INFO_DEF + 2); + break; + } + case GOSSIP_SENDER_USEITEM_USE: + { + if (uint32 guidLow = action - GOSSIP_ACTION_INFO_DEF) + { + Item const* item = nullptr; + bool found = false; + for (uint8 i = INVENTORY_SLOT_ITEM_START; i != INVENTORY_SLOT_ITEM_END; ++i) + { + item = player->GetItemByPos(INVENTORY_SLOT_BAG_0, i); + if (item && item->GetGUID().GetCounter() == guidLow) + { + found = true; + break; + } + } + + if (!found) + { + for (uint8 i = INVENTORY_SLOT_BAG_START; i != INVENTORY_SLOT_BAG_END; ++i) + { + if (Bag const* pBag = player->GetBagByPos(i)) + { + for (uint32 j = 0; j != pBag->GetBagSize(); ++j) + { + item = player->GetItemByPos(i, j); + if (item && item->GetGUID().GetCounter() == guidLow) + { + found = true; + break; + } + } + } + + if (found) + break; + } + } + + if (found) + { + ItemTemplate const* proto = item->GetTemplate(); + // Learning (483 / 55884) + if (proto->Spells[0].SpellId == 483 || proto->Spells[0].SpellId == 55884) + break; + + // cast item spell + SpellCastTargets targets; + targets.SetUnitTarget(me); + _castBotItemUseSpell(item, targets); + } + } + + //break; + action = GOSSIP_ACTION_INFO_DEF; + [[fallthrough]]; + } + case GOSSIP_SENDER_USEITEM: + { + subMenu = true; + + uint32 counter = 0; + uint32 maxcounter = BOT_GOSSIP_MAX_ITEMS - 2; //update, back + Item const* item; + + static const auto is_consumable_item = [](Item const* item, Creature const* bot) { + if (ItemTemplate const* proto = item ? item->GetTemplate() : nullptr) + { + if (!(proto->Class != ITEM_CLASS_WEAPON && proto->Class != ITEM_CLASS_ARMOR && + (proto->AllowableClass == 0 || (proto->AllowableClass & (1 << (bot->GetBotClass() - 1)))) && + proto->RequiredSkill == 0 && proto->RequiredSpell == 0 && bot->GetLevel() >= proto->RequiredLevel)) + return false; + bool has_spell = false; + for (auto const& ispell: proto->Spells) + { + if (ispell.SpellId != 0) + { + if (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(ispell.SpellId)) + { + if (spellInfo->IsPassive()) + continue; + bool valid_effect = true; + for (uint8 i = EFFECT_0; i < MAX_SPELL_EFFECTS; ++i) + { + SpellEffectInfo const& effect = spellInfo->GetEffect(SpellEffIndex(i)); + if (!effect.IsEffect()) + continue; + if (effect.TargetA.GetTarget() != TARGET_UNIT_CASTER || effect.TargetB.GetTarget() != 0) + valid_effect = false; + else + { + switch (effect.Effect) + { + case SPELL_EFFECT_SUMMON: + case SPELL_EFFECT_CREATE_ITEM: + valid_effect = false; + break; + default: + break; + } + } + } + if (!valid_effect) + continue; + has_spell = true; + } + } + } + return has_spell; + } + return false; + }; + + for (uint8 i = INVENTORY_SLOT_ITEM_START; i != INVENTORY_SLOT_ITEM_END; ++i) + { + item = player->GetItemByPos(INVENTORY_SLOT_BAG_0, i); + if (is_consumable_item(item, me)) + { + std::ostringstream name; + _AddItemLink(player, item, name); + AddGossipItemFor(player, GOSSIP_ICON_CHAT, name.str().c_str(), GOSSIP_SENDER_USEITEM_USE, GOSSIP_ACTION_INFO_DEF + item->GetGUID().GetCounter()); + ++counter; //no need to check max counter here + } + } + + for (uint8 i = INVENTORY_SLOT_BAG_START; i != INVENTORY_SLOT_BAG_END; ++i) + { + if (Bag const* pBag = player->GetBagByPos(i)) + { + for (uint32 j = 0; j != pBag->GetBagSize() && counter < maxcounter; ++j) + { + item = player->GetItemByPos(i, j); + if (is_consumable_item(item, me)) + { + std::ostringstream name; + _AddItemLink(player, item, name); + AddGossipItemFor(player, GOSSIP_ICON_CHAT, name.str().c_str(), GOSSIP_SENDER_USEITEM_USE, GOSSIP_ACTION_INFO_DEF + item->GetGUID().GetCounter()); + ++counter; + } + } + } + } + + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_UPDATE), sender, action); + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_BACK), 1, GOSSIP_ACTION_INFO_DEF + 1); + + break; + } + case GOSSIP_SENDER_HIRE: + { + int32 reason = action - GOSSIP_ACTION_INFO_DEF; + if (!reason) + { + if (_ownerGuid) + { + //std::ostringstream ostr; + //std::string name; + //ostr << "Go away. I serve my master "; + //if (sCharacterCache->GetCharacterNameByGuid(ObjectGuid(HighGuid::Player, _ownerGuid), name)) + // ostr << name; + //else + // ostr << "unknown (" << _ownerGuid << ')'; + //BotWhisper(ostr.str().c_str(), player); + ChatHandler ch(player->GetSession()); + ch.PSendSysMessage(LocalizedNpcText(player, BOT_TEXT_HIREFAIL_OWNED).c_str(), me->GetName().c_str()); + break; + } + + uint8 minlvl = BotDataMgr::GetMinLevelForBotClass(_botclass); + if (player->GetLevel() < minlvl) + { + ChatHandler ch(player->GetSession()); + uint32 locStringId; + switch (minlvl) + { + case 55: locStringId = BOT_TEXT_HIREFAIL_LVL55; break; + case 20: locStringId = BOT_TEXT_HIREFAIL_LVL20; break; + case 40: locStringId = BOT_TEXT_HIREFAIL_LVL40; break; + case 60: locStringId = BOT_TEXT_HIREFAIL_LVL60; break; + default: + TC_LOG_ERROR("scripts", "No message exists for min class level {}!", uint32(minlvl)); + locStringId = BOT_TEXT_HIREFAIL_LVL60; + break; + } + switch (_botclass) + { + case BOT_CLASS_DEATH_KNIGHT: + BotWhisper(LocalizedNpcText(player, BOT_TEXT_HIREDENY_DK), player); + break; + case BOT_CLASS_SPHYNX: + me->TextEmote((me->GetName() + LocalizedNpcText(player, BOT_TEXT_HIREDENY_SPHYNX)).c_str()); + break; + case BOT_CLASS_ARCHMAGE: + BotWhisper(LocalizedNpcText(player, BOT_TEXT_HIREDENY_ARCHMAGE), player); + break; + } + ch.PSendSysMessage(LocalizedNpcText(player, locStringId).c_str(), me->GetName().c_str()); + break; + } + + if (uint32 maxBotsPerAccount = BotMgr::GetMaxAccountBots()) + { + uint32 accountBotsCount = BotDataMgr::GetAccountBotsCount(player->GetSession()->GetAccountId()); + if (accountBotsCount >= maxBotsPerAccount) + { + ChatHandler ch(player->GetSession()); + ch.PSendSysMessage(LocalizedNpcText(player, BOT_TEXT_HIREFAIL_MAXBOTS_ACCOUNT).c_str(), accountBotsCount, maxBotsPerAccount); + break; + } + } + + if (SetBotOwner(player)) + { + if (_botclass == BOT_CLASS_SPHYNX) + { + std::string msg1 = me->GetName() + LocalizedNpcText(player, BOT_TEXT_HIRE_EMOTE_SPHYNX) + player->GetName(); + me->TextEmote(msg1.c_str()); + } + else + BotWhisper(LocalizedNpcText(player, BOT_TEXT_HIRE_SUCCESS), player); + } + else + BotSay("...", player); + } + else if (reason == -1) + { + me->SetFaction(14); + if (botPet) + botPet->SetFaction(14); + BotYell(LocalizedNpcText(player, BOT_TEXT_DIE), player); + me->Attack(player, true); + break; + } + else + { + ChatHandler ch(player->GetSession()); + switch (reason) + { + case 1: //has owner + { + std::ostringstream ostr; + std::string name; + ostr << LocalizedNpcText(player, BOT_TEXT_HIREDENY_MY_MASTER_IS_); + if (sCharacterCache->GetCharacterNameByGuid(ObjectGuid(HighGuid::Player, _ownerGuid), name)) + ostr << name; + else + ostr << LocalizedNpcText(player, BOT_TEXT_UNKNOWN) + " (" << _ownerGuid << ')'; + BotWhisper(ostr.str().c_str(), player); + ch.PSendSysMessage(LocalizedNpcText(player, BOT_TEXT_HIREFAIL_OWNED).c_str(), me->GetName().c_str()); + break; + } + case 2: //max npcbots exceed + ch.PSendSysMessage(LocalizedNpcText(player, BOT_TEXT_HIREFAIL_MAXBOTS).c_str(), BotMgr::GetMaxNpcBots(player->GetLevel())); + BotSay("...", player); + break; + case 3: //not enough money + { + std::string str = LocalizedNpcText(player, BOT_TEXT_HIREFAIL_COST) + " ("; + str += BotMgr::GetNpcBotCostStr(player->GetLevel(), _botclass); + str += ")!"; + ch.SendSysMessage(str.c_str()); + player->SendBuyError(BUY_ERR_NOT_ENOUGHT_MONEY, 0, 0, 0); + BotSay("...", player); + break; + } + case 4: //class bots exceed + { + uint8 count = 0; + BotMap const* map = player->GetBotMgr()->GetBotMap(); + for (BotMap::const_iterator itr = map->begin(); itr != map->end(); ++itr) + if (itr->second->GetBotClass() == GetBotClass()) + ++count; + + ch.PSendSysMessage(LocalizedNpcText(player, BOT_TEXT_HIREFAIL_MAXCLASSBOTS).c_str(), count, BotMgr::GetMaxClassBots()); + BotSay("...", player); + break; + } + default: + break; + } + } + break; + } + case GOSSIP_SENDER_DISMISS: + { + BotMgr* mgr = player->GetBotMgr(); + ASSERT(mgr); + + //send items to owner -- Unequip all + bool abort = false; + for (uint8 i = BOT_SLOT_MAINHAND; i != BOT_INVENTORY_SIZE; ++i) + { + if (!(i <= BOT_SLOT_RANGED ? _resetEquipment(i, player->GetGUID()) : _unequip(i, player->GetGUID()))) + { + ChatHandler ch(player->GetSession()); + ch.PSendSysMessage(LocalizedNpcText(player, BOT_TEXT_CANT_DISMISS_EQUIPMENT).c_str(), + uint32(i), LocalizedNpcText(player, BOT_TEXT_SLOT_MH + i).c_str()); + abort = true; + break; + } + } + + if (abort) + break; + + mgr->RemoveBot(me->GetGUID(), BOT_REMOVE_DISMISS); + if (BotMgr::IsEnrageOnDimissEnabled()) + { + if (Aura* bers = me->AddAura(BERSERK, me)) + { + uint32 dur = 5 * MINUTE * IN_MILLISECONDS; + bers->SetDuration(dur); + bers->SetMaxDuration(dur); + } + } + //if (urand(1,100) <= 25) + //{ + // me->SetFaction(14); + // if (Creature* pet = GetBotsPet()) + // pet->SetFaction(14); + // BotSay("Fool...", player); + // me->Attack(player, true); + //} + //else + BotSay("...", player); + + break; + } + case GOSSIP_SENDER_JOIN_GROUP: + { + uint32 option = action - GOSSIP_ACTION_INFO_DEF; + switch (option) + { + case 1: //single bot + player->GetBotMgr()->AddBotToGroup(me); + break; + case 2: //all bots + { + BotMap const* bmap = player->GetBotMgr()->GetBotMap(); + for (BotMap::const_iterator citr = bmap->begin(); citr != bmap->end(); ++citr) + { + if (!citr->second) + continue; + player->GetBotMgr()->AddBotToGroup(citr->second); + } + break; + } + } + break; + } + case GOSSIP_SENDER_LEAVE_GROUP: + { + player->GetBotMgr()->RemoveBotFromGroup(me); + break; + } + case GOSSIP_SENDER_HOLDPOSITION: + { + SetBotCommandState(BOT_COMMAND_STAY); + //BotWhisper("Standing still"); + break; + } + case GOSSIP_SENDER_DONOTHING: + { + SetBotCommandState(BOT_COMMAND_FULLSTOP); + //BotWhisper("As you wish"); + break; + } + case GOSSIP_SENDER_FOLLOWME: + { + SetBotCommandState(BOT_COMMAND_FOLLOW, true); + //BotWhisper("Following"); + break; + } + case GOSSIP_SENDER_FORMATION_TOGGLE_COMBAT_POSITIONING: + { + player->GetBotMgr()->SetBotAllowCombatPositioning(!player->GetBotMgr()->GetBotAllowCombatPositioning()); + + //break; //return to menu + } + [[fallthrough]]; + case GOSSIP_SENDER_FORMATION: + { + subMenu = true; + std::ostringstream diststr; + diststr << LocalizedNpcText(player, BOT_TEXT_FOLLOW_DISTANCE) << " (" << LocalizedNpcText(player, BOT_TEXT_CURRENT) << ": " << uint32(master->GetBotMgr()->GetBotFollowDist()) << ')'; + player->PlayerTalkClass->GetGossipMenu().AddMenuItem(-1, GOSSIP_ICON_CHAT, diststr.str(), + GOSSIP_SENDER_FORMATION_FOLLOW_DISTANCE_SET, GOSSIP_ACTION_INFO_DEF + 1, "", 0, true); + + if (HasRole(BOT_ROLE_RANGED)) + { + AddGossipItemFor(player, !player->GetBotMgr()->GetBotAllowCombatPositioning() ? GOSSIP_ICON_BATTLE : GOSSIP_ICON_CHAT, + LocalizedNpcText(player, BOT_TEXT_DISABLE_COMBAT_POSITIONING), GOSSIP_SENDER_FORMATION_TOGGLE_COMBAT_POSITIONING, GOSSIP_ACTION_INFO_DEF + 2); + AddGossipItemFor(player, GOSSIP_ICON_TALK, LocalizedNpcText(player, BOT_TEXT_ATTACK_DISTANCE) + "...", GOSSIP_SENDER_FORMATION_ATTACK_DISTANCE, GOSSIP_ACTION_INFO_DEF + 3); + AddGossipItemFor(player, GOSSIP_ICON_TALK, LocalizedNpcText(player, BOT_TEXT_ATTACK_ANGLE) + "...", GOSSIP_SENDER_FORMATION_ATTACK_ANGLE, GOSSIP_ACTION_INFO_DEF + 4); + } + + if (!HasRole(BOT_ROLE_TANK) && HasRole(BOT_ROLE_DPS | BOT_ROLE_HEAL)) + AddGossipItemFor(player, GOSSIP_ICON_TALK, LocalizedNpcText(player, BOT_TEXT_ENGAGE_BEHAVIOR) + "...", GOSSIP_SENDER_ENGAGE_BEHAVIOR, GOSSIP_ACTION_INFO_DEF + 5); + + if (player->GetGroup()) + { + for (uint32 role = BOT_ROLE_TANK; !!(role & BOT_ROLE_MASK_MAIN); role <<= 1) + { + if (role & (BOT_ROLE_TANK | BOT_ROLE_DPS) && HasRole(role)) + AddGossipItemFor(player, GOSSIP_ICON_TALK, LocalizedNpcText(player, BOT_TEXT_PRIORITY_TARGET) + " (" + LocalizedNpcText(player, GetRoleString(role)) + ")...", GOSSIP_SENDER_PRIORITY_TARGET, uint32(GOSSIP_ACTION_INFO_DEF) + role); + } + } + + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_BACK), 1, GOSSIP_ACTION_INFO_DEF + 6); + break; + } + case GOSSIP_SENDER_FORMATION_ATTACK_DISTANCE_SET: + { + uint32 choice = action - GOSSIP_ACTION_INFO_DEF; + if (choice == 1) //short + { + player->GetBotMgr()->SetBotAttackRangeMode(BOT_ATTACK_RANGE_SHORT); + } + else if (choice == 2) //long + { + player->GetBotMgr()->SetBotAttackRangeMode(BOT_ATTACK_RANGE_LONG); + } + + //break; //return to menu + } + [[fallthrough]]; + case GOSSIP_SENDER_FORMATION_ATTACK_DISTANCE: + { + subMenu = true; + + uint8 mode = master->GetBotMgr()->GetBotAttackRangeMode(); + AddGossipItemFor(player, mode == BOT_ATTACK_RANGE_SHORT ? GOSSIP_ICON_BATTLE : GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_SHORT_RANGE_ATTACKS), GOSSIP_SENDER_FORMATION_ATTACK_DISTANCE_SET, GOSSIP_ACTION_INFO_DEF + 1); + AddGossipItemFor(player, mode == BOT_ATTACK_RANGE_LONG ? GOSSIP_ICON_BATTLE : GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_LONG_RANGE_ATTACKS), GOSSIP_SENDER_FORMATION_ATTACK_DISTANCE_SET, GOSSIP_ACTION_INFO_DEF + 2); + + std::ostringstream diststr; + if (mode == BOT_ATTACK_RANGE_EXACT) + diststr << LocalizedNpcText(player, BOT_TEXT_EXACT) << " (" << LocalizedNpcText(player, BOT_TEXT_CURRENT) << ": " << uint32(master->GetBotMgr()->GetBotExactAttackRange()) << ')'; + else + diststr << LocalizedNpcText(player, BOT_TEXT_EXACT) << " (0-50)"; + player->PlayerTalkClass->GetGossipMenu().AddMenuItem(-1, mode == BOT_ATTACK_RANGE_EXACT ? GOSSIP_ICON_BATTLE : GOSSIP_ICON_CHAT, + diststr.str(), GOSSIP_SENDER_FORMATION_ATTACK_DISTANCE_SET, GOSSIP_ACTION_INFO_DEF + 3, "", 0, true); + + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_BACK), 1, GOSSIP_ACTION_INFO_DEF + 4); + break; + } + case GOSSIP_SENDER_FORMATION_ATTACK_ANGLE_SET: + { + uint32 choice = action - GOSSIP_ACTION_INFO_DEF; + if (choice == 1) //normal + { + player->GetBotMgr()->SetBotAttackAngleMode(BOT_ATTACK_ANGLE_NORMAL); + } + if (choice == 2) //avoid frontal aoe + { + player->GetBotMgr()->SetBotAttackAngleMode(BOT_ATTACK_ANGLE_AVOID_FRONTAL_AOE); + } + + //break; //return to menu + } + [[fallthrough]]; + case GOSSIP_SENDER_FORMATION_ATTACK_ANGLE: + { + subMenu = true; + + uint8 mode = master->GetBotMgr()->GetBotAttackAngleMode(); + AddGossipItemFor(player, mode == BOT_ATTACK_ANGLE_NORMAL ? GOSSIP_ICON_BATTLE : GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_NORMAL), GOSSIP_SENDER_FORMATION_ATTACK_ANGLE_SET, GOSSIP_ACTION_INFO_DEF + 1); + AddGossipItemFor(player, mode == BOT_ATTACK_ANGLE_AVOID_FRONTAL_AOE ? GOSSIP_ICON_BATTLE : GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_AVOID_FRONTAL_AOE), GOSSIP_SENDER_FORMATION_ATTACK_ANGLE_SET, GOSSIP_ACTION_INFO_DEF + 2); + + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_BACK), 1, GOSSIP_ACTION_INFO_DEF + 3); + break; + } + case GOSSIP_SENDER_ENGAGE_BEHAVIOR: + { + subMenu = true; + + if (HasRole(BOT_ROLE_DPS)) + { + std::ostringstream delaystr; + delaystr.setf(std::ios_base::fixed); + delaystr.precision(2); + delaystr << LocalizedNpcText(player, BOT_TEXT_DELAY_ATTACK_BY) << ": " << float(player->GetBotMgr()->GetEngageDelayDPS() / 1000.f) << LocalizedNpcText(player, BOT_TEXT_SECOND_SHORT); + player->PlayerTalkClass->GetGossipMenu().AddMenuItem(-1, GOSSIP_ICON_CHAT, delaystr.str(), + GOSSIP_SENDER_ENGAGE_DELAY_SET_ATTACK, GOSSIP_ACTION_INFO_DEF + 1, "", 0, true); + } + if (HasRole(BOT_ROLE_HEAL)) + { + std::ostringstream delaystr; + delaystr.setf(std::ios_base::fixed); + delaystr.precision(2); + delaystr << LocalizedNpcText(player, BOT_TEXT_DELAY_HEALING_BY) << ": " << float(player->GetBotMgr()->GetEngageDelayHeal() / 1000.f) << LocalizedNpcText(player, BOT_TEXT_SECOND_SHORT); + player->PlayerTalkClass->GetGossipMenu().AddMenuItem(-1, GOSSIP_ICON_CHAT, delaystr.str(), GOSSIP_SENDER_ENGAGE_DELAY_SET_HEALING, GOSSIP_ACTION_INFO_DEF + 2, "", 0, true); + if (GetBotClass() != BOT_CLASS_SPHYNX) + { + std::ostringstream thresholdstr; + thresholdstr << LocalizedNpcText(player, BOT_TEXT_HEAL_TARGET_HEALTH_THRESHOLD) << ": " << uint32(GetHealHpPctThreshold()) << "%"; + player->PlayerTalkClass->GetGossipMenu().AddMenuItem(-1, GOSSIP_ICON_CHAT, thresholdstr.str(), GOSSIP_SENDER_HEAL_HEALTH_THRESHOLD_SET, GOSSIP_ACTION_INFO_DEF + 3, "", 0, true); + } + } + + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_BACK), 1, GOSSIP_ACTION_INFO_DEF + 4); + break; + } + case GOSSIP_SENDER_PRIORITY_TARGET_SET_TANK: + case GOSSIP_SENDER_PRIORITY_TARGET_SET_DPS: + { + Group const* gr = player->GetGroup(); + if (!gr) + break; + + uint32 role = sender - GOSSIP_SENDER_PRIORITY_TARGET; + int8 icon = decltype(icon)(action - GOSSIP_ACTION_INFO_DEF); + + switch (role) + { + case BOT_ROLE_TANK: + _primaryIconTank = icon; + break; + case BOT_ROLE_DPS: + _primaryIconDamage = icon; + break; + default: + break; + } + + //break; + action = uint32(GOSSIP_ACTION_INFO_DEF) + role; //restore role value and return to the menu + } + [[fallthrough]]; + case GOSSIP_SENDER_PRIORITY_TARGET: + { + Group const* gr = player->GetGroup(); + if (!gr) + break; + + subMenu = true; + + uint32 role = action - GOSSIP_ACTION_INFO_DEF; + switch (role) + { + case BOT_ROLE_TANK: + for (int8 i = TARGET_ICONS_COUNT - 1; i >= 0; --i) + { + bool prio = i == _primaryIconTank; + ObjectGuid guid = gr->GetTargetIcons()[i]; + if (guid && BotMgr::GetTankTargetIconFlags() & GroupIconsFlags[i]) + AddGossipItemFor(player, prio ? GOSSIP_ICON_BATTLE : GOSSIP_ICON_CHAT, player->GetBotMgr()->GetTargetIconString(uint8(i)), GOSSIP_SENDER_PRIORITY_TARGET_SET_TANK, uint32(GOSSIP_ACTION_INFO_DEF) + uint32(i)); + } + AddGossipItemFor(player, (_primaryIconTank == -1) ? GOSSIP_ICON_BATTLE : GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_NONE2), GOSSIP_SENDER_PRIORITY_TARGET_SET_TANK, uint32(GOSSIP_ACTION_INFO_DEF - 1)); + break; + case BOT_ROLE_DPS: + for (int8 i = TARGET_ICONS_COUNT - 1; i >= 0; --i) + { + bool prio = i == _primaryIconDamage; + ObjectGuid guid = gr->GetTargetIcons()[i]; + uint32 iconMask = BotMgr::GetDPSTargetIconFlags(); + if (HasRole(BOT_ROLE_RANGED)) + iconMask |= BotMgr::GetRangedDPSTargetIconFlags(); + if (guid && iconMask & GroupIconsFlags[i]) + AddGossipItemFor(player, prio ? GOSSIP_ICON_BATTLE : GOSSIP_ICON_CHAT, player->GetBotMgr()->GetTargetIconString(uint8(i)), GOSSIP_SENDER_PRIORITY_TARGET_SET_DPS, uint32(GOSSIP_ACTION_INFO_DEF) + uint32(i)); + } + AddGossipItemFor(player, (_primaryIconDamage == -1) ? GOSSIP_ICON_BATTLE : GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_NONE2), GOSSIP_SENDER_PRIORITY_TARGET_SET_DPS, uint32(GOSSIP_ACTION_INFO_DEF - 1)); + break; + default: + BotWhisper("unknown role " + std::to_string(role)); + break; + } + + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_BACK), 1, GOSSIP_ACTION_INFO_DEF + 3); + break; + } + case GOSSIP_SENDER_TROUBLESHOOTING_AURA: + { + uint32 spellId = action - GOSSIP_ACTION_INFO_DEF; + Unit::AuraMap const& auras = me->GetOwnedAuras(); + for (Unit::AuraMap::const_iterator itr = auras.begin(); itr != auras.end(); ++itr) + { + if (itr->first != spellId) + continue; + + SpellInfo const* info = itr->second->GetSpellInfo(); + if (info->IsChanneled()) + if (Spell const* curSpell = me->GetCurrentSpell(CURRENT_CHANNELED_SPELL)) + if (curSpell->m_spellInfo->Id == spellId) + me->InterruptSpell(CURRENT_CHANNELED_SPELL); + + //Debug + //std::ostringstream msg; + //msg << "Removing "; + //_AddSpellLink(player, info, msg); + //msg << " by "; + //ObjectGuid casterGuid = itr->second->GetCasterGUID(); + //Unit* caster = casterGuid ? ObjectAccessor::GetUnit(*me, casterGuid) : nullptr; + //if (caster) + //{ + // if (casterGuid == me->GetGUID()) + // msg << "me"; + // else + // msg << caster->GetName(); + //} + //else + // msg << "Unknown unit"; + //BotWhisper(msg.str().c_str()); + + me->RemoveOwnedAura(spellId, ObjectGuid::Empty, 0, AURA_REMOVE_BY_CANCEL); + break; + } + + //break; + action = GOSSIP_ACTION_INFO_DEF + 2; //return to the list and update + } + [[fallthrough]]; + case GOSSIP_SENDER_TROUBLESHOOTING_FIX: + { + uint32 option = action - GOSSIP_ACTION_INFO_DEF; + switch (option) + { + //MOVED TO RECEIVEEMOTE + //case 1: //Bot is not mounting, not following while player is mounted + //{ + // //Reason: creature can sometimes retain UNIT_FLAG_X and UNIT_STATE_X + // //rare occasion, even for bots, you can still talk to them so yeah, stange + // if (me->HasUnitState(UNIT_STATE_STUNNED) && !me->HasAuraType(SPELL_AURA_MOD_STUN)) + // { + // me->ClearUnitState(UNIT_STATE_STUNNED); + // me->RemoveUnitFlag(UNIT_FLAG_STUNNED); + // } + // if (me->HasUnitState(UNIT_STATE_CONFUSED) && !me->HasAuraType(SPELL_AURA_MOD_CONFUSE)) + // { + // me->ClearUnitState(UNIT_STATE_CONFUSED); + // me->RemoveUnitFlag(UNIT_FLAG_CONFUSED); + // } + // break; + //} + case 2: //Remove a visible buff + { + subMenu = true; + uint32 count = 0; + Unit::AuraMap const& auras = me->GetOwnedAuras(); + for (Unit::AuraMap::const_iterator itr = auras.begin(); itr != auras.end(); ++itr) + { + SpellInfo const* info = itr->second->GetSpellInfo(); + //spells we cannot remove + //1 passive, negative spells, hidden, locked, shapeshift / mount spells (no hook for that) + if (info->IsPassive() || !info->IsPositive()) + continue; + if ((info->Attributes & (SPELL_ATTR0_CANT_CANCEL | SPELL_ATTR0_HIDDEN_CLIENTSIDE)) || + (info->AttributesEx & SPELL_ATTR1_DONT_DISPLAY_IN_AURA_BAR)) + continue; + if (info->HasAura(SPELL_AURA_MOD_SHAPESHIFT)) + continue; + if (info->HasAura(SPELL_AURA_MOUNTED) && player->HasAura(info->Id)) + continue; + //2 custom list + //2.1 Leader of the Pack AOE (supposed to be passive) + if (info->Id == 24932) + continue; + //2.2 Tree of Life AOE (supposed to be passive) + if (info->Id == 34123) + continue; + //2.3 Moonkin Aura AOE (supposed to be passive) + if (info->Id == 24907) + continue; + //2.4 Blood Pact AOE (supposed to be passive) + if (info->GetFirstRankSpell()->Id == 6307) + continue; + //2.5 Fel Intelligence AOE (supposed to be passive) + if (info->GetFirstRankSpell()->Id == 54424) + continue; + + std::ostringstream msg; + _AddSpellLink(player, info, msg); + AddGossipItemFor(player, GOSSIP_ICON_CHAT, msg.str().c_str(), GOSSIP_SENDER_TROUBLESHOOTING_AURA, GOSSIP_ACTION_INFO_DEF + itr->first); + + if (++count >= BOT_GOSSIP_MAX_ITEMS - 2) //update, back + break; + } + + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_UPDATE), sender, action); + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_BACK), 1, GOSSIP_ACTION_INFO_DEF + 1); + break; + } + case 3: //Fix Powers + { + InitPowers(); + break; + } + default: + //BotWhisper("Unknown action in GOSSIP_SENDER_TROUBLESHOOTING_FIX", player); + break; + } + break; + } + case GOSSIP_SENDER_TROUBLESHOOTING: + { + subMenu = true; + //AddGossipItemFor(player, GOSSIP_ICON_CHAT, "Fix not mounting/following", GOSSIP_SENDER_TROUBLESHOOTING_FIX, GOSSIP_ACTION_INFO_DEF + 1); + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_REMOVE_BUFF) + "...", GOSSIP_SENDER_TROUBLESHOOTING_FIX, GOSSIP_ACTION_INFO_DEF + 2); + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_FIX_POWER), GOSSIP_SENDER_TROUBLESHOOTING_FIX, GOSSIP_ACTION_INFO_DEF + 3); + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_BACK), 1, GOSSIP_ACTION_INFO_DEF + 4); + break; + } + case GOSSIP_SENDER_DEBUG_ACTION: + { + //!!! player != owner !!! + //DEBUG ACTIONS ARE NOT LOCALIZED + bool close = true; + switch (action - GOSSIP_ACTION_INFO_DEF) + { + case 1: //reset owner + if (!IAmFree()) + master->GetBotMgr()->RemoveBot(me->GetGUID(), BOT_REMOVE_DISMISS); + else + { + uint32 newOwner = 0; + BotDataMgr::UpdateNpcBotData(me->GetEntry(), NPCBOT_UPDATE_OWNER, &newOwner); + ResetBotAI(BOTAI_RESET_DISMISS); + } + break; + case 2: //reset stats + spawned = false; + DefaultInit(); + break; + case 3: //list stats + close = false; + ReceiveEmote(player, TEXT_EMOTE_BONK); + break; + case 4: //list roles + { + close = false; + ChatHandler ch(player->GetSession()); + ch.PSendSysMessage("%s's Roles:", me->GetName().c_str()); + for (uint32 i = BOT_MAX_ROLE; i != BOT_ROLE_NONE; i >>= 1) + { + if (_roleMask & i) + { + switch (i) + { + case BOT_ROLE_TANK: + ch.SendSysMessage("BOT_ROLE_TANK"); + break; + case BOT_ROLE_TANK_OFF: + ch.SendSysMessage("BOT_ROLE_TANK_OFF"); + break; + case BOT_ROLE_DPS: + ch.SendSysMessage("BOT_ROLE_DPS"); + break; + case BOT_ROLE_HEAL: + ch.SendSysMessage("BOT_ROLE_HEAL"); + break; + //case BOT_ROLE_MELEE: + // ch.SendSysMessage("BOT_ROLE_MELEE"); + // break; + case BOT_ROLE_RANGED: + ch.SendSysMessage("BOT_ROLE_RANGED"); + break; + case BOT_ROLE_PARTY: + ch.SendSysMessage("BOT_ROLE_PARTY"); + break; + default: + ch.PSendSysMessage("BOT_ROLE_%u",i); + break; + } + } + } + break; + } + case 5: //list spells + { + close = false; + ChatHandler ch(player->GetSession()); + ch.PSendSysMessage("%s's Spells:", me->GetName().c_str()); + uint32 counter = 0; + SpellInfo const* spellInfo; + BotSpellMap const& myspells = GetSpellMap(); + for (BotSpellMap::const_iterator itr = myspells.begin(); itr != myspells.end(); ++itr) + { + //if (itr->second->spellId == 0) + // continue; + + ++counter; + std::ostringstream sstr; + spellInfo = sSpellMgr->GetSpellInfo(itr->first); //always valid + _AddSpellLink(player, spellInfo, sstr); + sstr << " id: " << itr->second->spellId << ", base: " << itr->first + << ", cd: " << itr->second->cooldown << ", base: " << std::max(spellInfo->RecoveryTime, spellInfo->CategoryRecoveryTime); + if (itr->second->enabled == false) + sstr << " (disabled)"; + ch.PSendSysMessage("%u) %s", counter, sstr.str().c_str()); + } + break; + } + case 6: //reload config + { + close = false; + ChatHandler ch(player->GetSession()); + + TC_LOG_INFO("misc", "Re-Loading config settings..."); + sWorld->LoadConfigSettings(true); + sMapMgr->InitializeVisibilityDistanceInfo(); + ch.SendGlobalGMSysMessage("World config settings reloaded."); + BotMgr::ReloadConfig(); + ch.SendGlobalGMSysMessage("NpcBot config settings reloaded."); + + break; + } + default: + close = false; + break; + } + + if (close) + break; + } + [[fallthrough]]; + case GOSSIP_SENDER_DEBUG: + { + //!!! player != owner !!! + subMenu = true; + + std::ostringstream ostr; + std::string name; + ostr << "Bot: " << me->GetName() + << " (Id: " << me->GetEntry() + << ", guidlow: " << me->GetGUID().GetCounter() + << ", spec: " << uint32(_spec) << '(' << LocalizedNpcText(player, TextForSpec(_spec)) << ')' + << ", faction: " << me->GetFaction() + << "). owner: "; + if (_ownerGuid && sCharacterCache->GetCharacterNameByGuid(ObjectGuid(HighGuid::Player, _ownerGuid), name)) + ostr << name << " (" << _ownerGuid << ')'; + else + ostr << "none"; + + AddGossipItemFor(player, GOSSIP_ICON_CHAT, ostr.str().c_str(), GOSSIP_SENDER_DEBUG_ACTION, GOSSIP_ACTION_INFO_DEF + 0); + + AddGossipItemFor(player, GOSSIP_ICON_CHAT, "", GOSSIP_SENDER_DEBUG_ACTION, GOSSIP_ACTION_INFO_DEF + 1); + AddGossipItemFor(player, GOSSIP_ICON_CHAT, "", GOSSIP_SENDER_DEBUG_ACTION, GOSSIP_ACTION_INFO_DEF + 2); + AddGossipItemFor(player, GOSSIP_ICON_CHAT, "", GOSSIP_SENDER_DEBUG_ACTION, GOSSIP_ACTION_INFO_DEF + 3); + AddGossipItemFor(player, GOSSIP_ICON_CHAT, "", GOSSIP_SENDER_DEBUG_ACTION, GOSSIP_ACTION_INFO_DEF + 4); + AddGossipItemFor(player, GOSSIP_ICON_CHAT, "", GOSSIP_SENDER_DEBUG_ACTION, GOSSIP_ACTION_INFO_DEF + 5); + AddGossipItemFor(player, GOSSIP_ICON_CHAT, "", GOSSIP_SENDER_EQUIPMENT_LIST, GOSSIP_ACTION_INFO_DEF + 1); + AddGossipItemFor(player, GOSSIP_ICON_CHAT, "", GOSSIP_SENDER_DEBUG_ACTION, GOSSIP_ACTION_INFO_DEF + 6); + + AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_BACK), 1, GOSSIP_ACTION_INFO_DEF + 1); + break; + } + case GOSSIP_SENDER_SCAN: + { + subMenu = true; + + switch (_botclass) + { + case BOT_CLASS_BM: + gossipTextId = GOSSIP_CLASSDESC_BM; + break; + case BOT_CLASS_SPHYNX: + gossipTextId = GOSSIP_CLASSDESC_SPHYNX; + break; + case BOT_CLASS_ARCHMAGE: + gossipTextId = GOSSIP_CLASSDESC_ARCHMAGE; + break; + case BOT_CLASS_DREADLORD: + gossipTextId = GOSSIP_CLASSDESC_DREADLORD; + break; + case BOT_CLASS_SPELLBREAKER: + gossipTextId = GOSSIP_CLASSDESC_SPELLBREAKER; + break; + case BOT_CLASS_DARK_RANGER: + gossipTextId = GOSSIP_CLASSDESC_DARKRANGER; + break; + case BOT_CLASS_NECROMANCER: + gossipTextId = GOSSIP_CLASSDESC_NECROMANCER; + break; + case BOT_CLASS_SEA_WITCH: + gossipTextId = GOSSIP_CLASSDESC_SEAWITCH; + break; + case BOT_CLASS_CRYPT_LORD: + gossipTextId = GOSSIP_CLASSDESC_CRYPTLORD; + break; + default: + break; + } + + //AddGossipItemFor(player, GOSSIP_ICON_CHAT, LocalizedNpcText(player, BOT_TEXT_BACK), 1, GOSSIP_ACTION_INFO_DEF + 1); + + break; + } + default: + break; + } + + //if we add submenus send them else we should return + if (subMenu) + player->PlayerTalkClass->SendGossipMenu(gossipTextId, me->GetGUID()); + else + player->PlayerTalkClass->SendCloseGossip(); + + return true; +} + +//GossipSelectCode +bool bot_ai::OnGossipSelectCode(Player* player, Creature* creature/* == me*/, uint32 sender, uint32 action, char const* code) +{ + if (!*code) + return true; + + if (!BotMgr::IsNpcBotModEnabled() || me->HasUnitState(UNIT_STATE_CASTING) || CCed(me) || IsDuringTeleport() || + HasBotCommandState(BOT_COMMAND_ISSUED_ORDER) || + (me->GetVehicle() && me->GetVehicle()->GetBase()->IsInCombat())) + { + player->PlayerTalkClass->SendCloseGossip(); + return true; + } + + uint32 gossipTextId; + if (player->GetGUID().GetCounter() == _ownerGuid || !IAmFree()) + { + if (_botclass == BOT_CLASS_SPHYNX) + gossipTextId = GOSSIP_NORMAL_CUSTOM_SPHYNX; + else if (_botclass == BOT_CLASS_DREADLORD) + gossipTextId = GOSSIP_NORMAL_CUSTOM_DREADLORD; + else if (_botclass == BOT_CLASS_DARK_RANGER) + gossipTextId = GOSSIP_NORMAL_CUSTOM_DARKRANGER; + else if (_botclass == BOT_CLASS_SEA_WITCH) + gossipTextId = GOSSIP_NORMAL_CUSTOM_SEAWITCH; + else if (_botclass == BOT_CLASS_CRYPT_LORD) + gossipTextId = GOSSIP_NORMAL_CUSTOM_CRYPTLORD; + else + gossipTextId = GOSSIP_NORMAL_SERVE_MASTER; + } + else + { + if (_botclass == BOT_CLASS_SPHYNX) + gossipTextId = GOSSIP_GREET_CUSTOM_SPHYNX; + else if (_botclass == BOT_CLASS_DREADLORD) + gossipTextId = GOSSIP_GREET_CUSTOM_DREADLORD; + else if (_botclass == BOT_CLASS_DARK_RANGER) + gossipTextId = GOSSIP_GREET_CUSTOM_DARKRANGER; + else if (_botclass == BOT_CLASS_SEA_WITCH) + gossipTextId = GOSSIP_GREET_CUSTOM_SEAWITCH; + else if (_botclass == BOT_CLASS_CRYPT_LORD) + gossipTextId = GOSSIP_GREET_CUSTOM_CRYPTLORD; + else + gossipTextId = GOSSIP_GREET_NEED_SMTH; + } + + player->PlayerTalkClass->ClearMenus(); + + bool subMenu = false; + + switch (sender) + { + case GOSSIP_SENDER_FORMATION_FOLLOW_DISTANCE_SET: + { + char* dist = strtok((char*)code, ""); + uint8 distance = (uint8)std::min(std::max(atoi(dist), 0), 100); + + player->GetBotMgr()->SetBotFollowDist(distance); + + player->PlayerTalkClass->SendCloseGossip(); + return OnGossipSelect(player, creature, GOSSIP_SENDER_FORMATION, action); + } + case GOSSIP_SENDER_FORMATION_ATTACK_DISTANCE_SET: + { + uint32 choice = action - GOSSIP_ACTION_INFO_DEF; + if (choice == 3) //exact + { + char* dist = strtok((char*)code, ""); + uint8 distance = (uint8)std::min(std::max(atoi(dist), 0), 50); + + player->GetBotMgr()->SetBotAttackRangeMode(BOT_ATTACK_RANGE_EXACT, distance); + } + + player->PlayerTalkClass->SendCloseGossip(); + return OnGossipSelect(player, creature, GOSSIP_SENDER_FORMATION_ATTACK_DISTANCE, action); + } + case GOSSIP_SENDER_ENGAGE_DELAY_SET_ATTACK: + { + char* dist = strtok((char*)code, ""); + float delay = std::min(std::max(atof(dist), 0.f), 10.f); + + player->GetBotMgr()->SetEngageDelayDPS(uint32(delay * 1000)); + + player->PlayerTalkClass->SendCloseGossip(); + return OnGossipSelect(player, creature, GOSSIP_SENDER_ENGAGE_BEHAVIOR, action); + } + case GOSSIP_SENDER_ENGAGE_DELAY_SET_HEALING: + { + char* dist = strtok((char*)code, ""); + float delay = std::min(std::max(atof(dist), 0.f), 10.f); + + player->GetBotMgr()->SetEngageDelayHeal(uint32(delay * 1000)); + + player->PlayerTalkClass->SendCloseGossip(); + return OnGossipSelect(player, creature, GOSSIP_SENDER_ENGAGE_BEHAVIOR, action); + } + case GOSSIP_SENDER_HEAL_HEALTH_THRESHOLD_SET: + { + char* dist = strtok((char*)code, ""); + float threshold = std::min(std::max(atof(dist), 0.f), 99.f); + + SetHealHpPctThreshold(uint8(threshold)); + + player->PlayerTalkClass->SendCloseGossip(); + return OnGossipSelect(player, creature, GOSSIP_SENDER_ENGAGE_BEHAVIOR, action); + } + default: + break; + } + + if (subMenu) + player->PlayerTalkClass->SendGossipMenu(gossipTextId, me->GetGUID()); + else + player->PlayerTalkClass->SendCloseGossip(); + + return true; +} +//PvP trinket for minions +void bot_ai::BreakCC(uint32 diff) +{ + if (_botclass < BOT_CLASS_EX_START && me->GetLevel() >= 60 && IsSpellReady(PVPTRINKET, diff, false) && + CCed(me, true) && (me->GetVictim() || !me->getAttackers().empty()) && Rand() < 20) + { + if (doCast(me, PVPTRINKET)) + return; + } + + uint8 myrace = me->GetRace(); + + //Racial 6) Every Man for Himself + if (myrace == RACE_HUMAN && IsSpellReady(RACIAL_EVERY_MAN_FOR_HIMSELF, diff, false) && CCed(me, true) && + !me->HasAuraType(SPELL_AURA_MOD_STEALTH) && + (me->IsInCombat() || !me->getAttackers().empty()) && Rand() < 40 && !IsCasting() && + (me->GetLevel() < 60 || !IsSpellReady(PVPTRINKET, diff, false))) + { + if (doCast(me, RACIAL_EVERY_MAN_FOR_HIMSELF)) + return; + } + //Racial 5) Forsaken (Will of the Forsaken) + if (myrace == RACE_UNDEAD_PLAYER && IsSpellReady(RACIAL_WILL_OF_THE_FORSAKEN, diff, false) && + (me->IsInCombat() || !me->getAttackers().empty()) && CCed(me) && + Rand() < 10 && !me->HasAuraType(SPELL_AURA_MOD_STEALTH) && !IsCasting() && + (me->GetLevel() < 60 || !IsSpellReady(PVPTRINKET, diff, false)) && + me->HasAuraWithMechanic((1<GetRace() == RACE_GNOME && IsSpellReady(RACIAL_ESCAPE_ARTIST, diff, false) && CCed(me, true) && + !me->HasAuraType(SPELL_AURA_MOD_STEALTH) && + (me->IsInCombat() || !me->getAttackers().empty()) && Rand() < 40 && !IsCasting() && + (me->GetLevel() < 60 || !IsSpellReady(PVPTRINKET, diff, false)) && + me->HasAuraWithMechanic((1<GetRace(); + //Racial 1) Tauren (War Stomp) + if (myrace == RACE_TAUREN && IsSpellReady(RACIAL_WARSTOMP, diff, false) && + (!IsTank() || me->GetShapeshiftForm() == FORM_NONE) && + Rand() < 20 && !me->HasAuraType(SPELL_AURA_MOD_STEALTH) && !IsCasting()) + { + Unit const* u = me->SelectNearestTarget(7); + if (u && u->IsInCombat() && !CCed(u) && u->isTargetableForAttack(false) && IsInBotParty(u->GetVictim()) && me->IsWithinLOSInMap(u)) + { + if (doCast(me, RACIAL_WARSTOMP)) + return; + } + } + //Racial 2) Orc (Blood Fury) + if (myrace == RACE_ORC) + { + uint32 bloodFury = RaceSpellForClass(myrace, _botclass); + if (IsSpellReady(bloodFury, diff, false) && me->GetVictim() && GetHealthPCT(me) > 35 && !CCed(me, true) && + (me->GetVictim()->GetHealth() > me->GetMaxHealth() / 2 || me->getAttackers().size() > 1) && + Rand() < 20 && !me->HasAuraType(SPELL_AURA_MOD_STEALTH) && !IsCasting()) + { + if (doCast(me, bloodFury)) + return; + } + } + //Racial 3) Dwarf (Stoneform) + if (myrace == RACE_DWARF && IsSpellReady(RACIAL_STONEFORM, diff, false) && GetHealthPCT(me) < 80 && + Rand() < 10 && !me->HasAuraType(SPELL_AURA_MOD_STEALTH) && !IsCasting()) + { + //Unholy Blight prevents diseases from being dispelled + uint32 const dispelMask = me->GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_DEATHKNIGHT, 1494, 0) ? + (1<GetOwnedAuras(); + for (Unit::AuraMap::const_iterator itr = auras.begin(); itr != auras.end(); ++itr) + { + Aura const* aura = itr->second; + + if (aura->IsPassive()) + continue; + + AuraApplication const* aurApp = aura->GetApplicationOfTarget(me->GetGUID()); + if (!aurApp) + continue; + if (aurApp->IsPositive()) + continue; + + if ((aura->GetSpellInfo()->GetDispelMask() & dispelMask) || + aura->GetSpellInfo()->Mechanic == MECHANIC_BLEED) + if (++count > 1) + break; + } + + if (count > 1 - 1*(me->IsInCombat()) && doCast(me, RACIAL_STONEFORM)) + return; + } + //Racial 4) Night Elf (Shadowmeld) + if (myrace == RACE_NIGHTELF && IsSpellReady(RACIAL_SHADOWMELD, diff, false) && !me->IsInCombat() && me->GetVictim() && + me->GetVictim()->GetTypeId() == TYPEID_PLAYER && Rand() < 50 && + !me->HasAuraType(SPELL_AURA_MOD_STEALTH) && !IsCasting()) + { + if (Spell const* spell = me->GetVictim()->GetCurrentSpell(CURRENT_GENERIC_SPELL)) + { + if (spell->m_targets.GetUnitTarget() == me && spell->GetTimer() < 500 && + spell->GetSpellInfo()->HasEffect(SPELL_EFFECT_SCHOOL_DAMAGE)) + if (doCast(me, RACIAL_SHADOWMELD)) + return; + } + } + //Racial 8) Troll (Berserking) + if (myrace == RACE_TROLL && IsSpellReady(RACIAL_BERSERKING, diff, false) && me->GetVictim() && + GetHealthPCT(me) > 35 && !CCed(me, true) && + (me->GetVictim()->GetHealth() > me->GetMaxHealth() / 2 || me->getAttackers().size() > 1) && + Rand() < 20 && !me->HasAuraType(SPELL_AURA_MOD_STEALTH) && !IsCasting()) + { + if (doCast(me, RACIAL_BERSERKING)) + return; + } + //Racial 9) Blood Elf (Arcane Torrent) + if (myrace == RACE_BLOODELF) + { + uint32 arcaneTorrent = RaceSpellForClass(myrace, _botclass); + if (IsSpellReady(arcaneTorrent, diff, false) && !IsCasting() && !Feasting()) + { + Unit const* victim = me->GetVictim(); + if (victim && victim->IsNonMeleeSpellCast(false, false, true) && + (me->GetVictim()->GetHealth() > me->GetHealth() / 4 || me->getAttackers().size() > 1) && + me->GetDistance(victim) < 7 && Rand() < 30 && !me->HasAuraType(SPELL_AURA_MOD_STEALTH)) + { + if (Spell const* spell = victim->GetCurrentSpell(CURRENT_GENERIC_SPELL)) + { + if (IsInBotParty(spell->m_targets.GetUnitTargetGUID()) && spell->GetTimer() < 1000) + if (doCast(me, arcaneTorrent)) + return; + } + } + else if (GetManaPCT(me) < 25) + if (doCast(me, arcaneTorrent)) + return; + } + } + //Racial 10) Draenei (Gift of The Naaru) - self only + if (myrace == RACE_DRAENEI) + { + uint32 giftOfNaaru = RaceSpellForClass(myrace, _botclass); + if (IsSpellReady(giftOfNaaru, diff, false) && (me->IsInCombat() || !me->getAttackers().empty()) && + GetHealthPCT(me) < 60 - 10*me->HasAuraType(SPELL_AURA_PERIODIC_HEAL) && + Rand() < 50 && !IsCasting()) + { + if (doCast(me, giftOfNaaru)) + return; + } + } +} +//Force bots to start attack anyone who tries to DAMAGE me or master +//This means that anyone who attacks party will be attacked by whole bot party (see GetTarget()) +void bot_ai::OnOwnerDamagedBy(Unit* attacker) +{ + if (HasBotCommandState(BOT_COMMAND_FULLSTOP | BOT_COMMAND_INACTION)) + return; + + bool byspell = false; + switch (_botclass) + { + case BOT_CLASS_DRUID: + byspell = GetBotStance() == BOT_STANCE_NONE || GetBotStance() == DRUID_MOONKIN_FORM; + break; + case BOT_CLASS_PRIEST: + case BOT_CLASS_MAGE: + case BOT_CLASS_WARLOCK: + case BOT_CLASS_SHAMAN: + case BOT_CLASS_SPHYNX: + case BOT_CLASS_ARCHMAGE: + byspell = true; + break; + default: + //TC_LOG_ERROR("entities.player", "minion_ai: OnOwnerDamagedBy() - unknown bot class {}", uint8(_botclass)); + break; + } + + if (!_canSwitchToTarget(me->GetVictim(), attacker, byspell)) + return; + + SetBotCommandState(BOT_COMMAND_COMBATRESET); //reset AttackStart() + me->Attack(attacker, !HasRole(BOT_ROLE_RANGED)); +} +//force vehicle targeting and attack if vehicle is damaged +void bot_ai::OnOwnerVehicleDamagedBy(Unit* attacker) +{ + if (HasBotCommandState(BOT_COMMAND_FULLSTOP | BOT_COMMAND_INACTION)) + return; + + Creature* veh = me->GetVehicleCreatureBase(); + if (!veh || (veh->GetTarget() && HasBotCommandState(BOT_COMMAND_ATTACK)) || !veh->IsValidAttackTarget(attacker)) + return; + + veh->SetTarget(attacker->GetGUID()); + SetBotCommandState(BOT_COMMAND_ATTACK); + me->GetVehicleBase()->Attack(attacker, false); +} +////////// +///LOOT/// +////////// +void bot_ai::SpawnKillReward(Player* looter) const +{ + ASSERT(IsWanderer()); + + if (!me->GetMap()->GetEntry()->IsContinent()) + return; + + QuaternionData rotation = QuaternionData::fromEulerAnglesZYX(looter->GetOrientation(), 0.f, 0.f); + GameObject* moneyBag = looter->SummonGameObject(GO_BOT_MONEY_BAG, *me, rotation, std::chrono::duration_cast(Milliseconds(REVIVE_TIMER_DEFAULT))); + moneyBag->SetSpellId(GO_BOT_MONEY_BAG + me->GetEntry()); +} +void bot_ai::FillKillReward(GameObject* go) const +{ + static const uint32 MAX_KILL_REWARD_ITEMS = 2; + + ASSERT(IsWanderer()); + ASSERT(go->GetEntry() == GO_BOT_MONEY_BAG); + ASSERT((go->GetSpellId() - go->GetEntry()) == me->GetEntry()); + + go->SetObjectScale(0.875f); + + Loot& loot = go->loot; + + loot.clear(); + loot.loot_type = LOOT_CORPSE; + + //gold + float lvl = float(std::min(me->GetLevel(), DEFAULT_MAX_LEVEL)); + float gold = 125.0f; + switch (me->GetLevel() / 10) + { + case 0: gold *= 0.100f; break; + case 1: gold *= 0.125f; break; + case 2: gold *= 0.175f; break; + case 3: gold *= 0.225f; break; + case 4: gold *= 0.300f; break; + case 5: gold *= 0.400f; break; + case 6: gold *= 0.550f; break; + case 7: gold *= 0.750f; break; + default:gold *= 1.000f; break; + } + + loot.gold = uint32(lvl * std::min(std::max(gold + _killsCount * gold * 0.04f - _deathsCount * gold * 0.4f, gold), gold * 10.0f)); + + //items + uint32 loot_items_count = 0; + for (Item const* item : _equips) + { + if (item) + { + ItemTemplate const* proto = item->GetTemplate(); + if (proto->Quality == ITEM_QUALITY_UNCOMMON || proto->Quality == ITEM_QUALITY_RARE) + { + if (roll_chance_f(5.0f)) + { + loot.AddItem(LootStoreItem(proto->ItemId, 0, 100.0f, false, 0, 0, 1, 1)); + + if (++loot_items_count >= std::min(MAX_KILL_REWARD_ITEMS, MAX_NR_LOOT_ITEMS)) + break; + } + } + } + } +} +uint32 bot_ai::_getLootQualityMask() const +{ + uint32 lootRoleMask = (_roleMask & BOT_ROLE_MASK_LOOTING); + uint32 lootMask = 0; + + if (lootRoleMask & BOT_ROLE_AUTOLOOT_POOR) + lootMask |= (1 << ITEM_QUALITY_POOR); + if (lootRoleMask & BOT_ROLE_AUTOLOOT_COMMON) + lootMask |= (1 << ITEM_QUALITY_NORMAL); + if (lootRoleMask & BOT_ROLE_AUTOLOOT_UNCOMMON) + lootMask |= (1 << ITEM_QUALITY_UNCOMMON); + if (lootRoleMask & BOT_ROLE_AUTOLOOT_RARE) + lootMask |= (1 << ITEM_QUALITY_RARE); + if (lootRoleMask & BOT_ROLE_AUTOLOOT_EPIC) + lootMask |= (1 << ITEM_QUALITY_EPIC); + if (lootRoleMask & BOT_ROLE_AUTOLOOT_LEGENDARY) + lootMask |= (1 << ITEM_QUALITY_LEGENDARY); + + return lootMask; +} +uint32 bot_ai::_getLootQualityThreshold() const +{ + uint32 lootThreshold; + Group const* gr = master->GetGroup(); + if (!gr) + lootThreshold = uint32(MAX_ITEM_QUALITY); + else + { + switch (gr->GetLootMethod()) + { + case GROUP_LOOT: case NEED_BEFORE_GREED: case MASTER_LOOT: + lootThreshold = uint32(gr->GetLootThreshold()); break; + default: + lootThreshold = uint32(MAX_ITEM_QUALITY); break; + } + } + + return lootThreshold; +} +bool bot_ai::_canLootItemForPlayer(Player* player, Creature* creature, uint8 slot) const +{ + NotNormalLootItem* qitem = nullptr; + NotNormalLootItem* ffaitem = nullptr; + NotNormalLootItem* conditem = nullptr; + + LootItem const* item = creature->loot.LootItemInSlot(slot, player, &qitem, &ffaitem, &conditem); + if (!item || item->is_looted) + { + //TC_LOG_ERROR("scripts", "can't loot item {}, no item", slot); + return false; + } + + if (!qitem && item->is_blocked) + { + //TC_LOG_ERROR("scripts", "can't loot item {} ({}), blocked", slot, item->itemid); + return false; + } + + if (!item->rollWinnerGUID.IsEmpty() && item->rollWinnerGUID != player->GetGUID()) + { + //TC_LOG_ERROR("scripts", "can't loot item {} ({}), roll won", slot, item->itemid); + return false; + } + + ItemPosCountVec dest; + if (player->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, item->itemid, item->count) == EQUIP_ERR_OK) + return true; + + //TC_LOG_ERROR("scripts", "can't loot item {} ({}), can't store", slot, item->itemid); + return false; +} +bool bot_ai::_canLootCreatureForPlayer(Player* player, Creature* creature, uint32 lootQualityMask, uint32 lootThreshold) const +{ + if (!player || master->GetMap() != player->FindMap()/* || !player->IsAlive() || !p->IsAtGroupRewardDistance(creature)*/) + return false; + + bool canLoot = false; + uint8 slot = 0; + for (std::vector::const_iterator i = creature->loot.items.begin(); i != creature->loot.items.end(); ++i) + { + ++slot; + + if (i->is_blocked || i->is_looted) + { + //TC_LOG_ERROR("scripts", "item {} is blocked", i->itemid); + continue; + } + + if (!i->rollWinnerGUID.IsEmpty() && i->rollWinnerGUID != player->GetGUID()) + { + //TC_LOG_ERROR("scripts", "can't loot item {} ({}), roll won", slot, i->itemid); + continue; + } + + ItemTemplate const* itemProto = sObjectMgr->GetItemTemplate(i->itemid); + if (!itemProto) + { + //TC_LOG_ERROR("scripts", "no item proto for itemId {}", i->itemid); + return false; + } + + if (itemProto->Quality >= lootThreshold) + { + //TC_LOG_ERROR("scripts", "item {} group quality threshold mismatch", i->itemid); + continue; + } + + if (!((1 << itemProto->Quality) & lootQualityMask)) + { + //TC_LOG_ERROR("scripts", "item {} lootQualityMask mismatch", i->itemid); + continue; + } + + if (_canLootItemForPlayer(player, creature, slot - 1) && i->AllowedForPlayer(player)) + { + canLoot = true; + break; + } + } + if (!canLoot) + { + NotNormalLootItemMap const& lootPlayerQuestItems = creature->loot.GetPlayerQuestItems(); + NotNormalLootItemMap::const_iterator q_itr = lootPlayerQuestItems.find(player->GetGUID()); + if (q_itr != lootPlayerQuestItems.end()) + { + NotNormalLootItemList* q_list = q_itr->second; + for (NotNormalLootItemList::const_iterator qi = q_list->begin(); qi != q_list->end(); ++qi) + { + LootItem* i = &creature->loot.quest_items[qi->index]; + if (i->is_looted || qi->is_looted) + { + //TC_LOG_ERROR("scripts", "item {} is looted", i->itemid); + continue; + } + + if (!i->rollWinnerGUID.IsEmpty() && i->rollWinnerGUID != player->GetGUID()) + { + //TC_LOG_ERROR("scripts", "can't loot item {} ({}), roll won", slot, i->itemid); + continue; + } + + ItemTemplate const* itemProto = sObjectMgr->GetItemTemplate(i->itemid); + + if (itemProto->Quality >= lootThreshold) + { + //TC_LOG_ERROR("scripts", "item {} group quality threshold mismatch", i->itemid); + continue; + } + + if (!((1 << itemProto->Quality) & lootQualityMask)) + { + //TC_LOG_ERROR("scripts", "item {} lootQualityMask mismatch", i->itemid); + continue; + } + + uint8 qslot = uint8(creature->loot.items.size() + (qi - q_list->begin())); + + if (_canLootItemForPlayer(player, creature, qslot) && i->AllowedForPlayer(player)) + { + canLoot = true; + break; + } + } + } + } + + return canLoot; +} +bool bot_ai::_canLootCreature(Creature* creature) const +{ + Loot* loot = &creature->loot; + + if (loot->gold) + return true; + + uint32 lootQualityMask = _getLootQualityMask(); + uint32 lootThreshold = _getLootQualityThreshold(); + //TC_LOG_ERROR("scripts", "lootQualityMask {}, lootThreshold {}", lootQualityMask, lootThreshold); + bool canLootQuality = false; + + //std::vector const& lootItems = loot->quest_items; + for (std::vector::const_iterator ci = loot->quest_items.begin(); ci != loot->quest_items.end(); ++ci) + { + ItemTemplate const* itemProto = sObjectMgr->GetItemTemplate(ci->itemid); + if (!itemProto) + { + //TC_LOG_ERROR("scripts", "no item proto for itemId {}", ci->itemid); + return false; + } + + if (itemProto->Quality >= lootThreshold) + { + //TC_LOG_ERROR("scripts", "item {} group quality threshold mismatch", i->itemid); + continue; + } + + if ((1 << itemProto->Quality) & lootQualityMask) + { + canLootQuality = true; + break; + } + } + if (!canLootQuality) + { + for (std::vector::const_iterator ci = loot->items.begin(); ci != loot->items.end(); ++ci) + { + ItemTemplate const* itemProto = sObjectMgr->GetItemTemplate(ci->itemid); + if (!itemProto) + { + //TC_LOG_ERROR("scripts", "no item proto for itemId {}", ci->itemid); + return false; + } + + if (itemProto->Quality >= lootThreshold) + { + //TC_LOG_ERROR("scripts", "item {} group quality threshold mismatch", i->itemid); + continue; + } + + if ((1 << itemProto->Quality) & lootQualityMask) + { + canLootQuality = true; + break; + } + } + } + + if (!canLootQuality) + { + //TC_LOG_ERROR("scripts", "can't loot by quality"); + return false; + } + + bool canLootPlayers = false; + + Group const* gr = master->GetGroup(); + if (!gr) + canLootPlayers = _canLootCreatureForPlayer(master, creature, lootQualityMask, lootThreshold); + else + { + for (GroupReference const* itr = gr->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + if (_canLootCreatureForPlayer(itr->GetSource(), creature, lootQualityMask, lootThreshold)) + { + canLootPlayers = true; + break; + } + } + } + + if (!canLootPlayers) + { + //TC_LOG_ERROR("scripts", "can't loot by canLootPlayers"); + return false; + } + + return true; +} +void bot_ai::_autoLootCreatureGold(Creature* creature) const +{ + Loot* loot = &creature->loot; + + //sScriptMgr->OnBeforeLootMoney(master, loot); + loot->NotifyMoneyRemoved(); + Group const* gr = master->GetGroup(); + if (!gr) + { + master->ModifyMoney(loot->gold); + master->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LOOT_MONEY, loot->gold); + + WorldPacket data(SMSG_LOOT_MONEY_NOTIFY, 4 + 1); + data << uint32(loot->gold); + data << uint8(1); // "You loot..." + master->GetSession()->SendPacket(&data); + } + else + { + std::vector players; + for (GroupReference const* itr = gr->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* p = itr->GetSource(); + if (p && p->IsAtGroupRewardDistance(creature)) + players.push_back(p); + } + + uint32 goldPerPlayer = uint32(loot->gold / uint32(players.size())); + + for (std::vector::const_iterator i = players.begin(); i != players.end(); ++i) + { + (*i)->ModifyMoney(goldPerPlayer); + (*i)->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LOOT_MONEY, goldPerPlayer); + + WorldPacket data(SMSG_LOOT_MONEY_NOTIFY, 4 + 1); + data << uint32(goldPerPlayer); + data << uint8(players.size() <= 1); // Controls the text displayed in chat. 0 is "Your share is..." and 1 is "You loot..." + (*i)->SendDirectMessage(&data); + } + } + + loot->gold = 0; + + if (loot->isLooted()) + { + //TC_LOG_ERROR("scripts", "creature gold is looted, releasing"); + creature->AllLootRemovedFromCorpse(); + creature->RemoveDynamicFlag(UNIT_DYNFLAG_LOOTABLE); + loot->clear(); + } +} +void bot_ai::_autoLootCreatureItems(Player* receiver, Creature* creature, uint32 lootQualityMask, uint32 lootThreshold) const +{ + uint8 slot = 0; + for (std::vector::iterator i = creature->loot.items.begin(); i != creature->loot.items.end(); ++i) + { + ++slot; + + if (i->is_blocked || i->is_looted) + { + //TC_LOG_ERROR("scripts", "item {} is blocked", i->itemid); + continue; + } + + if (!i->rollWinnerGUID.IsEmpty() && i->rollWinnerGUID != receiver->GetGUID()) + { + //TC_LOG_ERROR("scripts", "can't loot item {} ({}), roll won", slot, i->itemid); + continue; + } + + ItemTemplate const* itemProto = sObjectMgr->GetItemTemplate(i->itemid); + + if (itemProto->Quality >= lootThreshold) + continue; + if (!((1 << itemProto->Quality) & lootQualityMask)) + continue; + + if (_canLootItemForPlayer(receiver, creature, slot - 1) && i->AllowedForPlayer(receiver)) + { + //TC_LOG_ERROR("scripts", "looting {} ({}), quality {}, threshold {}", + // itemProto->Name1, itemProto->ItemId, itemProto->Quality, lootThreshold); + receiver->StoreLootItem(slot - 1, &creature->loot); + } + } + + NotNormalLootItemMap const& lootPlayerQuestItems = creature->loot.GetPlayerQuestItems(); + NotNormalLootItemMap::const_iterator q_itr = lootPlayerQuestItems.find(receiver->GetGUID()); + if (q_itr != lootPlayerQuestItems.end()) + { + NotNormalLootItemList* q_list = q_itr->second; + for (NotNormalLootItemList::const_iterator qi = q_list->begin(); qi != q_list->end(); ++qi) + { + LootItem* i = &creature->loot.quest_items[qi->index]; + if (i->is_looted || qi->is_looted) + { + //TC_LOG_ERROR("scripts", "item {} is looted", i->itemid); + continue; + } + + if (!i->rollWinnerGUID.IsEmpty() && i->rollWinnerGUID != receiver->GetGUID()) + { + //TC_LOG_ERROR("scripts", "can't loot item {} ({}), roll won", slot, i->itemid); + continue; + } + + ItemTemplate const* itemProto = sObjectMgr->GetItemTemplate(i->itemid); + + if (itemProto->Quality >= lootThreshold) + continue; + if (!((1 << itemProto->Quality) & lootQualityMask)) + continue; + + //if (!receiver->HasQuestForItem(i->itemid)) + // continue; + + uint8 qslot = uint8(creature->loot.items.size() + (qi - q_list->begin())); + + if (_canLootItemForPlayer(receiver, creature, qslot) && i->AllowedForPlayer(receiver)) + receiver->StoreLootItem(qslot, &creature->loot); + } + } + if (creature->loot.isLooted()) + { + //TC_LOG_ERROR("scripts", "creature items is looted, releasing"); + creature->AllLootRemovedFromCorpse(); + creature->RemoveDynamicFlag(UNIT_DYNFLAG_LOOTABLE); + creature->loot.clear(); + } +} +void bot_ai::_autoLootCreature(Creature* creature) +{ + //money + if (creature->loot.gold) + { + _autoLootCreatureGold(creature); + + //nothing but gold was there + if (creature->loot.empty()) + return; + } + + //items + uint32 lootQualityMask = _getLootQualityMask(); + uint32 lootThreshold = _getLootQualityThreshold(); + + std::set pLooters; + Group* gr = master->GetGroup(); + if (!gr) + { + if (_canLootCreatureForPlayer(master, creature, lootQualityMask, lootThreshold)) + pLooters.insert(master); + } + else + { + for (GroupReference const* itr = gr->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + if (_canLootCreatureForPlayer(itr->GetSource(), creature, lootQualityMask, lootThreshold)) + pLooters.insert(itr->GetSource()); + } + } + + //creature->lootForBody = true; + + if (gr && creature->loot.loot_type == LOOT_NONE) + { + switch (gr->GetLootMethod()) + { + case GROUP_LOOT: gr->GroupLoot(&creature->loot, creature); break; + case NEED_BEFORE_GREED: gr->NeedBeforeGreed(&creature->loot, creature); break; + case MASTER_LOOT: gr->MasterLoot(&creature->loot, creature); break; + default: break; + } + } + + if (creature->loot.loot_type != LOOT_SKINNING) + creature->loot.loot_type = LOOT_CORPSE; + + Player* receiver = pLooters.size() == 1 ? *pLooters.begin() : + creature->loot.roundRobinPlayer ? ObjectAccessor::GetPlayer(*creature, creature->loot.roundRobinPlayer) : nullptr; + + if (!receiver) + { + if (pLooters.empty()) + return; + + ASSERT(pLooters.size() >= 2); + + do + { + receiver = Trinity::Containers::SelectRandomContainerElement(pLooters); + } while (receiver == _prevRRobin); + } + + _prevRRobin = receiver; + + _autoLootCreatureItems(receiver, creature, lootQualityMask, lootThreshold); +} +////////// +//EQUIPS// +////////// +bool bot_ai::_canUseOffHand() const +{ + //bm can on only equip in main hand + if (_botclass == BOT_CLASS_BM) + return false; + //sphynx can grab anything + if (_botclass == BOT_CLASS_SPHYNX) + return true; + //dreadlord / cryptlord can on only equip in main hand + if (_botclass == BOT_CLASS_DREADLORD || _botclass == BOT_CLASS_CRYPT_LORD) + return false; + //staff-only classes + if (_botclass == BOT_CLASS_ARCHMAGE || _botclass == BOT_CLASS_NECROMANCER) + return false; + + //warrior can wield any offhand with titan's grip + if (_botclass == BOT_CLASS_WARRIOR && me->GetLevel() >= 60 && GetSpec() == BOT_SPEC_WARRIOR_FURY) + return true; + + ItemTemplate const* protoMH = _equips[BOT_SLOT_MAINHAND] ? _equips[BOT_SLOT_MAINHAND]->GetTemplate() : nullptr; + + //no mainhand weapon OR + //mainhand is an one-hand weapon + if (!protoMH) + return true; + + if (protoMH->Class == ITEM_CLASS_WEAPON && + (protoMH->InventoryType == INVTYPE_WEAPON || protoMH->InventoryType == INVTYPE_WEAPONMAINHAND) && + (protoMH->SubClass == ITEM_SUBCLASS_WEAPON_AXE || protoMH->SubClass == ITEM_SUBCLASS_WEAPON_DAGGER || + protoMH->SubClass == ITEM_SUBCLASS_WEAPON_FIST_WEAPON || protoMH->SubClass == ITEM_SUBCLASS_WEAPON_MACE || + protoMH->SubClass == ITEM_SUBCLASS_WEAPON_SWORD)) + return true; + + //NO + return false; +} + +bool bot_ai::_canUseRanged() const +{ + return (_botclass == BOT_CLASS_HUNTER || _botclass == BOT_CLASS_ROGUE || + _botclass == BOT_CLASS_WARRIOR || _botclass == BOT_CLASS_PRIEST || + _botclass == BOT_CLASS_MAGE || _botclass == BOT_CLASS_WARLOCK || + _botclass == BOT_CLASS_DARK_RANGER || _botclass == BOT_CLASS_SEA_WITCH); +} + +bool bot_ai::_canUseRelic() const +{ + return (_botclass == BOT_CLASS_PALADIN || _botclass == BOT_CLASS_SHAMAN || + _botclass == BOT_CLASS_DRUID || _botclass == BOT_CLASS_DEATH_KNIGHT); +} + +bool bot_ai::_canEquip(ItemTemplate const* newProto, uint8 slot, bool ignoreItemLevel, Item const* newItem) const +{ + EquipmentInfo const* einfo = BotDataMgr::GetBotEquipmentInfo(me->GetEntry()); + + if (Item const* oldItem = _equips[slot]) + { + ItemTemplate const* oldProto = oldItem->GetTemplate(); + //prevent reequipping same items + if (newProto->ItemId == oldProto->ItemId && (!newItem || !newItem->GetItemRandomPropertyId())) + return false; + //prevent equipping worse items (only standard or not) + if (!ignoreItemLevel) + if (slot > BOT_SLOT_RANGED || einfo->ItemEntry[slot] != oldProto->ItemId) + if (IAmFree() || !master->IsGameMaster()) + if (_getItemGearStatScore(oldProto, slot, oldItem) > _getItemGearStatScore(newProto, slot, newItem)) + return false; + } + + if (slot == BOT_SLOT_OFFHAND && !_canUseOffHand()) + return false; + + //level requirements + if (me->GetLevel() < newProto->RequiredLevel) + return false; + + //class requirements + if (_botclass < BOT_CLASS_EX_START && !(newProto->AllowableClass & (1<<(_botclass-1)))) + return false; + + //skip race requirements + + //inventory related conditions + if (newProto->Class == ITEM_CLASS_WEAPON) + { + switch (slot) + { + case BOT_SLOT_MAINHAND: + switch (_botclass) + { + case BOT_CLASS_BM: + if (newProto->SubClass != ITEM_SUBCLASS_WEAPON_POLEARM && + newProto->SubClass != ITEM_SUBCLASS_WEAPON_AXE2 && + newProto->SubClass != ITEM_SUBCLASS_WEAPON_SWORD2) + return false; + break; + case BOT_CLASS_ARCHMAGE: + case BOT_CLASS_NECROMANCER: + if (newProto->SubClass != ITEM_SUBCLASS_WEAPON_STAFF) + return false; + break; + default: + break; + } + break; + case BOT_SLOT_OFFHAND: + if (newProto->SubClass == ITEM_SUBCLASS_WEAPON_POLEARM || newProto->SubClass == ITEM_SUBCLASS_WEAPON_STAFF) + return false; + switch (_botclass) + { + case BOT_CLASS_DEATH_KNIGHT: + case BOT_CLASS_ROGUE: + break; + case BOT_CLASS_WARRIOR: + case BOT_CLASS_HUNTER: + if (me->GetLevel() < 20) + return false; + break; + case BOT_CLASS_SHAMAN: + if (me->GetLevel() < 40 || _spec != BOT_SPEC_SHAMAN_ENHANCEMENT) + return false; + break; + case BOT_CLASS_SPHYNX: + break; + case BOT_CLASS_DARK_RANGER: + break; + case BOT_CLASS_SEA_WITCH: + break; + default: + return false; + } + break; + case BOT_SLOT_RANGED: + if (!_canUseRanged()) + return false; + break; + default: + return false; + } + + switch (newProto->InventoryType) + { + case INVTYPE_WEAPONMAINHAND: + if (slot != BOT_SLOT_MAINHAND) + return false; + break; + case INVTYPE_WEAPONOFFHAND: + if (slot != BOT_SLOT_OFFHAND) + return false; + break; + case INVTYPE_2HWEAPON: + switch (_botclass) + { + case BOT_CLASS_WARRIOR: + switch (slot) + { + case BOT_SLOT_OFFHAND: + if (me->GetLevel() < 60) + return false; + break; + case BOT_SLOT_RANGED: + return false; + default: + break; + } + break; + case BOT_CLASS_SPHYNX: + break; + default: + if (slot != BOT_SLOT_MAINHAND) + return false; + break; + } + break; + case INVTYPE_WEAPON: + if (slot != BOT_SLOT_MAINHAND && slot != BOT_SLOT_OFFHAND) + return false; + break; + case INVTYPE_THROWN: + case INVTYPE_RANGED: + if (slot != BOT_SLOT_RANGED) + return false; + break; + case INVTYPE_RANGEDRIGHT: + switch (_botclass) + { + case BOT_CLASS_SPHYNX: + if (slot != BOT_SLOT_MAINHAND && slot != BOT_SLOT_OFFHAND) + return false; + break; + default: + if (slot != BOT_SLOT_RANGED) + return false; + break; + } + break; + default: + return false; + } + + switch (_botclass) + { + case BOT_CLASS_WARRIOR: + switch (newProto->SubClass) + { + case ITEM_SUBCLASS_WEAPON_AXE: + case ITEM_SUBCLASS_WEAPON_AXE2: + case ITEM_SUBCLASS_WEAPON_MACE: + case ITEM_SUBCLASS_WEAPON_MACE2: + case ITEM_SUBCLASS_WEAPON_SWORD: + case ITEM_SUBCLASS_WEAPON_SWORD2: + case ITEM_SUBCLASS_WEAPON_POLEARM: + case ITEM_SUBCLASS_WEAPON_STAFF: + case ITEM_SUBCLASS_WEAPON_FIST_WEAPON: + case ITEM_SUBCLASS_WEAPON_DAGGER: + case ITEM_SUBCLASS_WEAPON_BOW: + case ITEM_SUBCLASS_WEAPON_CROSSBOW: + case ITEM_SUBCLASS_WEAPON_GUN: + case ITEM_SUBCLASS_WEAPON_THROWN: + //case ITEM_SUBCLASS_WEAPON_WAND: + break; + default: + return false; + } + break; + case BOT_CLASS_PALADIN: + case BOT_CLASS_DEATH_KNIGHT: + switch (newProto->SubClass) + { + case ITEM_SUBCLASS_WEAPON_AXE: + case ITEM_SUBCLASS_WEAPON_AXE2: + case ITEM_SUBCLASS_WEAPON_MACE: + case ITEM_SUBCLASS_WEAPON_MACE2: + case ITEM_SUBCLASS_WEAPON_SWORD: + case ITEM_SUBCLASS_WEAPON_SWORD2: + case ITEM_SUBCLASS_WEAPON_POLEARM: + break; + default: + return false; + } + break; + case BOT_CLASS_HUNTER: + switch (newProto->SubClass) + { + case ITEM_SUBCLASS_WEAPON_AXE: + case ITEM_SUBCLASS_WEAPON_AXE2: + //case ITEM_SUBCLASS_WEAPON_MACE: + //case ITEM_SUBCLASS_WEAPON_MACE2: + case ITEM_SUBCLASS_WEAPON_SWORD: + case ITEM_SUBCLASS_WEAPON_SWORD2: + case ITEM_SUBCLASS_WEAPON_POLEARM: + case ITEM_SUBCLASS_WEAPON_STAFF: + case ITEM_SUBCLASS_WEAPON_FIST_WEAPON: + case ITEM_SUBCLASS_WEAPON_DAGGER: + case ITEM_SUBCLASS_WEAPON_BOW: + case ITEM_SUBCLASS_WEAPON_CROSSBOW: + case ITEM_SUBCLASS_WEAPON_GUN: + //case ITEM_SUBCLASS_WEAPON_THROWN: //hunters can use thrown but bots can't, also pointless + break; + default: + return false; + } + break; + case BOT_CLASS_ROGUE: + switch (newProto->SubClass) + { + case ITEM_SUBCLASS_WEAPON_AXE: + case ITEM_SUBCLASS_WEAPON_MACE: + case ITEM_SUBCLASS_WEAPON_SWORD: + case ITEM_SUBCLASS_WEAPON_FIST_WEAPON: + case ITEM_SUBCLASS_WEAPON_DAGGER: + case ITEM_SUBCLASS_WEAPON_BOW: + case ITEM_SUBCLASS_WEAPON_CROSSBOW: + case ITEM_SUBCLASS_WEAPON_GUN: + case ITEM_SUBCLASS_WEAPON_THROWN: + break; + default: + return false; + } + break; + case BOT_CLASS_PRIEST: + switch (newProto->SubClass) + { + case ITEM_SUBCLASS_WEAPON_MACE: + case ITEM_SUBCLASS_WEAPON_STAFF: + case ITEM_SUBCLASS_WEAPON_DAGGER: + case ITEM_SUBCLASS_WEAPON_WAND: + break; + default: + return false; + } + break; + case BOT_CLASS_MAGE: + case BOT_CLASS_WARLOCK: + switch (newProto->SubClass) + { + case ITEM_SUBCLASS_WEAPON_SWORD: + case ITEM_SUBCLASS_WEAPON_STAFF: + case ITEM_SUBCLASS_WEAPON_DAGGER: + case ITEM_SUBCLASS_WEAPON_WAND: + break; + default: + return false; + } + break; + case BOT_CLASS_DRUID: + switch (newProto->SubClass) + { + case ITEM_SUBCLASS_WEAPON_MACE: + case ITEM_SUBCLASS_WEAPON_MACE2: + case ITEM_SUBCLASS_WEAPON_POLEARM: + case ITEM_SUBCLASS_WEAPON_STAFF: + case ITEM_SUBCLASS_WEAPON_FIST_WEAPON: + case ITEM_SUBCLASS_WEAPON_DAGGER: + break; + default: + return false; + } + break; + case BOT_CLASS_SHAMAN: + switch (newProto->SubClass) + { + case ITEM_SUBCLASS_WEAPON_AXE: + case ITEM_SUBCLASS_WEAPON_AXE2: + case ITEM_SUBCLASS_WEAPON_MACE: + case ITEM_SUBCLASS_WEAPON_MACE2: + case ITEM_SUBCLASS_WEAPON_STAFF: + case ITEM_SUBCLASS_WEAPON_FIST_WEAPON: + case ITEM_SUBCLASS_WEAPON_DAGGER: + break; + default: + return false; + } + break; + case BOT_CLASS_BM: + switch (newProto->SubClass) + { + case ITEM_SUBCLASS_WEAPON_AXE2: + case ITEM_SUBCLASS_WEAPON_SWORD2: + case ITEM_SUBCLASS_WEAPON_POLEARM: + break; + default: + return false; + } + break; + case BOT_CLASS_SPHYNX: + switch (newProto->SubClass) + { + case ITEM_SUBCLASS_WEAPON_WAND: + break; + default: + return false; + } + break; + case BOT_CLASS_ARCHMAGE: + case BOT_CLASS_NECROMANCER: + switch (newProto->SubClass) + { + case ITEM_SUBCLASS_WEAPON_STAFF: + break; + default: + return false; + } + break; + case BOT_CLASS_DREADLORD: + case BOT_CLASS_CRYPT_LORD: + switch (newProto->SubClass) + { + case ITEM_SUBCLASS_WEAPON_AXE: + case ITEM_SUBCLASS_WEAPON_AXE2: + case ITEM_SUBCLASS_WEAPON_MACE: + case ITEM_SUBCLASS_WEAPON_MACE2: + case ITEM_SUBCLASS_WEAPON_SWORD: + case ITEM_SUBCLASS_WEAPON_SWORD2: + case ITEM_SUBCLASS_WEAPON_POLEARM: + case ITEM_SUBCLASS_WEAPON_STAFF: + case ITEM_SUBCLASS_WEAPON_FIST_WEAPON: + case ITEM_SUBCLASS_WEAPON_DAGGER: + break; + default: + return false; + } + break; + case BOT_CLASS_SPELLBREAKER: + switch (newProto->SubClass) + { + case ITEM_SUBCLASS_WEAPON_AXE: + case ITEM_SUBCLASS_WEAPON_MACE: + case ITEM_SUBCLASS_WEAPON_SWORD: + case ITEM_SUBCLASS_WEAPON_FIST_WEAPON: + case ITEM_SUBCLASS_WEAPON_DAGGER: + break; + default: + return false; + } + break; + case BOT_CLASS_DARK_RANGER: + switch (newProto->SubClass) + { + case ITEM_SUBCLASS_WEAPON_SWORD: + case ITEM_SUBCLASS_WEAPON_DAGGER: + case ITEM_SUBCLASS_WEAPON_BOW: + break; + default: + return false; + } + break; + case BOT_CLASS_SEA_WITCH: + switch (newProto->SubClass) + { + case ITEM_SUBCLASS_WEAPON_DAGGER: + case ITEM_SUBCLASS_WEAPON_BOW: + break; + default: + return false; + } + break; + default: + return false; + } + + return true; + } + else if (newProto->Class == ITEM_CLASS_ARMOR/* || newProto->Class == ITEM_CLASS_QUEST*/) + { + switch (newProto->InventoryType) + { + case INVTYPE_HEAD: + if (slot != BOT_SLOT_HEAD) + return false; + break; + case INVTYPE_SHOULDERS: + if (slot != BOT_SLOT_SHOULDERS) + return false; + break; + case INVTYPE_BODY: + if (slot != BOT_SLOT_BODY) + return false; + switch (_botclass) + { + case BOT_CLASS_SPHYNX: + return false; + default: + break; + } + break; + case INVTYPE_CHEST: + case INVTYPE_ROBE: + if (slot != BOT_SLOT_CHEST) + return false; + break; + case INVTYPE_WAIST: + if (slot != BOT_SLOT_WAIST) + return false; + break; + case INVTYPE_LEGS: + if (slot != BOT_SLOT_LEGS) + return false; + break; + case INVTYPE_FEET: + if (slot != BOT_SLOT_FEET) + return false; + break; + case INVTYPE_WRISTS: + if (slot != BOT_SLOT_WRIST) + return false; + break; + case INVTYPE_HANDS: + if (slot != BOT_SLOT_HANDS) + return false; + break; + case INVTYPE_FINGER: + if (slot != BOT_SLOT_FINGER1 && slot != BOT_SLOT_FINGER2) + return false; + switch (_botclass) + { + case BOT_CLASS_SPHYNX: + return false; + default: + break; + } + break; + case INVTYPE_TRINKET: + if (slot != BOT_SLOT_TRINKET1 && slot != BOT_SLOT_TRINKET2) + return false; + switch (_botclass) + { + case BOT_CLASS_SPHYNX: + return false; + default: + break; + } + break; + case INVTYPE_NECK: + if (slot != BOT_SLOT_NECK) + return false; + switch (_botclass) + { + case BOT_CLASS_SPHYNX: + return false; + default: + break; + } + break; + case INVTYPE_CLOAK: + if (slot != BOT_SLOT_BACK) + return false; + switch (_botclass) + { + case BOT_CLASS_SPHYNX: + return false; + default: + break; + } + break; + case INVTYPE_HOLDABLE: + case INVTYPE_SHIELD: + if (slot != BOT_SLOT_OFFHAND) + return false; + switch (_botclass) + { + case BOT_CLASS_SPHYNX: + return false; + default: + break; + } + break; + case INVTYPE_RELIC: + if (slot != BOT_SLOT_RANGED) + return false; + break; + default: + return false; + } + + switch (newProto->SubClass) + { + case ITEM_SUBCLASS_ARMOR_SHIELD: + if (slot != BOT_SLOT_OFFHAND) + return false; + switch (_botclass) + { + case BOT_CLASS_SPELLBREAKER: + break; + case BOT_CLASS_WARRIOR: + case BOT_CLASS_PALADIN: + case BOT_CLASS_SHAMAN: + break; + default: + return false; + } + break; + case ITEM_SUBCLASS_ARMOR_PLATE: + switch (_botclass) + { + case BOT_CLASS_BM: + case BOT_CLASS_SPHYNX: + case BOT_CLASS_DREADLORD: + case BOT_CLASS_SPELLBREAKER: + case BOT_CLASS_CRYPT_LORD: + break; + case BOT_CLASS_WARRIOR: + case BOT_CLASS_PALADIN: + case BOT_CLASS_DEATH_KNIGHT: + if (me->GetLevel() >= 40 || newProto->Quality == ITEM_QUALITY_HEIRLOOM) + break; + return false; + default: + return false; + } + break; + case ITEM_SUBCLASS_ARMOR_MAIL: + switch (_botclass) + { + case BOT_CLASS_BM: + case BOT_CLASS_SPHYNX: + case BOT_CLASS_SPELLBREAKER: + case BOT_CLASS_CRYPT_LORD: + break; + case BOT_CLASS_WARRIOR: + case BOT_CLASS_PALADIN: + case BOT_CLASS_DEATH_KNIGHT: + break; + case BOT_CLASS_SHAMAN: + case BOT_CLASS_HUNTER: + if (me->GetLevel() >= 40 || newProto->Quality == ITEM_QUALITY_HEIRLOOM) + break; + return false; + default: + return false; + } + break; + case ITEM_SUBCLASS_ARMOR_LEATHER: + switch (_botclass) + { + case BOT_CLASS_DARK_RANGER: + break; + case BOT_CLASS_WARRIOR: + case BOT_CLASS_PALADIN: + case BOT_CLASS_DEATH_KNIGHT: + case BOT_CLASS_BM: + case BOT_CLASS_SHAMAN: + case BOT_CLASS_HUNTER: + case BOT_CLASS_ROGUE: + case BOT_CLASS_DRUID: + break; + default: + return false; + } + break; + case ITEM_SUBCLASS_ARMOR_CLOTH: + switch (_botclass) + { + case BOT_CLASS_SPHYNX: + return false; + case BOT_CLASS_DREADLORD: + case BOT_CLASS_SPELLBREAKER: + case BOT_CLASS_CRYPT_LORD: + if (newProto->InventoryType != INVTYPE_CLOAK) + return false; + break; + default: + break; + } + break; + case ITEM_SUBCLASS_ARMOR_MISCELLANEOUS: + switch (_botclass) + { + case BOT_CLASS_SPHYNX: + return false; + default: + break; + } + break; + case ITEM_SUBCLASS_ARMOR_LIBRAM: + switch (_botclass) + { + case BOT_CLASS_PALADIN: + break; + default: + return false; + } + break; + case ITEM_SUBCLASS_ARMOR_IDOL: + switch (_botclass) + { + case BOT_CLASS_DRUID: + break; + default: + return false; + } + break; + case ITEM_SUBCLASS_ARMOR_TOTEM: + switch (_botclass) + { + case BOT_CLASS_SHAMAN: + break; + default: + return false; + } + break; + case ITEM_SUBCLASS_ARMOR_SIGIL: + switch (_botclass) + { + case BOT_CLASS_DEATH_KNIGHT: + break; + default: + return false; + } + break; + default: + return false; + } + + return true; + } + + return false; +} + +void bot_ai::_removeEquipment(uint8 slot) +{ + Item* item = _equips[slot]; + if (!item) + return; //already unequipped + + _usableItemSlotsMask &= ~(1ul << slot); + + RemoveItemBonuses(slot); + ApplyItemSetBonuses(item, false); + + if (slot == BOT_SLOT_OFFHAND) + { + if (me->CanDualWield()) + me->SetCanDualWield(false); + if (!(me->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_NO_BLOCK)) + const_cast(me->GetCreatureTemplate())->flags_extra |= CREATURE_FLAG_EXTRA_NO_BLOCK; + } + + _equips[slot] = nullptr; +} + +bool bot_ai::_unequip(uint8 slot, ObjectGuid receiver) +{ + EquipmentInfo const* einfo = BotDataMgr::GetBotEquipmentInfo(me->GetEntry()); + ASSERT(einfo, "Trying to unequip item for bot with no equip info!"); + + Item* item = _equips[slot]; + if (!item) + return true; //already unequipped + + uint32 itemId = item->GetEntry(); + + BotLogger::Log(NPCBOT_LOG_UNEQUIP, me, uint32(slot), uint32(item->GetGUID().GetCounter()), uint32(itemId), uint32(receiver.GetCounter())); + + _removeEquipment(slot); + + //hand old weapon to master + if (receiver && (slot > BOT_SLOT_RANGED || einfo->ItemEntry[slot] != itemId)) + { + if (receiver == master->GetGUID()) + { + ItemPosCountVec dest; + uint32 no_space = 0; + InventoryResult msg = master->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, itemId, 1, &no_space); + if (msg != EQUIP_ERR_OK) + { + std::ostringstream istr; + _AddItemLink(master, item, istr, false); + ChatHandler ch(master->GetSession()); + ch.PSendSysMessage(LocalizedNpcText(master, BOT_TEXT_CANT_UNEQUIP_MAILING).c_str(), istr.str().c_str()); + + item->SetOwnerGUID(master->GetGUID()); + + CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); + item->FSetState(ITEM_CHANGED); + item->SaveToDB(trans); + MailDraft(istr.str(), "").AddItem(item).SendMailTo(trans, MailReceiver(master), MailSender(me)); + CharacterDatabase.CommitTransaction(trans); + + //master->SendEquipError(msg, nullptr, nullptr, itemId); + //return false; + } + else + { + Item* pItem = master->StoreItem(dest, item, true); + master->SendNewItem(pItem, 1, true, false, false); + } + } + else + { + item->SetOwnerGUID(receiver); + + CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); + item->FSetState(ITEM_CHANGED); + item->SaveToDB(trans); + static const std::string subject = LocalizedNpcText(nullptr, BOT_TEXT_OWNERSHIP_EXPIRED); + MailDraft(subject, "").AddItem(item).SendMailTo(trans, MailReceiver(receiver.GetCounter()), MailSender(me)); + CharacterDatabase.CommitTransaction(trans); + } + } + else + { + //slot < BOT_SLOT_RANGED && einfo->ItemEntry[slot] == itemId + //we have our standard weapon which we should get rid of + //item->SetState(ITEM_REMOVED, master); //delete Item object + delete item; //!Invalidated! + //item = nullptr; //already in "_updateEquips(slot, nullptr);" + } + + if (slot <= BOT_SLOT_RANGED && CanChangeEquip(slot)) //weapons + { + me->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + slot, 0); + me->SetAttackTime(WeaponAttackType(slot), BASE_ATTACK_TIME); //without weapon + } + + _updateEquips(slot, nullptr); + return true; +} + +bool bot_ai::_equip(uint8 slot, Item* newItem, ObjectGuid receiver) +{ + ASSERT(newItem); + + EquipmentInfo const* einfo = BotDataMgr::GetBotEquipmentInfo(me->GetEntry()); + ASSERT(einfo, "Trying to equip item for bot with no equip info!"); + + ItemTemplate const* proto = newItem->GetTemplate(); + + if (newItem->GetState() == ITEM_REMOVED) + { + TC_LOG_ERROR("entities.player", + "minion_ai::_equip(): player {} ({}) is trying to make bot {} (id: {}) equip item: {} (id: {}, {}) which has state ITEM_REMOVED!", + master->GetName(), master->GetGUID().ToString(), me->GetName(), me->GetEntry(), proto->Name1, proto->ItemId, newItem->GetGUID().ToString()); + return false; + } + + uint32 newItemId = newItem->GetEntry(); + + if (Item const* oldItem = _equips[slot]) + { + //same id + if (oldItem->GetEntry() == newItemId && !newItem->GetItemRandomPropertyId()) + return false; + } + + if (!_unequip(slot, receiver)) + { + //BotWhisper("You have no space for my current item", master); + return false; + } + + BotLogger::Log(NPCBOT_LOG_EQUIP, me, uint32(slot), uint32(newItem->GetGUID().GetCounter()), uint32(newItemId), uint32(receiver.GetCounter())); + + if (receiver && (slot > BOT_SLOT_RANGED || einfo->ItemEntry[slot] != newItemId)) + { + ASSERT(receiver == master->GetGUID()); + + //cheating + if (newItem->GetOwnerGUID() != master->GetGUID() || !master->HasItemCount(newItemId, 1)) + { + //std::ostringstream msg; + //msg << "Cannot find "; + //_AddItemLink(master, newItem, msg, false); + //msg << " (id: " << uint32(newItemId) << ")!"; + //BotWhisper(msg.str().c_str()); + + TC_LOG_ERROR("entities.player", + "minion_ai::_equip(): player {} ({}) is trying to make bot {} (id: {}) equip item: {} (id: {}, {}) but either does not have this item or does not own it", + master->GetName(), master->GetGUID().ToString(), me->GetName(), me->GetEntry(), proto->Name1, proto->ItemId, newItem->GetGUID().ToString()); + return false; + } + + master->MoveItemFromInventory(newItem->GetBagSlot(), newItem->GetSlot(), true); + //Item is removed from inventory table in _updateEquips(slot, newItem); + //newItem->SetOwnerGUID(ObjectGuid::Empty); //needed to prevent some logs to be sent to master, restored at unequip + } + + if (slot <= BOT_SLOT_RANGED) + { + if (CanChangeEquip(slot)) + { + NpcBotTransmogData const* transmogData = BotDataMgr::SelectNpcBotTransmogs(me->GetEntry()); + if (einfo->ItemEntry[slot] != newItemId && transmogData && BotMgr::IsTransmogEnabled() && (transmogData->transmogs[slot].first == newItemId || BotMgr::TransmogUseEquipmentSlots()) && + transmogData->transmogs[slot].second >= 0) + me->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + slot, uint32(transmogData->transmogs[slot].second)); + else + me->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + slot, newItemId); + } + uint32 delay = + /*einfo->ItemEntry[slot] != newItemId || */RespectEquipsAttackTime() || slot == BOT_SLOT_OFFHAND ? proto->Delay : + slot == BOT_SLOT_RANGED ? me->GetCreatureTemplate()->RangeAttackTime : me->GetCreatureTemplate()->BaseAttackTime; + //attack time will be updated in SetStats() -> OnMeleeDamageUpdate() + if (!me->IsInFeralForm()) + me->SetAttackTime(WeaponAttackType(slot), delay); //set attack speed + } + + if (IsUsableItem(newItem)) + { + uint32 slotMask = 1ul << slot; + ASSERT(!(_usableItemSlotsMask & slotMask)); + _usableItemSlotsMask |= slotMask; + } + + _updateEquips(slot, newItem); + + //only for non-standard items + if (slot > BOT_SLOT_RANGED || einfo->ItemEntry[slot] != newItemId) + ApplyItemBonuses(slot); + ApplyItemSetBonuses(newItem, true); + + if (slot == BOT_SLOT_OFFHAND) + { + if (proto->Class == ITEM_CLASS_WEAPON) + { + if (!me->CanDualWield()) + me->SetCanDualWield(true); + } + else if (proto->Class == ITEM_CLASS_ARMOR && proto->SubClass == ITEM_SUBCLASS_ARMOR_SHIELD) + { + if (me->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_NO_BLOCK) + const_cast(me->GetCreatureTemplate())->flags_extra &= ~CREATURE_FLAG_EXTRA_NO_BLOCK; + } + } + else if (slot == BOT_SLOT_MAINHAND) + { + if (proto->InventoryType == INVTYPE_2HWEAPON && !(_botclass == BOT_CLASS_WARRIOR && me->GetLevel() >= 60 && GetSpec() == BOT_SPEC_WARRIOR_FURY)) + { + //if have incompatible offhand unequip it + if (_equips[BOT_SLOT_OFFHAND] != nullptr) + _unequip(BOT_SLOT_OFFHAND, receiver); + } + else if (_equips[BOT_SLOT_OFFHAND] == nullptr && einfo->ItemEntry[BOT_SLOT_OFFHAND]) + _resetEquipment(BOT_SLOT_OFFHAND, receiver); + } + + //send info to class ai + if (proto->Class == ITEM_CLASS_WEAPON) + { + if (slot == BOT_SLOT_MAINHAND) + { + SetAIMiscValue(BOTAI_MISC_DAGGER_MAINHAND, proto->SubClass == ITEM_SUBCLASS_WEAPON_DAGGER); + SetAIMiscValue(BOTAI_MISC_ENCHANT_CAN_EXPIRE_MH, newItem->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT)); + SetAIMiscValue(BOTAI_MISC_WEAPON_SPEC, proto->SubClass); + } + if (slot == BOT_SLOT_OFFHAND) + { + SetAIMiscValue(BOTAI_MISC_DAGGER_OFFHAND, proto->SubClass == ITEM_SUBCLASS_WEAPON_DAGGER); + SetAIMiscValue(BOTAI_MISC_ENCHANT_CAN_EXPIRE_OH, newItem->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT)); + } + } + + return true; +} + +void bot_ai::_updateEquips(uint8 slot, Item* item) +{ + _equips[slot] = item; + BotDataMgr::UpdateNpcBotData(me->GetEntry(), NPCBOT_UPDATE_EQUIPS, _equips); +} +//Called from gossip menu only (applies only to weapons) +bool bot_ai::_resetEquipment(uint8 slot, ObjectGuid receiver) +{ + if (IsWanderer()) + { + TC_LOG_ERROR("scripts", "bot_ai::_resetEquipment() is called for wanderer bot {} ({}), slot {}!", + me->GetName(), me->GetEntry(), uint32(slot)); + return false; + } + + ASSERT(slot <= BOT_SLOT_RANGED); + + EquipmentInfo const* einfo = BotDataMgr::GetBotEquipmentInfo(me->GetEntry()); + ASSERT(einfo, "Trying to reset equipment for bot with no equip info!"); + + uint32 itemId = einfo->ItemEntry[slot]; + Item const* oldItem = _equips[slot]; + + BotLogger::Log(NPCBOT_LOG_EQUIP_RESET, me, uint32(slot), uint32(oldItem ? oldItem->GetGUID().GetCounter() : 0), uint32(oldItem ? oldItem->GetEntry() : 0), uint32(receiver.GetCounter()), uint32(itemId)); + + if (!itemId) + return _unequip(slot, receiver); + else if (oldItem) + if (oldItem->GetEntry() == itemId) + return true; + + if (slot == BOT_SLOT_MAINHAND && !(_botclass == BOT_CLASS_WARRIOR && me->GetLevel() >= 60 && GetSpec() == BOT_SPEC_WARRIOR_FURY)) + { + if (ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId)) + { + if (proto->Class == ITEM_CLASS_WEAPON && + (proto->SubClass == ITEM_SUBCLASS_WEAPON_AXE2 || proto->SubClass == ITEM_SUBCLASS_WEAPON_MACE2 || + proto->SubClass == ITEM_SUBCLASS_WEAPON_SWORD2 || proto->SubClass == ITEM_SUBCLASS_WEAPON_POLEARM || + proto->SubClass == ITEM_SUBCLASS_WEAPON_STAFF || proto->SubClass == ITEM_SUBCLASS_WEAPON_SPEAR)) + { + if (!_unequip(BOT_SLOT_OFFHAND, receiver)) + return false; + } + } + } + + //we have our standard weapon itemId which we should use to create new item + Item* stItem = Item::CreateItem(itemId, 1, nullptr); + ASSERT(stItem, "Failed to create standard Item for bot!"); + + if (!_equip(slot, stItem, receiver)) + { + TC_LOG_ERROR("entities.player", "minion_ai::_resetEquipment(): player {} ({}) failed to reset equipment for bot {} (id: {}) in slot {}", + master->GetName(), master->GetGUID().ToString(), me->GetName(), me->GetEntry(), slot); + return false; + } + return true; +} + +void bot_ai::ApplyItemBonuses(uint8 slot) +{ + //ensurance to set zeros + RemoveItemBonuses(slot); + + Item* item = _equips[slot]; + if (!item) + return; + + ItemTemplate const* proto = sObjectMgr->GetItemTemplate(item->GetEntry()); + if (!proto) + return; + + ScalingStatDistributionEntry const* ssd = proto->ScalingStatDistribution ? sScalingStatDistributionStore.LookupEntry(proto->ScalingStatDistribution) : nullptr; + + uint32 ssd_level = me->GetLevel(); + if (ssd && ssd_level > ssd->Maxlevel) + ssd_level = ssd->Maxlevel; + + ScalingStatValuesEntry const* ssv = proto->ScalingStatValue ? sScalingStatValuesStore.LookupEntry(ssd_level) : nullptr; + + for (uint8 i = 0; i != MAX_ITEM_PROTO_STATS; ++i) + { + uint32 statType = 0; + int32 val = 0; + if (ssd && ssv) + { + if (ssd->StatID[i] < 0) + continue; + statType = ssd->StatID[i]; + val = (ssv->getssdMultiplier(proto->ScalingStatValue) * ssd->Bonus[i]) / 10000; + } + else + { + if (i >= proto->StatsCount) + continue; + statType = proto->ItemStat[i].ItemStatType; + val = proto->ItemStat[i].ItemStatValue; + } + + if (val == 0) + continue; + + _stats[slot][statType] += val; + } + + _stats[slot][BOT_STAT_MOD_RESIST_HOLY] += proto->HolyRes; + _stats[slot][BOT_STAT_MOD_RESIST_FIRE] += proto->FireRes; + _stats[slot][BOT_STAT_MOD_RESIST_NATURE] += proto->NatureRes; + _stats[slot][BOT_STAT_MOD_RESIST_FROST] += proto->FrostRes; + _stats[slot][BOT_STAT_MOD_RESIST_SHADOW] += proto->ShadowRes; + _stats[slot][BOT_STAT_MOD_RESIST_ARCANE] += proto->ArcaneRes; + + _stats[slot][BOT_STAT_MOD_ARMOR] += proto->Armor; + _stats[slot][BOT_STAT_MOD_BLOCK_VALUE] += proto->Block; + + EquipmentInfo const* einfo = BotDataMgr::GetBotEquipmentInfo(me->GetEntry()); + if (slot > BOT_SLOT_RANGED || item->GetEntry() != einfo->ItemEntry[slot]) + { + if (ssv) + { + int32 extraDPS = ssv->getDPSMod(proto->ScalingStatValue); + if (extraDPS) + { + float average = extraDPS * proto->Delay / 1000.0f; + float mod = ssv->isTwoHand(proto->ScalingStatValue) ? 0.2f : 0.3f; + + _stats[slot][BOT_STAT_MOD_DAMAGE_MIN] += (1.0f - mod) * average; + _stats[slot][BOT_STAT_MOD_DAMAGE_MAX] += (1.0f + mod) * average; + } + } + else + { + _stats[slot][BOT_STAT_MOD_DAMAGE_MIN] += proto->Damage[0].DamageMin + proto->Damage[1].DamageMin; + _stats[slot][BOT_STAT_MOD_DAMAGE_MAX] += proto->Damage[0].DamageMax + proto->Damage[1].DamageMax; + } + + if (_botclass == BOT_CLASS_DRUID) + { + int32 dpsMod = 0; + int32 feral_bonus = 0; + + if (ssv) + { + dpsMod = ssv->getDPSMod(proto->ScalingStatValue); + feral_bonus += ssv->getFeralBonus(proto->ScalingStatValue); + } + + feral_bonus += proto->getFeralBonus(dpsMod); + if (feral_bonus) + _stats[slot][BOT_STAT_MOD_FERAL_ATTACK_POWER] += feral_bonus; + //ApplyFeralAPBonus(feral_bonus, apply); + } + } + + ApplyItemEnchantments(item, slot); + ApplyItemEquipSpells(item, true); + + shouldUpdateStats = true; +} + +void bot_ai::RemoveItemBonuses(uint8 slot) +{ + Item* item = _equips[slot]; + if (!item) + return; + + ItemTemplate const* proto = sObjectMgr->GetItemTemplate(item->GetEntry()); + if (!proto) + return; + + for (uint8 i = 0; i != MAX_BOT_ITEM_MOD; ++i) + _stats[slot][i] = 0; + + RemoveItemEnchantments(item); //remove spells + ApplyItemEquipSpells(item, false); + + shouldUpdateStats = true; +} + +void bot_ai::ApplyItemEnchantments(Item* item, uint8 slot) +{ + for (uint8 i = 0; i != MAX_ENCHANTMENT_SLOT; ++i) + ApplyItemEnchantment(item, EnchantmentSlot(i), slot); +} + +void bot_ai::ApplyItemEnchantment(Item* item, EnchantmentSlot eslot, uint8 slot) +{ + uint32 enchant_id = item->GetEnchantmentId(eslot); + if (!enchant_id) + return; + + SpellItemEnchantmentEntry const* pEnchant = sSpellItemEnchantmentStore.LookupEntry(enchant_id); + if (!pEnchant) + return; + + if (pEnchant->MinLevel > me->GetLevel()) + return; + + uint32 enchant_display_type; + uint32 enchant_amount; + uint32 enchant_spell_id; + + for (uint8 s = 0; s != MAX_ITEM_ENCHANTMENT_EFFECTS; ++s) + { + enchant_display_type = pEnchant->Effect[s]; + enchant_amount = pEnchant->EffectPointsMin[s]; + enchant_spell_id = pEnchant->EffectArg[s]; + + switch (enchant_display_type) + { + case ITEM_ENCHANTMENT_TYPE_DAMAGE: + _stats[slot][BOT_STAT_MOD_DAMAGE_MIN] += enchant_amount; + _stats[slot][BOT_STAT_MOD_DAMAGE_MAX] += enchant_amount; + break; + case ITEM_ENCHANTMENT_TYPE_EQUIP_SPELL: + if (enchant_spell_id) + { + int32 basepoints = 0; + // Random Property Exist - try found basepoints for spell (basepoints depends from item suffix factor) + if (item->GetItemRandomPropertyId()) + { + ItemRandomSuffixEntry const* item_rand = sItemRandomSuffixStore.LookupEntry(abs(item->GetItemRandomPropertyId())); + if (item_rand) + { + // Search enchant_amount + for (uint8 k = 0; k != MAX_ITEM_ENCHANTMENT_EFFECTS; ++k) + { + if (item_rand->Enchantment[k] == enchant_id) + { + basepoints = int32((item_rand->AllocationPct[k] * item->GetItemSuffixFactor()) / 10000); + break; + } + } + } + } + // Cast custom spell vs all equal basepoints got from enchant_amount + CastSpellExtraArgs args(item); + if (basepoints) + for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) + args.AddSpellMod(SpellValueMod(SPELLVALUE_BASE_POINT0 + i), basepoints); + me->CastSpell(me, enchant_spell_id, args); + } + break; + case ITEM_ENCHANTMENT_TYPE_RESISTANCE: + if (!enchant_amount) + { + ItemRandomSuffixEntry const* item_rand = sItemRandomSuffixStore.LookupEntry(abs(item->GetItemRandomPropertyId())); + if (item_rand) + { + for (uint8 k = 0; k < MAX_ITEM_ENCHANTMENT_EFFECTS; ++k) + { + if (item_rand->Enchantment[k] == enchant_id) + { + enchant_amount = uint32((item_rand->AllocationPct[k] * item->GetItemSuffixFactor()) / 10000); + break; + } + } + } + } + _stats[slot][BOT_STAT_MOD_RESISTANCE_START + enchant_spell_id] += enchant_amount; + break; + case ITEM_ENCHANTMENT_TYPE_STAT: + { + if (!enchant_amount) + { + ItemRandomSuffixEntry const* item_rand_suffix = sItemRandomSuffixStore.LookupEntry(abs(item->GetItemRandomPropertyId())); + if (item_rand_suffix) + { + for (uint8 k = 0; k != MAX_ITEM_ENCHANTMENT_EFFECTS; ++k) + { + if (item_rand_suffix->Enchantment[k] == enchant_id) + { + enchant_amount = uint32((item_rand_suffix->AllocationPct[k] * item->GetItemSuffixFactor()) / 10000); + break; + } + } + } + } + + switch (enchant_spell_id) + { + case ITEM_MOD_MANA: + case ITEM_MOD_HEALTH: + case ITEM_MOD_AGILITY: + case ITEM_MOD_STRENGTH: + case ITEM_MOD_INTELLECT: + case ITEM_MOD_SPIRIT: + case ITEM_MOD_STAMINA: + case ITEM_MOD_DEFENSE_SKILL_RATING: + case ITEM_MOD_DODGE_RATING: + case ITEM_MOD_PARRY_RATING: + case ITEM_MOD_BLOCK_RATING: + case ITEM_MOD_HIT_MELEE_RATING: + case ITEM_MOD_HIT_RANGED_RATING: + case ITEM_MOD_HIT_SPELL_RATING: + case ITEM_MOD_CRIT_MELEE_RATING: + case ITEM_MOD_CRIT_RANGED_RATING: + case ITEM_MOD_CRIT_SPELL_RATING: + //case ITEM_MOD_HIT_TAKEN_MELEE_RATING: + //case ITEM_MOD_HIT_TAKEN_RANGED_RATING: + //case ITEM_MOD_HIT_TAKEN_SPELL_RATING: + //case ITEM_MOD_CRIT_TAKEN_MELEE_RATING: + //case ITEM_MOD_CRIT_TAKEN_RANGED_RATING: + //case ITEM_MOD_CRIT_TAKEN_SPELL_RATING: + case ITEM_MOD_HASTE_MELEE_RATING: + case ITEM_MOD_HASTE_RANGED_RATING: + case ITEM_MOD_HASTE_SPELL_RATING: + case ITEM_MOD_HIT_RATING: + case ITEM_MOD_CRIT_RATING: + case ITEM_MOD_HASTE_RATING: + case ITEM_MOD_RESILIENCE_RATING: + case ITEM_MOD_EXPERTISE_RATING: + case ITEM_MOD_ATTACK_POWER: + case ITEM_MOD_RANGED_ATTACK_POWER: + case ITEM_MOD_MANA_REGENERATION: + case ITEM_MOD_ARMOR_PENETRATION_RATING: + case ITEM_MOD_SPELL_POWER: + case ITEM_MOD_HEALTH_REGEN: + case ITEM_MOD_SPELL_PENETRATION: + case ITEM_MOD_BLOCK_VALUE: + case ITEM_MOD_SPELL_HEALING_DONE: // deprecated + case ITEM_MOD_SPELL_DAMAGE_DONE: // deprecated + _stats[slot][enchant_spell_id] += enchant_amount; + break; + default: + break; + } + break; + } + case ITEM_ENCHANTMENT_TYPE_TOTEM: // Shaman Rockbiter Weapon + case ITEM_ENCHANTMENT_TYPE_USE_SPELL: + case ITEM_ENCHANTMENT_TYPE_PRISMATIC_SOCKET: + break; + default: + break; + } + } +} + +void bot_ai::RemoveItemEnchantments(Item const* item) +{ + for (uint8 i = 0; i != MAX_ENCHANTMENT_SLOT; ++i) + RemoveItemEnchantment(item, EnchantmentSlot(i)); +} + +void bot_ai::RemoveItemEnchantment(Item const* item, EnchantmentSlot eslot) +{ + uint32 enchant_id = item->GetEnchantmentId(eslot); + if (!enchant_id) + return; + + SpellItemEnchantmentEntry const* pEnchant = sSpellItemEnchantmentStore.LookupEntry(enchant_id); + if (!pEnchant) + return; + + ////skip level reqs + //if (pEnchant->MinLevel > me->GetLevel()) + // return; + + uint32 enchant_display_type; + //uint32 enchant_amount; + uint32 enchant_spell_id; + + for (uint8 s = 0; s != MAX_ITEM_ENCHANTMENT_EFFECTS; ++s) + { + enchant_display_type = pEnchant->Effect[s]; + //enchant_amount = pEnchant->EffectPointsMin[s]; + enchant_spell_id = pEnchant->EffectArg[s]; + + switch (enchant_display_type) + { + case ITEM_ENCHANTMENT_TYPE_DAMAGE: + //Already removed in RemoveItemBonuses() + break; + case ITEM_ENCHANTMENT_TYPE_EQUIP_SPELL: + if (enchant_spell_id) + me->RemoveAurasDueToItemSpell(enchant_spell_id, item->GetGUID()); + break; + case ITEM_ENCHANTMENT_TYPE_RESISTANCE: + //Already removed in RemoveItemBonuses() + break; + case ITEM_ENCHANTMENT_TYPE_STAT: + //Already removed in RemoveItemBonuses() + break; + case ITEM_ENCHANTMENT_TYPE_TOTEM: // Shaman Rockbiter Weapon + case ITEM_ENCHANTMENT_TYPE_USE_SPELL: + case ITEM_ENCHANTMENT_TYPE_PRISMATIC_SOCKET: + break; + default: + break; + } + } +} + +void bot_ai::RemoveItemClassEnchantment(uint8 slot) +{ + uint8 eslot = TEMP_ENCHANTMENT_SLOT; + + if (!GetAIMiscValue(slot == BOT_SLOT_MAINHAND ? BOTAI_MISC_ENCHANT_CAN_EXPIRE_MH : BOTAI_MISC_ENCHANT_CAN_EXPIRE_OH)) + return; + + Item* weap = _equips[slot]; + if (!weap || !weap->GetEnchantmentId(EnchantmentSlot(eslot))) + return; + + RemoveItemEnchantment(weap, EnchantmentSlot(eslot)); + + for (uint8 i = 0; i != MAX_ITEM_ENCHANTMENT_EFFECTS; ++i) + weap->SetUInt32Value(ITEM_FIELD_ENCHANTMENT_1_1 + eslot*MAX_ENCHANTMENT_OFFSET + i, 0); +} + +void bot_ai::RemoveItemClassEnchantments() +{ + for (uint8 k = BOT_SLOT_MAINHAND; k != BOT_SLOT_RANGED; ++k) + RemoveItemClassEnchantment(k); +} + +void bot_ai::ApplyItemEquipSpells(Item* item, bool apply) +{ + if (!item) + return; + + ItemTemplate const* proto = item->GetTemplate(); + if (!proto) + return; + + for (uint8 i = 0; i != MAX_ITEM_PROTO_SPELLS; ++i) + { + _Spell const& spellData = proto->Spells[i]; + + if (!spellData.SpellId) + continue; + + // wrong triggering type + if (apply && spellData.SpellTrigger != ITEM_SPELLTRIGGER_ON_EQUIP) + continue; + + // check if it is valid spell + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellData.SpellId); + if (!spellInfo) + continue; + + //ApplyEquipSpell(spellproto, item, apply); + + //if (apply) + // me->AddAura(spellInfo->Id, me); + //else + // me->RemoveAura(spellInfo->Id); + + CastSpellExtraArgs args(item); + if (apply) + me->CastSpell(me, spellInfo->Id, args); + else + me->RemoveAurasDueToItemSpell(spellInfo->Id, item->GetGUID()); // un-apply all spells, not only at-equipped + } +} + +void bot_ai::ApplyItemEquipEnchantmentSpells(Item* item) +{ + for (uint8 e_slot = 0; e_slot != MAX_ENCHANTMENT_SLOT; ++e_slot) + { + uint32 enchant_id = item->GetEnchantmentId(EnchantmentSlot(e_slot)); + if (!enchant_id) + continue; + SpellItemEnchantmentEntry const* pEnchant = sSpellItemEnchantmentStore.LookupEntry(enchant_id); + if (!pEnchant) + continue; + if (pEnchant->MinLevel > me->GetLevel()) + continue; + + uint32 enchant_display_type; + //uint32 enchant_amount; + uint32 enchant_spell_id; + + for (uint8 s = 0; s != MAX_ITEM_ENCHANTMENT_EFFECTS; ++s) + { + enchant_display_type = pEnchant->Effect[s]; + //enchant_amount = pEnchant->EffectPointsMin[s]; + enchant_spell_id = pEnchant->EffectArg[s]; + + switch (enchant_display_type) + { + case ITEM_ENCHANTMENT_TYPE_EQUIP_SPELL: + { + if (!enchant_spell_id) + break; + int32 basepoints = 0; + // Random Property Exist - try found basepoints for spell (basepoints depends from item suffix factor) + if (item->GetItemRandomPropertyId()) + { + ItemRandomSuffixEntry const* item_rand = sItemRandomSuffixStore.LookupEntry(abs(item->GetItemRandomPropertyId())); + if (item_rand) + { + // Search enchant_amount + for (uint8 k = 0; k != MAX_ITEM_ENCHANTMENT_EFFECTS; ++k) + { + if (item_rand->Enchantment[k] == enchant_id) + { + basepoints = int32((item_rand->AllocationPct[k] * item->GetItemSuffixFactor()) / 10000); + break; + } + } + } + } + // Cast custom spell vs all equal basepoints got from enchant_amount + CastSpellExtraArgs args(item); + if (basepoints) + for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) + args.AddSpellMod(SpellValueMod(SPELLVALUE_BASE_POINT0 + i), basepoints); + me->CastSpell(me, enchant_spell_id, args); + break; + } + default: + break; + } + } + } +} + +void bot_ai::ApplyItemSetBonuses(Item* item, bool apply) +{ + if (item) //(un)equip, NOT from loops + { + ItemTemplate const* proto = item->GetTemplate(); + if (!proto) + return; + + uint32 setId = proto->ItemSet; + if (!setId) + return; + + ItemSetEntry const* itemSet = sItemSetStore.LookupEntry(setId); + if (!itemSet) + return; + + uint8 setItemCount = 0; + for (uint8 i = BOT_SLOT_MAINHAND; i != BOT_INVENTORY_SIZE; ++i) + if (_equips[i] && _equips[i]->GetTemplate()->ItemSet == setId) + ++setItemCount; //same at equip and unequip + + for (uint8 i = 0; i != MAX_ITEM_SET_SPELLS; ++i) + { + if (!itemSet->SetSpellID[i]) + continue; + if (itemSet->SetThreshold[i] != setItemCount) + continue; + + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(itemSet->SetSpellID[i]); + if (!spellInfo) + continue; + + //TC_LOG_ERROR("entities.player", "ApplyItemSetBonusesB: {}'s {}, {} ({}), {} ({}), icount {}", + // me->GetName(), apply ? "apply" : "remove", itemSet->name[0], setId, spellInfo->SpellName[0], spellInfo->Id, uint32(setItemCount)); + + if (apply) + { + CastSpellExtraArgs args(item); + me->CastSpell(me, spellInfo->Id, args); + } + else + me->RemoveAurasDueToSpell(spellInfo->Id); // un-apply spell (item set case) + } + return; + } + + //no item means all (init, reset, reset (lvl change)) + std::list itemSets; + for (uint8 i = BOT_SLOT_MAINHAND; i != BOT_INVENTORY_SIZE; ++i) + { + item = _equips[i]; + if (!item) + continue; + + ItemTemplate const* proto = item->GetTemplate(); + if (!proto) + continue; + + uint32 setId = proto->ItemSet; + if (!setId) + continue; + + ItemSetEntry const* itemSet = sItemSetStore.LookupEntry(setId); + if (!itemSet) + continue; + + itemSets.push_back(setId); + } + + itemSets.sort(); + itemSets.unique(); + for (std::list::const_iterator itr = itemSets.begin(); itr != itemSets.end(); ++itr) + { + ItemSetEntry const* itemSet = sItemSetStore.LookupEntry(*itr); + uint8 setItemCount = 0; + for (uint8 k = BOT_SLOT_MAINHAND; k != BOT_INVENTORY_SIZE; ++k) + if (_equips[k] && _equips[k]->GetTemplate()->ItemSet == *itr) + ++setItemCount; + + for (uint8 j = 0; j != MAX_ITEM_SET_SPELLS; ++j) + { + if (!itemSet->SetSpellID[j]) + continue; + if (itemSet->SetThreshold[j] > setItemCount) + continue; + + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(itemSet->SetSpellID[j]); + if (!spellInfo) + continue; + + //TC_LOG_ERROR("entities.player", "ApplyItemSetBonusesB (all): {}'s {}, {} ({}), {} ({}), c {}, req {}", + // me->GetName(), apply ? "apply" : "remove", itemSet->name[0], *itr, spellInfo->SpellName[0], spellInfo->Id, uint32(setItemCount), itemSet->SetThreshold[j]); + + if (apply) + { + CastSpellExtraArgs args(item); + me->CastSpell(me, spellInfo->Id, args); + } + else + me->RemoveAurasDueToSpell(spellInfo->Id); // un-apply spell (item set case) + } + } +} + +void bot_ai::ApplyItemsSpells() +{ + for (uint8 slot = BOT_SLOT_MAINHAND; slot != BOT_INVENTORY_SIZE; ++slot) + { + if (Item* item = _equips[slot]) + { + ApplyItemEquipSpells(item, true); //item template spells + ApplyItemEquipEnchantmentSpells(item); //item enchants + } + } + + ApplyItemSetBonuses(nullptr, true); //item set bonuses +} +//stats bonuses from equipment +inline float bot_ai::_getBotStat(uint8 slot, BotStatMods stat) const +{ + return float(_stats[slot][stat]); +} + +float bot_ai::_getTotalBotStat(BotStatMods stat) const +{ + int32 value = 0; + for (uint8 slot = BOT_SLOT_MAINHAND; slot != BOT_INVENTORY_SIZE; ++slot) + value += _stats[slot][stat]; + + uint8 lvl = me->GetLevel(); + float fval = float(value); + + switch (stat) + { + case BOT_STAT_MOD_STRENGTH: + fval += me->GetTotalStatValue(STAT_STRENGTH); + switch (_botclass) + { + case BOT_CLASS_WARRIOR: + //Vitality, Strength of Arms + if (lvl >= 45 && GetSpec() == BOT_SPEC_WARRIOR_PROTECTION) + fval *= 1.06f; + if (lvl >= 40 && GetSpec() == BOT_SPEC_WARRIOR_ARMS) + fval *= 1.04f; + //Improved Berserker Stance part 1 (all stances) + if (lvl >= 45 && GetSpec() == BOT_SPEC_WARRIOR_FURY/* && GetBotStance() == WARRIOR_BERSERKER_STANCE*/) + fval *= 1.2f; + break; + case BOT_CLASS_PALADIN: + //Divine Strength + if (lvl >= 10) + fval *= 1.15f; + break; + case BOT_CLASS_DEATH_KNIGHT: + //Ravenous Dead part 1 + //Endless Winter part 1 + //Veteran of the Third War part 1 + //Abomination's might part 2 + if (lvl >= 56) + fval *= 1.03f; + if (lvl >= 58) + fval *= 1.04f; + if (lvl >= 59 && GetSpec() == BOT_SPEC_DK_BLOOD) + fval *= 1.06f; + if (lvl >= 60 && GetSpec() == BOT_SPEC_DK_BLOOD) + fval *= 1.02f; + //Frost Presence passive / Improved Frost Presence + if (lvl >= 61 && GetBotStance() == DEATH_KNIGHT_FROST_PRESENCE && GetSpec() == BOT_SPEC_DK_FROST) + fval *= 1.08f; + break; + case BOT_CLASS_DRUID: + //Survival of the Fittest, Improved Mark of the Wild + if (lvl >= 35 && GetSpec() == BOT_SPEC_DRUID_FERAL) + fval *= 1.08f; + else if (lvl >= 10) + fval *= 1.02f; + break; + default: + break; + } + break; + case BOT_STAT_MOD_AGILITY: + fval += me->GetTotalStatValue(STAT_AGILITY); + switch (_botclass) + { + case BOT_CLASS_HUNTER: + //Combat Experience, Lightning Reflexes + if (lvl >= 35 && GetSpec() == BOT_SPEC_HUNTER_MARKSMANSHIP) + fval *= 1.04f; + if (lvl >= 35 && GetSpec() == BOT_SPEC_HUNTER_SURVIVAL) + fval *= 1.15f; + //Hunting Party + if (lvl >= 35 && GetSpec() == BOT_SPEC_HUNTER_SURVIVAL) + fval *= 1.03f; + break; + case BOT_CLASS_ROGUE: + //Sinister Calling + if (lvl >= 45 && GetSpec() == BOT_SPEC_ROGUE_SUBTLETY) + fval *= 1.15f; + break; + case BOT_CLASS_DRUID: + //Survival of the Fittest, Improved Mark of the Wild + if (lvl >= 35 && GetSpec() == BOT_SPEC_DRUID_FERAL) + fval *= 1.08f; + else if (lvl >= 10) + fval *= 1.02f; + break; + default: + break; + } + break; + case BOT_STAT_MOD_STAMINA: + fval += me->GetTotalStatValue(STAT_STAMINA); + switch (_botclass) + { + case BOT_CLASS_WARRIOR: + //Vitality, Strength of Arms + if (lvl >= 45 && GetSpec() == BOT_SPEC_WARRIOR_PROTECTION) + fval *= 1.09f; + if (lvl >= 40 && GetSpec() == BOT_SPEC_WARRIOR_ARMS) + fval *= 1.04f; + break; + case BOT_CLASS_PALADIN: + //Combat Expertise, Sacred Duty + if (lvl >= 45 && GetSpec() == BOT_SPEC_PALADIN_PROTECTION) + fval *= 1.06f; + if (lvl >= 35 && GetSpec() == BOT_SPEC_PALADIN_PROTECTION) + fval *= 1.04f; + break; + case BOT_CLASS_HUNTER: + //Survivalist + if (lvl >= 20) + fval *= 1.1f; + break; + case BOT_CLASS_ROGUE: + //Lightning Reflexes part 2 + if (lvl >= 25 && GetSpec() == BOT_SPEC_ROGUE_COMBAT) + fval *= 1.04f; + break; + case BOT_CLASS_PRIEST: + //Improved Power Word: Fortitude + if (lvl >= 15) + fval *= 1.04f; + break; + case BOT_CLASS_DEATH_KNIGHT: + //Veteran of the Third War part 2 + if (lvl >= 59 && GetSpec() == BOT_SPEC_DK_BLOOD) + fval *= 1.03f; + break; + case BOT_CLASS_WARLOCK: + //Demonic Embrace: 10% stam bonus + if (lvl >= 10) + fval *= 1.1f; + break; + case BOT_CLASS_DRUID: + if (GetBotStance() == DRUID_BEAR_FORM) + { + //Bear form: stamina bonus base 25% + //Heart of the Wild: 10% stam bonus for bear + fval *= 1.25f; + if (lvl >= 35 && GetSpec() == BOT_SPEC_DRUID_FERAL) + fval *= 1.1f; + } + //Survival of the Fittest, Improved Mark of the Wild + if (lvl >= 35 && GetSpec() == BOT_SPEC_DRUID_FERAL) + fval *= 1.06f; + if (lvl >= 10) + fval *= 1.02f; + break; + default: + break; + } + break; + case BOT_STAT_MOD_INTELLECT: + fval += me->GetTotalStatValue(STAT_INTELLECT); + switch (_botclass) + { + case BOT_CLASS_PALADIN: + //Divine Intellect + if (lvl >= 15) + fval *= 1.1f; + break; + case BOT_CLASS_HUNTER: + //Combat Experience + if (lvl >= 35 && GetSpec() == BOT_SPEC_HUNTER_MARKSMANSHIP) + fval *= 1.04f; + break; + case BOT_CLASS_MAGE: + //Arcane Mind + if (lvl >= 30 && GetSpec() == BOT_SPEC_MAGE_ARCANE) + fval *= 1.15f; + break; + case BOT_CLASS_PRIEST: + //Mental Strength + if (lvl >= 30 && GetSpec() == BOT_SPEC_PRIEST_DISCIPLINE) + fval *= 1.15f; + break; + case BOT_CLASS_SHAMAN: + //Ancestral Knowledge + if (lvl >= 10) + fval *= 1.1f; + break; + case BOT_CLASS_DRUID: + //Survival of the Fittest, Improved Mark of the Wild + if (lvl >= 35 && GetSpec() == BOT_SPEC_DRUID_FERAL) + fval *= 1.08f; + else if (lvl >= 10) + fval *= 1.02f; + //Furor (Moonkin Form) + if (GetBotStance() == DRUID_MOONKIN_FORM) + fval *= 1.1f; + //Heart of the Wild: ferals only (tanks included) + if (lvl >= 35 && GetSpec() == BOT_SPEC_DRUID_FERAL) + fval *= 1.2f; + break; + default: + break; + } + break; + case BOT_STAT_MOD_SPIRIT: + fval += me->GetTotalStatValue(STAT_SPIRIT); + switch (_botclass) + { + case BOT_CLASS_PRIEST: + //Spirit of Redemption part 1 + if (lvl >= 30 && GetSpec() == BOT_SPEC_PRIEST_HOLY) + fval *= 1.05f; + //Enlightenment part 1 + if (lvl >= 35 && GetSpec() == BOT_SPEC_PRIEST_DISCIPLINE) + fval *= 1.06f; + break; + case BOT_CLASS_MAGE: + //Student of the Mind + if (lvl >= 20) + fval *= 1.1f; + break; + case BOT_CLASS_DRUID: + //Survival of the Fittest, Improved Mark of the Wild + if (lvl >= 35 && GetSpec() == BOT_SPEC_DRUID_FERAL) + fval *= 1.08f; + else if (lvl >= 10) + fval *= 1.02f; + //Living Spirit + if (lvl >= 40 && GetSpec() == BOT_SPEC_DRUID_RESTORATION) + fval *= 1.15f; + break; + default: + break; + } + break; + default: + break; + } + + return fval; +} + +inline float bot_ai::_getRatingMultiplier(CombatRating cr) const +{ + GtCombatRatingsEntry const* Rating = sGtCombatRatingsStore.LookupEntry(cr*GT_MAX_LEVEL + (me->GetLevel()-1)); + GtOCTClassCombatRatingScalarEntry const* classRating = sGtOCTClassCombatRatingScalarStore.LookupEntry((GetPlayerClass()-1)*GT_MAX_RATING + cr + 1); + if (!Rating || !classRating) + return 1.0f; + + return classRating->Data / Rating->Data; +} + +float bot_ai::_getStatScore(uint8 stat) const +{ + static const float fone = 1.0f; + static const float fzero = 0.0f; + + float tankMod = IsTank() ? fone : fzero; + float healMod = HasRole(BOT_ROLE_HEAL) ? fone : fzero; + float castMod = IsCastingClass(_botclass) ? fone : fzero; + float spiritMod = (_botclass == BOT_CLASS_PRIEST || _botclass == BOT_CLASS_MAGE || _botclass == BOT_CLASS_WARLOCK || (_botclass == BOT_CLASS_DRUID && _spec != BOT_SPEC_DRUID_FERAL)) ? fone : fzero; + float dpsMod = HasRole(BOT_ROLE_DPS) ? fone : fzero; + float meleeMod = !HasRole(BOT_ROLE_RANGED) ? fone : fzero; + float manaMod = (_botclass == BOT_CLASS_DRUID || me->GetPowerType() == POWER_MANA) ? fone : fzero; + + switch (stat) + { + case BOT_STAT_MOD_MANA: + return 0.1f * manaMod; + case BOT_STAT_MOD_HEALTH: + return 0.1f; + case BOT_STAT_MOD_AGILITY: + return _botclass == BOT_CLASS_ROGUE ? 1.2f * dpsMod * meleeMod : (_botclass == BOT_CLASS_HUNTER ? 1.0f : 0.5f) * dpsMod; + case BOT_STAT_MOD_STRENGTH: + return (IsMeleeClass(_botclass) ? 1.0f : 0.5f) * dpsMod * meleeMod; + case BOT_STAT_MOD_INTELLECT: + return 1.0f * castMod; + case BOT_STAT_MOD_SPIRIT: + return 1.0f * spiritMod; + case BOT_STAT_MOD_STAMINA: + return IsTank() ? 2.0f : 1.0f; + case BOT_STAT_MOD_DEFENSE_SKILL_RATING: + return 2.0f * tankMod; + case BOT_STAT_MOD_DODGE_RATING: + case BOT_STAT_MOD_PARRY_RATING: + return 2.0f * tankMod; + case BOT_STAT_MOD_BLOCK_RATING: + return CanBlock() ? 2.0f : 0.0f * tankMod; + case BOT_STAT_MOD_BLOCK_VALUE: + return CanBlock() ? 0.67f : 0.0f * tankMod; + case BOT_STAT_MOD_HIT_TAKEN_RATING: + case BOT_STAT_MOD_CRIT_TAKEN_RATING: + return 1.0f * tankMod; + case BOT_STAT_MOD_HIT_TAKEN_MELEE_RATING: + case BOT_STAT_MOD_HIT_TAKEN_RANGED_RATING: + case BOT_STAT_MOD_HIT_TAKEN_SPELL_RATING: + case BOT_STAT_MOD_CRIT_TAKEN_MELEE_RATING: + case BOT_STAT_MOD_CRIT_TAKEN_RANGED_RATING: + case BOT_STAT_MOD_CRIT_TAKEN_SPELL_RATING: + return 0.4f * tankMod; + case BOT_STAT_MOD_ARMOR: + return 0.05f * tankMod; + case BOT_STAT_MOD_HIT_MELEE_RATING: + case BOT_STAT_MOD_HIT_RANGED_RATING: + case BOT_STAT_MOD_HIT_SPELL_RATING: + return 1.0f * dpsMod; + case BOT_STAT_MOD_CRIT_MELEE_RATING: + case BOT_STAT_MOD_CRIT_RANGED_RATING: + case BOT_STAT_MOD_CRIT_SPELL_RATING: + case BOT_STAT_MOD_HASTE_MELEE_RATING: + case BOT_STAT_MOD_HASTE_RANGED_RATING: + case BOT_STAT_MOD_HASTE_SPELL_RATING: + case BOT_STAT_MOD_HIT_RATING: + case BOT_STAT_MOD_CRIT_RATING: + case BOT_STAT_MOD_HASTE_RATING: + return HasRole(BOT_ROLE_DPS|BOT_ROLE_HEAL) ? 1.0f : 0.0f; + case BOT_STAT_MOD_EXPERTISE_RATING: + return 2.0f * dpsMod * meleeMod; + case BOT_STAT_MOD_ATTACK_POWER: + return ((IsMeleeClass(_botclass) || _botclass == BOT_CLASS_HUNTER) ? 0.43f : 0.1f) * dpsMod; + case BOT_STAT_MOD_RANGED_ATTACK_POWER: + switch (_botclass) + { + case BOT_CLASS_HUNTER: case BOT_CLASS_DARK_RANGER: case BOT_CLASS_SEA_WITCH: return 0.43f * dpsMod; + case BOT_CLASS_PRIEST: case BOT_CLASS_MAGE: case BOT_CLASS_WARLOCK: return 0.15f * dpsMod; + default: return 0.0f; + } + case BOT_STAT_MOD_FERAL_ATTACK_POWER: + return GetSpec() == BOT_SPEC_DRUID_FERAL ? 0.43f : 0.0f; + case BOT_STAT_MOD_SPELL_HEALING_DONE: + return 1.25f * healMod; + case BOT_STAT_MOD_SPELL_DAMAGE_DONE: + return 1.25f * dpsMod * castMod; + case BOT_STAT_MOD_MANA_REGENERATION: + return _botclass == BOT_CLASS_SPHYNX ? 0.0f : 1.2f * manaMod; + case BOT_STAT_MOD_ARMOR_PENETRATION_RATING: + return 2.0f * dpsMod * meleeMod; + case BOT_STAT_MOD_SPELL_POWER: + return 1.25f * castMod; + case BOT_STAT_MOD_HEALTH_REGEN: + return 0.33f * tankMod; + case BOT_STAT_MOD_SPELL_PENETRATION: + return 1.2f * castMod * dpsMod; + case BOT_STAT_MOD_DAMAGE_MIN: + case BOT_STAT_MOD_DAMAGE_MAX: + return ((IsMeleeClass(_botclass) || _botclass == BOT_CLASS_HUNTER) ? 0.33f : 0.0f) * dpsMod; + case BOT_STAT_MOD_RESIST_HOLY: + case BOT_STAT_MOD_RESIST_FIRE: + case BOT_STAT_MOD_RESIST_NATURE: + case BOT_STAT_MOD_RESIST_FROST: + case BOT_STAT_MOD_RESIST_SHADOW: + case BOT_STAT_MOD_RESIST_ARCANE: + return IsTank() ? 1.25f : 0.25f; + default: + return 0.0f; + } +} + +float bot_ai::_getItemGearStatScore(ItemTemplate const* iproto, uint8 forslot, Item const* item) const +{ + ItemTemplate const* proto = item ? sObjectMgr->GetItemTemplate(item->GetEntry()) : iproto; + if (!proto) + return 0.0f; + + //TC_LOG_ERROR("scripts", "_getItemGearScore for {} - {}", proto->ItemId, proto->Name1); + + ItemStatBonus istats = {}; + //for (uint8 i = 0; i != MAX_BOT_ITEM_MOD; ++i) + // TC_LOG_ERROR("scripts", "_getItemGearScore at {} {}", uint32(i), istats[i]); + + ScalingStatDistributionEntry const* ssd = proto->ScalingStatDistribution ? sScalingStatDistributionStore.LookupEntry(proto->ScalingStatDistribution) : NULL; + + uint32 ssd_level = me->GetLevel(); + if (ssd && ssd_level > ssd->Maxlevel) + ssd_level = ssd->Maxlevel; + + ScalingStatValuesEntry const* ssv = proto->ScalingStatValue ? sScalingStatValuesStore.LookupEntry(ssd_level) : NULL; + + for (uint8 i = 0; i != MAX_ITEM_PROTO_STATS; ++i) + { + uint32 statType = 0; + int32 val = 0; + if (ssd && ssv) + { + if (ssd->StatID[i] < 0) + continue; + statType = ssd->StatID[i]; + val = (ssv->getssdMultiplier(proto->ScalingStatValue) * ssd->Bonus[i]) / 10000; + } + else + { + if (i >= proto->StatsCount) + continue; + statType = proto->ItemStat[i].ItemStatType; + val = proto->ItemStat[i].ItemStatValue; + } + + if (val == 0) + continue; + + istats[statType] += val; + } + + istats[BOT_STAT_MOD_RESIST_HOLY] += proto->HolyRes; + istats[BOT_STAT_MOD_RESIST_FIRE] += proto->FireRes; + istats[BOT_STAT_MOD_RESIST_NATURE] += proto->NatureRes; + istats[BOT_STAT_MOD_RESIST_FROST] += proto->FrostRes; + istats[BOT_STAT_MOD_RESIST_SHADOW] += proto->ShadowRes; + istats[BOT_STAT_MOD_RESIST_ARCANE] += proto->ArcaneRes; + + istats[BOT_STAT_MOD_ARMOR] += proto->Armor; + istats[BOT_STAT_MOD_BLOCK_VALUE] += proto->Block; + + EquipmentInfo const* einfo = BotDataMgr::GetBotEquipmentInfo(me->GetEntry()); + if (forslot > BOT_SLOT_RANGED || proto->ItemId != einfo->ItemEntry[forslot]) + { + if (ssv) + { + int32 extraDPS = ssv->getDPSMod(proto->ScalingStatValue); + if (extraDPS) + { + float average = extraDPS * proto->Delay / 1000.0f; + float mod = ssv->isTwoHand(proto->ScalingStatValue) ? 0.2f : 0.3f; + + istats[BOT_STAT_MOD_DAMAGE_MIN] += (1.0f - mod) * average; + istats[BOT_STAT_MOD_DAMAGE_MAX] += (1.0f + mod) * average; + } + } + else + { + istats[BOT_STAT_MOD_DAMAGE_MIN] += proto->Damage[0].DamageMin + proto->Damage[1].DamageMin; + istats[BOT_STAT_MOD_DAMAGE_MAX] += proto->Damage[0].DamageMax + proto->Damage[1].DamageMax; + } + + if (_botclass == BOT_CLASS_DRUID) + { + int32 dpsMod = 0; + int32 feral_bonus = 0; + + if (ssv) + { + dpsMod = ssv->getDPSMod(proto->ScalingStatValue); + feral_bonus += ssv->getFeralBonus(proto->ScalingStatValue); + } + + feral_bonus += proto->getFeralBonus(dpsMod); + if (feral_bonus) + istats[BOT_STAT_MOD_FERAL_ATTACK_POWER] += feral_bonus; + } + } + + for (uint8 i = 0; i != MAX_ENCHANTMENT_SLOT && item != nullptr; ++i) + { + EnchantmentSlot eslot = EnchantmentSlot(i); + uint32 enchant_id = item->GetEnchantmentId(eslot); + if (!enchant_id) + continue; + + SpellItemEnchantmentEntry const* pEnchant = sSpellItemEnchantmentStore.LookupEntry(enchant_id); + if (!pEnchant) + continue; + + uint32 enchant_display_type; + uint32 enchant_amount; + uint32 enchant_spell_id; + + for (uint8 s = 0; s != MAX_ITEM_ENCHANTMENT_EFFECTS; ++s) + { + enchant_display_type = pEnchant->Effect[s]; + enchant_amount = pEnchant->EffectPointsMin[s]; + enchant_spell_id = pEnchant->EffectArg[s]; + + switch (enchant_display_type) + { + case ITEM_ENCHANTMENT_TYPE_DAMAGE: + istats[BOT_STAT_MOD_DAMAGE_MIN] += enchant_amount; + istats[BOT_STAT_MOD_DAMAGE_MAX] += enchant_amount; + break; + case ITEM_ENCHANTMENT_TYPE_RESISTANCE: + if (!enchant_amount) + { + ItemRandomSuffixEntry const* item_rand = sItemRandomSuffixStore.LookupEntry(abs(item->GetItemRandomPropertyId())); + if (item_rand) + { + for (uint8 k = 0; k < MAX_ITEM_ENCHANTMENT_EFFECTS; ++k) + { + if (item_rand->Enchantment[k] == enchant_id) + { + enchant_amount = uint32((item_rand->AllocationPct[k] * item->GetItemSuffixFactor()) / 10000); + break; + } + } + } + } + istats[BOT_STAT_MOD_RESISTANCE_START + enchant_spell_id] += enchant_amount; + break; + case ITEM_ENCHANTMENT_TYPE_STAT: + { + if (!enchant_amount) + { + ItemRandomSuffixEntry const* item_rand_suffix = sItemRandomSuffixStore.LookupEntry(abs(item->GetItemRandomPropertyId())); + if (item_rand_suffix) + { + for (uint8 k = 0; k != MAX_ITEM_ENCHANTMENT_EFFECTS; ++k) + { + if (item_rand_suffix->Enchantment[k] == enchant_id) + { + enchant_amount = uint32((item_rand_suffix->AllocationPct[k] * item->GetItemSuffixFactor()) / 10000); + break; + } + } + } + } + + switch (enchant_spell_id) + { + case ITEM_MOD_MANA: + case ITEM_MOD_HEALTH: + case ITEM_MOD_AGILITY: + case ITEM_MOD_STRENGTH: + case ITEM_MOD_INTELLECT: + case ITEM_MOD_SPIRIT: + case ITEM_MOD_STAMINA: + case ITEM_MOD_DEFENSE_SKILL_RATING: + case ITEM_MOD_DODGE_RATING: + case ITEM_MOD_PARRY_RATING: + case ITEM_MOD_BLOCK_RATING: + case ITEM_MOD_HIT_MELEE_RATING: + case ITEM_MOD_HIT_RANGED_RATING: + case ITEM_MOD_HIT_SPELL_RATING: + case ITEM_MOD_CRIT_MELEE_RATING: + case ITEM_MOD_CRIT_RANGED_RATING: + case ITEM_MOD_CRIT_SPELL_RATING: + case ITEM_MOD_HASTE_MELEE_RATING: + case ITEM_MOD_HASTE_RANGED_RATING: + case ITEM_MOD_HASTE_SPELL_RATING: + case ITEM_MOD_HIT_RATING: + case ITEM_MOD_CRIT_RATING: + case ITEM_MOD_HASTE_RATING: + case ITEM_MOD_RESILIENCE_RATING: + case ITEM_MOD_EXPERTISE_RATING: + case ITEM_MOD_ATTACK_POWER: + case ITEM_MOD_RANGED_ATTACK_POWER: + case ITEM_MOD_MANA_REGENERATION: + case ITEM_MOD_ARMOR_PENETRATION_RATING: + case ITEM_MOD_SPELL_POWER: + case ITEM_MOD_HEALTH_REGEN: + case ITEM_MOD_SPELL_PENETRATION: + case ITEM_MOD_BLOCK_VALUE: + istats[enchant_spell_id] += enchant_amount; + break; + default: + break; + } + break; + } + } + } + } + + //for (uint8 i = 0; i != MAX_BOT_ITEM_MOD; ++i) + // TC_LOG_ERROR("scripts", "_getItemGearScore total {} {}", uint32(i), istats[i]); + + //stats are fetched, not calculate + float itemScore = 0.0f; + + for (uint8 i = 0; i != MAX_BOT_ITEM_MOD; ++i) + itemScore += istats[i] * _getStatScore(i); + + float itemGearScore = CalculateItemGearScore(me->GetEntry(), me->GetLevel(), GetBotClass(), GetSpec(), forslot, iproto); + itemScore += itemGearScore; + + //TC_LOG_ERROR("scripts", "_getItemGearScore total score {}", itemScore); + return itemScore; +} + +void bot_ai::_saveStats() +{ + NpcBotStats stats; + stats.entry = me->GetEntry(); + stats.maxhealth = me->GetMaxHealth(); + stats.maxpower = me->GetMaxPower(_botclass == BOT_CLASS_DRUID ? POWER_MANA : me->GetPowerType()); + stats.strength = GetTotalBotStat(BOT_STAT_MOD_STRENGTH); + stats.agility = GetTotalBotStat(BOT_STAT_MOD_AGILITY); + stats.stamina = GetTotalBotStat(BOT_STAT_MOD_STAMINA); + stats.intellect = GetTotalBotStat(BOT_STAT_MOD_INTELLECT); + stats.spirit = GetTotalBotStat(BOT_STAT_MOD_SPIRIT); + stats.armor = me->GetArmor(); + stats.defense = me->GetDefenseSkillValue(); + stats.resHoly = me->GetResistance(SPELL_SCHOOL_HOLY) + resistbonus[SPELL_SCHOOL_HOLY-1]; + stats.resFire = me->GetResistance(SPELL_SCHOOL_FIRE) + resistbonus[SPELL_SCHOOL_FIRE-1]; + stats.resNature = me->GetResistance(SPELL_SCHOOL_NATURE) + resistbonus[SPELL_SCHOOL_NATURE-1]; + stats.resFrost = me->GetResistance(SPELL_SCHOOL_FROST) + resistbonus[SPELL_SCHOOL_FROST-1]; + stats.resShadow = me->GetResistance(SPELL_SCHOOL_SHADOW) + resistbonus[SPELL_SCHOOL_SHADOW-1]; + stats.resArcane = me->GetResistance(SPELL_SCHOOL_ARCANE) + resistbonus[SPELL_SCHOOL_ARCANE-1]; + stats.blockPct = me->GetUnitBlockChance(BASE_ATTACK, me); + stats.dodgePct = me->GetUnitDodgeChance(BASE_ATTACK, me); + stats.parryPct = me->GetUnitParryChance(BASE_ATTACK, me); + stats.critPct = crit + me->GetTotalAuraModifier(SPELL_AURA_MOD_WEAPON_CRIT_PERCENT) + me->GetTotalAuraModifier(SPELL_AURA_MOD_CRIT_PCT); + stats.attackPower = me->GetTotalAttackPowerValue(BASE_ATTACK); + stats.spellPower = me->SpellBaseDamageBonusDone(SPELL_SCHOOL_MASK_MAGIC); + stats.spellPen = spellpen; + stats.hastePct = std::max(haste, 0.f); + stats.hitBonusPct = std::max(hit, 0.f); + stats.expertise = expertise; + stats.armorPenPct = me->GetCreatureArmorPenetrationCoef(); + + BotDataMgr::SaveNpcBotStats(&stats); +} + +//!Copied from Player::CastItemUseSpell +void bot_ai::_castBotItemUseSpell(Item const* item, SpellCastTargets const& targets/*, uint8 cast_count, uint32 glyphIndex*/) +{ + ItemTemplate const* proto = item->GetTemplate(); + + // use triggered flag only for items with many spell casts and for not first cast + uint8 count = 0; + + // item spells casted at use + SpellInfo const* spellInfo; + for (uint8 i = 0; i != MAX_ITEM_PROTO_SPELLS; ++i) + { + _Spell const& spellData = proto->Spells[i]; + if (!spellData.SpellId || spellData.SpellTrigger != ITEM_SPELLTRIGGER_ON_USE) + continue; + + spellInfo = sSpellMgr->GetSpellInfo(spellData.SpellId); + if (!spellInfo) + continue; + + Spell* spell = new Spell(me, spellInfo, (count > 0) ? TRIGGERED_FULL_MASK : TRIGGERED_NONE); + //spell->m_CastItem = item; // DO NOT TAKE ITEM + //spell->m_cast_count = cast_count; // set count of casts + //spell->m_glyphIndex = glyphIndex; // glyph index + spell->prepare(targets); + ++count; + } + + // Item enchantments spells casted at use + for (uint8 e_slot = 0; e_slot != MAX_ENCHANTMENT_SLOT; ++e_slot) + { + uint32 enchant_id = item->GetEnchantmentId(EnchantmentSlot(e_slot)); + SpellItemEnchantmentEntry const* pEnchant = sSpellItemEnchantmentStore.LookupEntry(enchant_id); + if (!pEnchant) + continue; + + for (uint8 s = 0; s != MAX_ITEM_ENCHANTMENT_EFFECTS; ++s) + { + if (pEnchant->Effect[s] != ITEM_ENCHANTMENT_TYPE_USE_SPELL) + continue; + + spellInfo = sSpellMgr->GetSpellInfo(pEnchant->EffectArg[s]); + if (!spellInfo) + continue; + + Spell* spell = new Spell(me, spellInfo, (count > 0) ? TRIGGERED_FULL_MASK : TRIGGERED_NONE); + //spell->m_CastItem = item; // DO NOT TAKE ITEM + //spell->m_cast_count = cast_count; // set count of casts + //spell->m_glyphIndex = glyphIndex; // glyph index + spell->prepare(targets); + ++count; + } + } +} + +Item* bot_ai::GetEquipsByGuid(ObjectGuid itemGuid) const +{ + for (uint8 i = BOT_SLOT_MAINHAND; i != BOT_INVENTORY_SIZE; ++i) + { + if (Item* item = _equips[i]) + if (item->GetGUID() == itemGuid) + return item; + } + + return nullptr; +} + +uint32 bot_ai::GetEquipDisplayId(uint8 slot) const +{ + int32 displayId = -1; + if (_equips[slot]) + { + NpcBotTransmogData const* transmogData = BotDataMgr::SelectNpcBotTransmogs(me->GetEntry()); + if (transmogData && BotMgr::IsTransmogEnabled() && + (_equips[slot]->GetTemplate()->ItemId == transmogData->transmogs[slot].first || BotMgr::TransmogUseEquipmentSlots())) + { + int32 item_id = transmogData->transmogs[slot].second; + if (item_id > 0) + { + if (ItemTemplate const* proto = sObjectMgr->GetItemTemplate(uint32(item_id))) + { + displayId = proto->DisplayInfoID; + } + else + { + TC_LOG_ERROR("scripts", "bot_ai::GetEquipDisplayId(): invalid item Id {} for bot {} {} slot {}", + item_id, me->GetEntry(), me->GetName(), uint32(slot)); + } + } + else + displayId = item_id; + } + if (displayId == -1) + displayId = int32(_equips[slot]->GetTemplate()->DisplayInfoID); + } + + return uint32(std::max(displayId, 0)); +} + +bool bot_ai::UnEquipAll(ObjectGuid receiver) +{ + bool suc = true; + for (uint8 i = BOT_SLOT_MAINHAND; i != BOT_INVENTORY_SIZE; ++i) + { + if (!(i <= BOT_SLOT_RANGED ? _resetEquipment(i, receiver) : _unequip(i, receiver))) + { + suc = false; + break; + } + } + + return suc; +} + +bool bot_ai::HasRealEquipment() const +{ + EquipmentInfo const* einfo = BotDataMgr::GetBotEquipmentInfo(me->GetEntry()); + ASSERT(einfo, "Trying to call HasRealEquipment for bot with no equip info!"); + + for (uint8 i = BOT_SLOT_MAINHAND; i != BOT_INVENTORY_SIZE; ++i) + { + if (Item const* item = GetEquips(i)) + { + if (i > BOT_SLOT_RANGED || einfo->ItemEntry[i] != item->GetEntry()) + return true; + } + } + + return false; +} + +float bot_ai::GetAverageItemLevel() const +{ + float sum = 0.f; + uint32 count = 0; + for (uint8 i = BOT_SLOT_MAINHAND; i != BOT_INVENTORY_SIZE; ++i) + { + if (_equips[i] && !(/*i == BOT_SLOT_TABARD || */i == BOT_SLOT_OFFHAND || i == BOT_SLOT_RANGED || i == BOT_SLOT_BODY)) + { + if (ItemTemplate const* itemProto = _equips[i]->GetTemplate()) + { + ++count; + sum += itemProto->GetItemLevelIncludingQuality(); + } + } + } + + return !count ? 0.f : (sum / float(count)); +} +std::pair bot_ai::GetBotGearScores() const +{ + return CalculateBotGearScore(me->GetEntry(), me->GetLevel(), GetBotClass(), GetSpec(), _equips); +} +///////// +//ROLES// +///////// +GossipOptionIcon bot_ai::GetRoleIcon(uint32 role) const +{ + return HasRole(role) ? BOT_ICON_ON : BOT_ICON_OFF; +} + +uint32 bot_ai::GetRoleString(uint32 role) +{ + switch (role) + { + case BOT_ROLE_TANK: return BOT_TEXT_TANK; + case BOT_ROLE_TANK_OFF: return BOT_TEXT_TANK_OFF; + case BOT_ROLE_DPS: return BOT_TEXT_DPS; + case BOT_ROLE_HEAL: return BOT_TEXT_HEAL; + case BOT_ROLE_RANGED: return BOT_TEXT_RANGED; + case BOT_ROLE_GATHERING_MINING: return BOT_TEXT_MINER; + case BOT_ROLE_GATHERING_HERBALISM: return BOT_TEXT_HERBALIST; + case BOT_ROLE_GATHERING_SKINNING: return BOT_TEXT_SKINNER; + case BOT_ROLE_GATHERING_ENGINEERING:return BOT_TEXT_ENGINEER; + case BOT_ROLE_AUTOLOOT: return BOT_TEXT_LOOTING; + case BOT_ROLE_AUTOLOOT_POOR: return BOT_TEXT_POOR; + case BOT_ROLE_AUTOLOOT_COMMON: return BOT_TEXT_COMMON; + case BOT_ROLE_AUTOLOOT_UNCOMMON: return BOT_TEXT_UNCOMMON; + case BOT_ROLE_AUTOLOOT_RARE: return BOT_TEXT_RARE; + case BOT_ROLE_AUTOLOOT_EPIC: return BOT_TEXT_EPIC; + case BOT_ROLE_AUTOLOOT_LEGENDARY: return BOT_TEXT_LEGENDARY; + default: return BOT_TEXT_UNKNOWN; + } +} + +void bot_ai::ToggleRole(uint32 role, bool force) +{ + if (!force && roleTimer > lastdiff) + return; + + roleTimer = 350; //delay next attempt (prevent abuse) + + if (HasRole(role)) + { + //linked roles + if (role & BOT_ROLE_TANK) + role |= BOT_ROLE_TANK_OFF; + + _roleMask &= ~role; + } + else + { + //linked roles + if (role & BOT_ROLE_TANK_OFF) + role |= BOT_ROLE_TANK; + + _roleMask |= role; + } + + BotDataMgr::UpdateNpcBotData(me->GetEntry(), NPCBOT_UPDATE_ROLES, &_roleMask); + + //Update passives + shouldUpdateStats = true; +} + +uint32 bot_ai::DefaultRolesForClass(uint8 m_class, uint8 spec) +{ + uint32 roleMask = BOT_ROLE_DPS; + + //if (bot_ai::IsHealingClass(m_class)) + // roleMask |= BOT_ROLE_HEAL; + + if (!bot_ai::IsMeleeClass(m_class)) + { + switch (spec) + { + case BOT_SPEC_SHAMAN_ENHANCEMENT: + case BOT_SPEC_DRUID_FERAL: + break; + default: + roleMask |= BOT_ROLE_RANGED; + break; + } + } + + return roleMask; +} + +bool bot_ai::IsTank(Unit const* unit) const +{ + if (!unit || unit == me) + return HasRole(BOT_ROLE_TANK); + + if (Creature const* bot = unit->ToCreature()) + return bot->GetBotAI() && bot->GetBotAI()->HasRole(BOT_ROLE_TANK); + + if (Player const* player = unit->ToPlayer()) + { + if (Group const* gr = player->GetGroup()) + { + if (gr->GetMemberFlags(unit->GetGUID()) & (MEMBER_FLAG_MAINTANK | MEMBER_FLAG_MAINASSIST)) + return true; + if (gr->isLFGGroup() && sLFGMgr->GetRoles(unit->GetGUID()) & lfg::PLAYER_ROLE_TANK) + return true; + } + } + + return false; +} + +bool bot_ai::IsOffTank(Unit const* unit) const +{ + if (!unit || unit == me) + return HasRole(BOT_ROLE_TANK_OFF); + + if (Creature const* bot = unit->ToCreature()) + return bot->GetBotAI() && bot->GetBotAI()->HasRole(BOT_ROLE_TANK_OFF); + + if (Player const* player = unit->ToPlayer()) + { + if (Group const* gr = player->GetGroup()) + { + if (gr->GetMemberFlags(unit->GetGUID()) & (MEMBER_FLAG_MAINTANK | MEMBER_FLAG_MAINASSIST)) + return true; + } + } + + return false; +} + +bool bot_ai::CCed(Unit const* target, bool root) +{ + return target ? target->HasUnitState(UNIT_STATE_CONFUSED | UNIT_STATE_STUNNED | UNIT_STATE_FLEEING | UNIT_STATE_DISTRACTED | UNIT_STATE_CONFUSED_MOVE | UNIT_STATE_FLEEING_MOVE) || (root && (target->HasUnitState(UNIT_STATE_ROOT) || target->IsFrozen() || target->IsRooted())) : true; +} +//AI initialization common +//Called at ai reset, level change (spawned = true) +void bot_ai::DefaultInit() +{ + //only once + if (spawned) + return; + + spawned = true; + + if (!firstspawn) + { + me->RemoveAllAurasExceptType(SPELL_AURA_CONTROL_VEHICLE); + RemoveItemClassEnchantments(); //clear rogue poisons / shaman ecnhants + ApplyItemsSpells(); //restore item equip spells + } + else + { + InitRace(); + ASSERT(!me->GetBotAI()); + ASSERT(!me->GetBotPetAI()); + me->SetBotAI(this); + BotLogger::Log(NPCBOT_LOG_SPAWN, me); + } + + me->SetPvP(master->IsPvP() || IsWanderer()); + if (sWorld->IsFFAPvPRealm()) + me->SetByteFlag(UNIT_FIELD_BYTES_2, 1, UNIT_BYTE2_FLAG_FFA_PVP); + else if (IAmFree()) + me->SetByteFlag(UNIT_FIELD_BYTES_2, 1, 0); + + InitSpec(); + InitRoles(); + + if (IsWanderer()) + { + _travel_node_cur = ASSERT_NOTNULL(BotDataMgr::GetClosestWanderNode(me)); + if (firstspawn && BotMgr::IsWanderingWorldBot(me)) + StartPotionTimer(); + } + + SetStats(true); // Class passives included + + if (!IsTempBot()) + ApplyRacials(); + + if (firstspawn) + { + if (!IsTempBot()) + { + InitFaction(); + InitOwner(); + InitEquips(); + } + + firstspawn = false; + } +} + +void bot_ai::ApplyRacials() +{ + uint8 myrace = me->GetRace(); + switch (myrace) + { + case RACE_HUMAN: + RefreshAura(20598); //Human Spirit + RefreshAura(20864); //Mace Specialization + RefreshAura(20597); //Sword Specialization + //RefreshAura(58985); //Perception pointless + if (firstspawn) + InitSpellMap(RACIAL_EVERY_MAN_FOR_HIMSELF, true, false); + break; + case RACE_ORC: + RefreshAura(20573); //Hardiness + RefreshAura(20574); //Axe Specialization + //Blood Fury + if (firstspawn) + InitSpellMap(RaceSpellForClass(myrace, _botclass), true, false); + break; + case RACE_DWARF: + RefreshAura(20595); //Gun Specialization + RefreshAura(59224); //Mace Specialization + RefreshAura(20596); //Frost Resistance + if (firstspawn) + InitSpellMap(RACIAL_STONEFORM, true, false); + break; + case RACE_NIGHTELF: + RefreshAura(20583); //Nature Resistance + RefreshAura(20582); //Quickness + InitSpellMap(RACIAL_SHADOWMELD, true, false); + break; + case RACE_UNDEAD_PLAYER: + RefreshAura(20579); //Shadow Resistance + if (firstspawn) + InitSpellMap(RACIAL_WILL_OF_THE_FORSAKEN, true, false); + //cannibalize is skipped + break; + case RACE_TAUREN: + RefreshAura(20550); //Endurance + RefreshAura(20551); //Nature Resistance + if (firstspawn) + InitSpellMap(RACIAL_WARSTOMP, true, false); + break; + case RACE_GNOME: + RefreshAura(20592); //Arcane Resistance + RefreshAura(20591); //Expansive Mind + if (firstspawn) + InitSpellMap(RACIAL_ESCAPE_ARTIST, true, false); + break; + case RACE_TROLL: + RefreshAura(20557); //Beast Slaying + RefreshAura(20558); //Thrown Specialization + RefreshAura(26290); //Bow Specialization + RefreshAura(58943); //Da Voodoo Shuffle + RefreshAura(20555); //Regeneration + if (firstspawn) + InitSpellMap(RACIAL_BERSERKING, true, false); + break; + case RACE_BLOODELF: + RefreshAura(822); //Magic Resistance + if (firstspawn) + InitSpellMap(RaceSpellForClass(myrace, _botclass), true, false); + break; + case RACE_DRAENEI: + RefreshAura(6562, uint8(!IAmFree())); //Heroic Presence (28878 is not present) + RefreshAura(20579); //Shadow Resistance (universal since creatures do not lose cast time on damage anyways) + if (firstspawn) + InitSpellMap(RaceSpellForClass(myrace, _botclass), true, false); + break; + default: + //TC_LOG_ERROR("entities.player", "bot_ai::ApplyRacePassives(): unknown race {} for bot {} ({})", uint32(me->GetRace()), me->GetName(), me->GetEntry()); + return; + } +} + +void bot_ai::InitFaction() +{ + NpcBotData const* npcBotData = BotDataMgr::SelectNpcBotData(me->GetEntry()); + ASSERT(npcBotData, "bot_ai::InitFaction(): data not found!"); + + uint32 faction = npcBotData->faction; + + //if (faction == 14) + // faction = 35; + + me->SetFaction(faction); + if (botPet) + botPet->SetFaction(faction); + const_cast(me->GetCreatureTemplate())->faction = faction; +} + +void bot_ai::InitRace() +{ + NpcBotExtras const* npcBotExtras = BotDataMgr::SelectNpcBotExtras(me->GetEntry()); + ASSERT(npcBotExtras, "bot_ai::InitRace: extra data not found!"); + + me->SetByteValue(UNIT_FIELD_BYTES_0, 0, npcBotExtras->race); //set race +} + +void bot_ai::InitOwner() +{ + NpcBotData const* npcBotData = BotDataMgr::SelectNpcBotData(me->GetEntry()); + ASSERT(npcBotData, "bot_ai::InitOwner(): data not found!"); + + _ownerGuid = npcBotData->owner; +} + +void bot_ai::InitRoles() +{ + if (IsTempBot()) + { + _roleMask = BOT_ROLE_DPS; + return; + } + else if (IAmFree()) + { + //default roles + _roleMask = DefaultRolesForClass(_botclass, GetSpec()); + return; + } + + NpcBotData const* npcBotData = BotDataMgr::SelectNpcBotData(me->GetEntry()); + ASSERT(npcBotData, "bot_ai::InitRoles(): data not found!"); + + _roleMask = npcBotData->roles; +} + +void bot_ai::InitSpec() +{ + uint8 spec; + if (IAmFree()) + spec = SelectSpecForClass(_botclass); + else + { + NpcBotData const* npcBotData = BotDataMgr::SelectNpcBotData(me->GetEntry()); + ASSERT(npcBotData, "bot_ai::InitSpec(): data not found!"); + + spec = npcBotData->spec; + } + + //TC_LOG_ERROR("entities.unit", "bot_ai::InitSpec(): bot {} class {} spec: {}", me->GetEntry(), uint32(_botclass), uint32(spec)); + + if (spec < BOT_SPEC_BEGIN || spec > BOT_SPEC_END) + { + TC_LOG_ERROR("entities.unit", "bot_ai::InitSpec(): spec ({}) is out of range for bot {}! Falling to default (1)...", + uint32(spec), me->GetEntry()); + + spec = BOT_SPEC_DEFAULT; + } + + SetSpec(spec, false); +} + +void bot_ai::SetSpec(uint8 spec, bool activate) +{ + ASSERT(spec >= BOT_SPEC_BEGIN && spec <= BOT_SPEC_END); + + _spec = spec; + + if (activate) + { + BotDataMgr::UpdateNpcBotData(me->GetEntry(), NPCBOT_UPDATE_SPEC, &spec); + + UnsummonAll(false); + removeShapeshiftForm(); + //from DefaultInit + me->RemoveAllAurasExceptType(SPELL_AURA_CONTROL_VEHICLE); + //RemoveItemClassEnchants(); + ApplyItemsSpells(); + ApplyRacials(); + //from SetStats + //InitPowers(); + InitSpells(); + ApplyClassPassives(); + InitHeals(); + + me->SetPower(POWER_MANA, 0); + me->SetPower(POWER_RAGE, 0); + me->SetPower(POWER_ENERGY, 0); + } +} + +uint8 bot_ai::GetSpec() const +{ + return me->GetLevel() < 10 ? uint8(BOT_SPEC_DEFAULT) : _spec; +} + +uint8 bot_ai::SelectSpecForClass(uint8 m_class) +{ + std::vector specs; + specs.reserve(3); + switch (m_class) + { + case BOT_CLASS_WARRIOR: //any + specs.push_back(BOT_SPEC_WARRIOR_ARMS); + specs.push_back(BOT_SPEC_WARRIOR_FURY); + specs.push_back(BOT_SPEC_WARRIOR_PROTECTION); + break; + case BOT_CLASS_PALADIN: //retri + specs.push_back(BOT_SPEC_PALADIN_RETRIBUTION); + break; + case BOT_CLASS_HUNTER: //any + specs.push_back(BOT_SPEC_HUNTER_BEASTMASTERY); + specs.push_back(BOT_SPEC_HUNTER_MARKSMANSHIP); + specs.push_back(BOT_SPEC_HUNTER_SURVIVAL); + break; + case BOT_CLASS_ROGUE: //any + specs.push_back(BOT_SPEC_ROGUE_ASSASINATION); + specs.push_back(BOT_SPEC_ROGUE_COMBAT); + specs.push_back(BOT_SPEC_ROGUE_SUBTLETY); + break; + case BOT_CLASS_PRIEST: //discipline, shadow + specs.push_back(BOT_SPEC_PRIEST_DISCIPLINE); + specs.push_back(BOT_SPEC_PRIEST_SHADOW); + break; + case BOT_CLASS_DEATH_KNIGHT: //any + specs.push_back(BOT_SPEC_DK_BLOOD); + specs.push_back(BOT_SPEC_DK_FROST); + specs.push_back(BOT_SPEC_DK_UNHOLY); + break; + case BOT_CLASS_SHAMAN: //elem, enh + specs.push_back(BOT_SPEC_SHAMAN_ELEMENTAL); + specs.push_back(BOT_SPEC_SHAMAN_ENHANCEMENT); + break; + case BOT_CLASS_MAGE: //fire, frost + specs.push_back(BOT_SPEC_MAGE_FIRE); + specs.push_back(BOT_SPEC_MAGE_FROST); + break; + case BOT_CLASS_WARLOCK: //affli, destr + specs.push_back(BOT_SPEC_WARLOCK_AFFLICTION); + specs.push_back(BOT_SPEC_WARLOCK_DESTRUCTION); + break; + case BOT_CLASS_DRUID: //balance, feral + specs.push_back(BOT_SPEC_DRUID_BALANCE); + specs.push_back(BOT_SPEC_DRUID_FERAL); + break; + default: + specs.push_back(BOT_SPEC_DEFAULT); + break; + } + + return specs.size() == 1 ? specs.front() : Trinity::Containers::SelectRandomContainerElement(specs); +} + +uint32 bot_ai::TextForSpec(uint8 spec) +{ + switch (spec) + { + case BOT_SPEC_WARRIOR_ARMS: return BOT_TEXT_SPEC_ARMS; + case BOT_SPEC_WARRIOR_FURY: return BOT_TEXT_SPEC_FURY; + case BOT_SPEC_WARRIOR_PROTECTION: return BOT_TEXT_SPEC_PROTECTION; + case BOT_SPEC_PALADIN_HOLY: return BOT_TEXT_SPEC_HOLY; + case BOT_SPEC_PALADIN_PROTECTION: return BOT_TEXT_SPEC_PROTECTION; + case BOT_SPEC_PALADIN_RETRIBUTION: return BOT_TEXT_SPEC_RETRIBUTION; + case BOT_SPEC_HUNTER_BEASTMASTERY: return BOT_TEXT_SPEC_BEASTMASTERY; + case BOT_SPEC_HUNTER_MARKSMANSHIP: return BOT_TEXT_SPEC_MARKSMANSHIP; + case BOT_SPEC_HUNTER_SURVIVAL: return BOT_TEXT_SPEC_SURVIVAL; + case BOT_SPEC_ROGUE_ASSASINATION: return BOT_TEXT_SPEC_ASSASINATION; + case BOT_SPEC_ROGUE_COMBAT: return BOT_TEXT_SPEC_COMBAT; + case BOT_SPEC_ROGUE_SUBTLETY: return BOT_TEXT_SPEC_SUBTLETY; + case BOT_SPEC_PRIEST_DISCIPLINE: return BOT_TEXT_SPEC_DISCIPLINE; + case BOT_SPEC_PRIEST_HOLY: return BOT_TEXT_SPEC_HOLY; + case BOT_SPEC_PRIEST_SHADOW: return BOT_TEXT_SPEC_SHADOW; + case BOT_SPEC_DK_BLOOD: return BOT_TEXT_SPEC_BLOOD; + case BOT_SPEC_DK_FROST: return BOT_TEXT_SPEC_FROST; + case BOT_SPEC_DK_UNHOLY: return BOT_TEXT_SPEC_UNHOLY; + case BOT_SPEC_SHAMAN_ELEMENTAL: return BOT_TEXT_SPEC_ELEMENTAL; + case BOT_SPEC_SHAMAN_ENHANCEMENT: return BOT_TEXT_SPEC_ENHANCEMENT; + case BOT_SPEC_SHAMAN_RESTORATION: return BOT_TEXT_SPEC_RESTORATION; + case BOT_SPEC_MAGE_ARCANE: return BOT_TEXT_SPEC_ARCANE; + case BOT_SPEC_MAGE_FIRE: return BOT_TEXT_SPEC_FIRE; + case BOT_SPEC_MAGE_FROST: return BOT_TEXT_SPEC_FROST; + case BOT_SPEC_WARLOCK_AFFLICTION: return BOT_TEXT_SPEC_AFFLICTION; + case BOT_SPEC_WARLOCK_DEMONOLOGY: return BOT_TEXT_SPEC_DEMONOLOGY; + case BOT_SPEC_WARLOCK_DESTRUCTION: return BOT_TEXT_SPEC_DESTRUCTION; + case BOT_SPEC_DRUID_BALANCE: return BOT_TEXT_SPEC_BALANCE; + case BOT_SPEC_DRUID_FERAL: return BOT_TEXT_SPEC_FERAL; + case BOT_SPEC_DRUID_RESTORATION: return BOT_TEXT_SPEC_RESTORATION; + case BOT_SPEC_DEFAULT: default: return BOT_TEXT_SPEC_UNKNOWN; + } +} + +bool bot_ai::IsValidSpecForClass(uint8 m_class, uint8 spec) +{ + switch (m_class) + { + case BOT_CLASS_WARRIOR: + switch (spec) + { + case BOT_SPEC_WARRIOR_ARMS: + case BOT_SPEC_WARRIOR_FURY: + case BOT_SPEC_WARRIOR_PROTECTION: + return true; + default: + break; + } + break; + case BOT_CLASS_PALADIN: + switch (spec) + { + case BOT_SPEC_PALADIN_HOLY: + case BOT_SPEC_PALADIN_PROTECTION: + case BOT_SPEC_PALADIN_RETRIBUTION: + return true; + default: + break; + } + break; + case BOT_CLASS_HUNTER: + switch (spec) + { + case BOT_SPEC_HUNTER_BEASTMASTERY: + case BOT_SPEC_HUNTER_MARKSMANSHIP: + case BOT_SPEC_HUNTER_SURVIVAL: + return true; + default: + break; + } + break; + case BOT_CLASS_ROGUE: + switch (spec) + { + case BOT_SPEC_ROGUE_ASSASINATION: + case BOT_SPEC_ROGUE_COMBAT: + case BOT_SPEC_ROGUE_SUBTLETY: + return true; + default: + break; + } + break; + case BOT_CLASS_PRIEST: + switch (spec) + { + case BOT_SPEC_PRIEST_DISCIPLINE: + case BOT_SPEC_PRIEST_HOLY: + case BOT_SPEC_PRIEST_SHADOW: + return true; + default: + break; + } + break; + case BOT_CLASS_DEATH_KNIGHT: + switch (spec) + { + case BOT_SPEC_DK_BLOOD: + case BOT_SPEC_DK_FROST: + case BOT_SPEC_DK_UNHOLY: + return true; + default: + break; + } + break; + case BOT_CLASS_SHAMAN: + switch (spec) + { + case BOT_SPEC_SHAMAN_ELEMENTAL: + case BOT_SPEC_SHAMAN_ENHANCEMENT: + case BOT_SPEC_SHAMAN_RESTORATION: + return true; + default: + break; + } + break; + case BOT_CLASS_MAGE: + switch (spec) + { + case BOT_SPEC_MAGE_ARCANE: + case BOT_SPEC_MAGE_FIRE: + case BOT_SPEC_MAGE_FROST: + return true; + default: + break; + } + break; + case BOT_CLASS_WARLOCK: + switch (spec) + { + case BOT_SPEC_WARLOCK_AFFLICTION: + case BOT_SPEC_WARLOCK_DEMONOLOGY: + case BOT_SPEC_WARLOCK_DESTRUCTION: + return true; + default: + break; + } + break; + case BOT_CLASS_DRUID: + switch (spec) + { + case BOT_SPEC_DRUID_BALANCE: + case BOT_SPEC_DRUID_FERAL: + case BOT_SPEC_DRUID_RESTORATION: + return true; + default: + break; + } + break; + case BOT_CLASS_BM: + case BOT_CLASS_SPHYNX: + case BOT_CLASS_ARCHMAGE: + case BOT_CLASS_DREADLORD: + case BOT_CLASS_SPELLBREAKER: + case BOT_CLASS_DARK_RANGER: + case BOT_CLASS_NECROMANCER: + case BOT_CLASS_SEA_WITCH: + case BOT_CLASS_CRYPT_LORD: + return spec == BOT_SPEC_DEFAULT; + default: + break; + } + return false; +} + +void bot_ai::InitEquips() +{ + EquipmentInfo const* einfo = BotDataMgr::GetBotEquipmentInfo(me->GetEntry()); + ASSERT(einfo, "Trying to spawn bot with no equip info!"); + + NpcBotData const* npcBotData = BotDataMgr::SelectNpcBotData(me->GetEntry()); + ASSERT(npcBotData, "bot_ai::InitEquips(): data not found!"); + + if (IsWanderer()) + { + GenerateRand(); + uint8 lvl = me->GetLevel(); + std::ostringstream gss; + gss << "bot_ai::InitEquips(): Wanderer bot " << me->GetName() << " id " << me->GetEntry() << ' ' << "level " << uint32(lvl) << " generated gear:"; + for (uint8 i = BOT_SLOT_MAINHAND; i < BOT_INVENTORY_SIZE; ++i) + { + if (i == BOT_SLOT_OFFHAND && (!_canUseOffHand() || (lvl < 10 && IsCastingClass(_botclass)))) + continue; + if ((i == BOT_SLOT_FINGER1 || i == BOT_SLOT_FINGER2 || i == BOT_SLOT_NECK || i == BOT_SLOT_SHOULDERS) && lvl < 20) + continue; + if ((i == BOT_SLOT_TRINKET1 || i == BOT_SLOT_TRINKET2 || i == BOT_SLOT_HEAD) && lvl < 30) + continue; + + Item* item = BotDataMgr::GenerateWanderingBotItem(i, _botclass, lvl, [this, lslot = i](ItemTemplate const* proto) { + if (!_canEquip(proto, lslot, true)) + return false; + + if (me->GetLevel() >= DEFAULT_MAX_LEVEL && me->GetMap()->IsBattlegroundOrArena() && Rand() < 50) + { + if (Rand() < 20 && proto->ItemLevel < 245) + return false; + if (Rand() < 10 && proto->ItemLevel < 264) + return false; + + switch (lslot) + { + case BOT_SLOT_HEAD: + case BOT_SLOT_SHOULDERS: + case BOT_SLOT_CHEST: + case BOT_SLOT_WAIST: + case BOT_SLOT_LEGS: + case BOT_SLOT_FEET: + case BOT_SLOT_WRIST: + case BOT_SLOT_HANDS: + if (!std::any_of(proto->ItemStat.cbegin(), proto->ItemStat.cend(), [](_ItemStat const& stat) { + return stat.ItemStatType == ITEM_MOD_RESILIENCE_RATING && stat.ItemStatValue > 0; + })) + return false; + break; + default: + break; + } + } + + switch (GetSpec()) + { + case BOT_SPEC_WARRIOR_ARMS: + switch (lslot) + { + case BOT_SLOT_MAINHAND: + return proto->InventoryType == INVTYPE_2HWEAPON; + default: + break; + } + break; + case BOT_SPEC_WARRIOR_FURY: + switch (lslot) + { + case BOT_SLOT_MAINHAND: + return (me->GetLevel() < 60) ? (proto->InventoryType == INVTYPE_WEAPON || proto->InventoryType == INVTYPE_WEAPONMAINHAND) : + (proto->InventoryType == INVTYPE_2HWEAPON); + case BOT_SLOT_OFFHAND: + return (me->GetLevel() < 60) ? (proto->InventoryType == INVTYPE_WEAPON || proto->InventoryType == INVTYPE_WEAPONOFFHAND) : + (proto->InventoryType == INVTYPE_2HWEAPON); + default: + break; + } + break; + case BOT_SPEC_WARRIOR_PROTECTION: + switch (lslot) + { + case BOT_SLOT_MAINHAND: + return proto->InventoryType == INVTYPE_WEAPON || proto->InventoryType == INVTYPE_WEAPONMAINHAND; + case BOT_SLOT_OFFHAND: + return proto->InventoryType == INVTYPE_SHIELD; + default: + break; + } + break; + case BOT_SPEC_PALADIN_PROTECTION: + switch (lslot) + { + case BOT_SLOT_MAINHAND: + if (!(proto->InventoryType == INVTYPE_WEAPON || proto->InventoryType == INVTYPE_WEAPONMAINHAND)) + return false; + if (me->GetLevel() < 70) + break; + return !std::any_of(proto->ItemStat.cbegin(), proto->ItemStat.cend(), [](_ItemStat const& stat) { + return stat.ItemStatType == ITEM_MOD_INTELLECT && stat.ItemStatValue > 0; + }); + case BOT_SLOT_OFFHAND: + if (!(proto->InventoryType == INVTYPE_SHIELD)) + return false; + if (me->GetLevel() < 70) + break; + return !std::any_of(proto->ItemStat.cbegin(), proto->ItemStat.cend(), [](_ItemStat const& stat) { + return stat.ItemStatType == ITEM_MOD_INTELLECT && stat.ItemStatValue > 0; + }); + default: + break; + } + break; + case BOT_SPEC_PALADIN_HOLY: + case BOT_SPEC_SHAMAN_ELEMENTAL: + case BOT_SPEC_SHAMAN_RESTORATION: + switch (lslot) + { + case BOT_SLOT_MAINHAND: + if (!(proto->InventoryType == INVTYPE_WEAPON || proto->InventoryType == INVTYPE_WEAPONMAINHAND)) + return false; + if (me->GetLevel() < 70) + break; + return std::any_of(proto->ItemStat.cbegin(), proto->ItemStat.cend(), [](_ItemStat const& stat) { + return stat.ItemStatType == ITEM_MOD_INTELLECT && stat.ItemStatValue > 0; + }); + case BOT_SLOT_OFFHAND: + if (!(proto->InventoryType == INVTYPE_SHIELD)) + return false; + if (me->GetLevel() < 70) + break; + return std::any_of(proto->ItemStat.cbegin(), proto->ItemStat.cend(), [](_ItemStat const& stat) { + return stat.ItemStatType == ITEM_MOD_INTELLECT && stat.ItemStatValue > 0; + }); + default: + if (me->GetLevel() < 70) + break; + return std::any_of(proto->ItemStat.cbegin(), proto->ItemStat.cend(), [](_ItemStat const& stat) { + return stat.ItemStatType == ITEM_MOD_INTELLECT && stat.ItemStatValue > 0; + }); + } + break; + case BOT_SPEC_PALADIN_RETRIBUTION: + switch (lslot) + { + case BOT_SLOT_MAINHAND: + return proto->InventoryType == INVTYPE_2HWEAPON; + default: + break; + } + break; + case BOT_SPEC_HUNTER_BEASTMASTERY: + case BOT_SPEC_HUNTER_MARKSMANSHIP: + case BOT_SPEC_HUNTER_SURVIVAL: + switch (lslot) + { + case BOT_SLOT_TRINKET1: case BOT_SLOT_TRINKET2: + break; + default: + if (me->GetLevel() < 70) + break; + return std::any_of(proto->ItemStat.cbegin(), proto->ItemStat.cend(), [](_ItemStat const& stat) { + return stat.ItemStatType == ITEM_MOD_AGILITY && stat.ItemStatValue > 0; + }); + break; + } + break; + case BOT_SPEC_ROGUE_ASSASINATION: + switch (lslot) + { + case BOT_SLOT_MAINHAND: case BOT_SLOT_OFFHAND: + return proto->SubClass == ITEM_SUBCLASS_WEAPON_DAGGER; + case BOT_SLOT_RANGED: + return me->GetLevel() < 64 || proto->SubClass == ITEM_SUBCLASS_WEAPON_THROWN; + default: + break; + } + break; + case BOT_SPEC_ROGUE_COMBAT: + switch (lslot) + { + case BOT_SLOT_MAINHAND: case BOT_SLOT_OFFHAND: + return proto->SubClass == ITEM_SUBCLASS_WEAPON_SWORD || proto->SubClass == ITEM_SUBCLASS_WEAPON_AXE; + case BOT_SLOT_RANGED: + return me->GetLevel() < 64 || proto->SubClass == ITEM_SUBCLASS_WEAPON_THROWN; + default: + break; + } + break; + case BOT_SPEC_ROGUE_SUBTLETY: + switch (lslot) + { + case BOT_SLOT_MAINHAND: case BOT_SLOT_OFFHAND: + return proto->SubClass == ITEM_SUBCLASS_WEAPON_MACE; + case BOT_SLOT_RANGED: + return me->GetLevel() < 64 || proto->SubClass == ITEM_SUBCLASS_WEAPON_THROWN; + default: + break; + } + break; + case BOT_SPEC_DK_FROST: + switch (lslot) + { + case BOT_SLOT_MAINHAND: + return me->GetLevel() < 61 || proto->InventoryType == INVTYPE_2HWEAPON; + default: + break; + } + break; + case BOT_SPEC_SHAMAN_ENHANCEMENT: + switch (lslot) + { + case BOT_SLOT_OFFHAND: + return proto->InventoryType == INVTYPE_WEAPON || proto->InventoryType == INVTYPE_WEAPONOFFHAND; + case BOT_SLOT_TRINKET1: case BOT_SLOT_TRINKET2: + break; + default: + if (me->GetLevel() < 70) + break; + return std::any_of(proto->ItemStat.cbegin(), proto->ItemStat.cend(), [](_ItemStat const& stat) { + return stat.ItemStatType == ITEM_MOD_AGILITY && stat.ItemStatValue > 0; + }); + } + break; + case BOT_SPEC_DRUID_FERAL: + switch (lslot) + { + case BOT_SLOT_TRINKET1: case BOT_SLOT_TRINKET2: + break; + case BOT_SLOT_MAINHAND: + if (proto->InventoryType != INVTYPE_2HWEAPON) + return false; + [[fallthrough]]; + default: + if (me->GetLevel() < 70) + break; + return std::any_of(proto->ItemStat.cbegin(), proto->ItemStat.cend(), [](_ItemStat const& stat) { + return stat.ItemStatType == ITEM_MOD_AGILITY && stat.ItemStatValue > 0; + }); + } + break; + case BOT_SPEC_DRUID_BALANCE: + switch (lslot) + { + case BOT_SLOT_TRINKET1: case BOT_SLOT_TRINKET2: + break; + default: + if (me->GetLevel() < 70) + break; + return std::any_of(proto->ItemStat.cbegin(), proto->ItemStat.cend(), [](_ItemStat const& stat) { + return stat.ItemStatType == ITEM_MOD_INTELLECT && stat.ItemStatValue > 0; + }); + } + break; + default: + break; + } + + return true; + }); + + if (!item) + { + if (i <= BOT_SLOT_RANGED && einfo->ItemEntry[i] != 0) + { + TC_LOG_WARN("npcbots", "Wanderer bot {} id {} level {} can't generate req gear in slot {}, generating standard item!", + me->GetName(), me->GetEntry(), uint32(me->GetLevel()), uint32(i)); + + item = Item::CreateItem(einfo->ItemEntry[i], 1); + ASSERT(item, "Failed to init standard Item for wandering bot!"); + _equips[i] = item; + } + } + else + { + _equips[i] = item; + if (GetSpec() != BOT_SPEC_DEFAULT && BotDataMgr::GenerateWanderingBotItemEnchants(item, i, GetSpec())) {} + + gss << " [" << uint32(i) << "] " << _equips[i]->GetTemplate()->Name1 << " (" << _equips[i]->GetEntry() << ')'; + } + } + TC_LOG_TRACE("npcbots", "{}", gss.str()); + } + else + { + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_NPCBOT_EQUIP_BY_ITEM_INSTANCE); + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 + //"SELECT creatorGuid, giftCreatorGuid, count, duration, charges, flags, enchantments, randomPropertyId, durability, playedTime, text, guid, itemEntry, owner_guid " + // "FROM item_instance WHERE guid IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_SYNCH + + std::array assigned_item_guids{}; + for (uint8 i = 0; i != BOT_INVENTORY_SIZE; ++i) + { + stmt->setUInt32(i, npcBotData->equips[i]); + assigned_item_guids[i] = npcBotData->equips[i]; + } + + PreparedQueryResult iiresult = CharacterDatabase.Query(stmt); + + if (!iiresult) //blank bot - fill with standard items + { + for (uint8 i = 0; i != MAX_EQUIPMENT_ITEMS; ++i) + { + uint32 itemId = einfo->ItemEntry[i]; + if (!itemId) + continue; + + Item* item = Item::CreateItem(itemId, 1, nullptr); + ASSERT(item, "Failed to init standard Item for bot!"); + _equips[i] = item; + } + } + else + { + Field* fields2; + do + { + fields2 = iiresult->Fetch(); + uint32 itemGuidLow = fields2[11].GetUInt32(); + uint32 itemId = fields2[12].GetUInt32(); + Item* item = new Item; + ASSERT(item->LoadFromDB(itemGuidLow, ObjectGuid::Empty, fields2, itemId)); + //gonna find where to store our new item + bool found = false; + for (uint8 i = BOT_SLOT_MAINHAND; i != BOT_INVENTORY_SIZE; ++i) + { + if (npcBotData->equips[i] == itemGuidLow && !_equips[i]) + { + _equips[i] = item; + found = true; + BotLogger::Log(NPCBOT_LOG_INIT_EQUIP, me, uint32(i), uint32(itemGuidLow), uint32(itemId)); + break; + } + } + ASSERT(found); + for (uint8 i = 0; i != BOT_INVENTORY_SIZE; ++i) + { + if (assigned_item_guids[i] == itemGuidLow) + assigned_item_guids[i] = 0; + } + + } while (iiresult->NextRow()); + } + + for (uint8 i = 0; i != BOT_INVENTORY_SIZE; ++i) + { + if (assigned_item_guids[i] != 0) + TC_LOG_ERROR("npcbots", "InitEquips: bot {} {} owner {} has item guid {} assigned to slot {} which doesn't exist in DB!", + me->GetEntry(), me->GetName(), _ownerGuid, assigned_item_guids[i], uint32(i)); + } + } + + //visualize + for (uint8 i = BOT_SLOT_MAINHAND; i <= BOT_SLOT_RANGED; ++i) + { + if (CanChangeEquip(i) && _equips[i]) + { + NpcBotTransmogData const* transmogData = BotDataMgr::SelectNpcBotTransmogs(me->GetEntry()); + if (einfo->ItemEntry[i] != _equips[i]->GetEntry() && transmogData && BotMgr::IsTransmogEnabled() && (transmogData->transmogs[i].first == _equips[i]->GetEntry() || BotMgr::TransmogUseEquipmentSlots())) + me->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + i, uint32(std::max(transmogData->transmogs[i].second, 0))); + else + me->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + i, _equips[i]->GetEntry()); + } + else if (einfo->ItemEntry[i]) + me->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + i, einfo->ItemEntry[i]); + } + + //apply weapons' parameters + if (Item const* MH = _equips[BOT_SLOT_MAINHAND]) + { + uint32 itemId = MH->GetEntry(); + if (einfo->ItemEntry[0] != itemId) + { + if (ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId)) + { + if (RespectEquipsAttackTime()) + me->SetAttackTime(BASE_ATTACK, proto->Delay); + ApplyItemBonuses(BOT_SLOT_MAINHAND); + } + } + } + if (Item const* OH = _equips[BOT_SLOT_OFFHAND]) + { + uint32 itemId = OH->GetEntry(); + if (ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId)) + { + if (einfo->ItemEntry[1] != itemId) + ApplyItemBonuses(BOT_SLOT_OFFHAND); + + if (proto->Class == ITEM_CLASS_WEAPON) + { + if (RespectEquipsAttackTime()) + me->SetAttackTime(OFF_ATTACK, proto->Delay); + me->SetCanDualWield(true); + } + else if (proto->Class == ITEM_CLASS_ARMOR && proto->SubClass == ITEM_SUBCLASS_ARMOR_SHIELD) + { + if (me->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_NO_BLOCK) + const_cast(me->GetCreatureTemplate())->flags_extra &= ~CREATURE_FLAG_EXTRA_NO_BLOCK; + } + } + } + if (Item const* RH = _equips[BOT_SLOT_RANGED]) + { + uint32 itemId = RH->GetEntry(); + if (einfo->ItemEntry[2] != itemId) + { + if (ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId)) + { + if (proto->Class == ITEM_CLASS_WEAPON) + if (RespectEquipsAttackTime()) + me->SetAttackTime(RANGED_ATTACK, proto->Delay); + + ApplyItemBonuses(BOT_SLOT_RANGED); + } + } + } + + for (uint8 i = BOT_SLOT_RANGED + 1; i != BOT_INVENTORY_SIZE; ++i) + ApplyItemBonuses(i); + + ApplyItemSetBonuses(nullptr, true); + + for (uint8 i = 0; i != MAX_EQUIPMENT_ITEMS; ++i) + { + if (_equips[i] == nullptr && einfo->ItemEntry[i] != 0) + { + if (i == BOT_SLOT_OFFHAND && !_canUseOffHand()) + { + me->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + i, uint32(0)); + continue; + } + + //if bot has no equips but equip template then use those + Item* item = Item::CreateItem(einfo->ItemEntry[i], 1, nullptr); + ASSERT(item, "Failed to init standard Item for bot point 2!"); + _equips[i] = item; + + me->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + i, einfo->ItemEntry[i]); + if (i == BOT_SLOT_OFFHAND) + { + if (ItemTemplate const* proto = sObjectMgr->GetItemTemplate(einfo->ItemEntry[i])) + { + if (proto->Class == ITEM_CLASS_WEAPON) + { + me->SetAttackTime(OFF_ATTACK, _botclass == BOT_CLASS_ROGUE ? 1400 : 1800); + me->SetCanDualWield(true); + } + else if (proto->Class == ITEM_CLASS_ARMOR && proto->SubClass == ITEM_SUBCLASS_ARMOR_SHIELD) + { + if (me->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_NO_BLOCK) + const_cast(me->GetCreatureTemplate())->flags_extra &= ~CREATURE_FLAG_EXTRA_NO_BLOCK; + } + } + } + } + } + + for (uint8 slot = BOT_SLOT_MAINHAND; slot < BOT_INVENTORY_SIZE; ++slot) + { + if (_equips[slot] && IsUsableItem(_equips[slot])) + { + uint32 slotMask = 1ul << slot; + ASSERT(!(_usableItemSlotsMask & slotMask)); + _usableItemSlotsMask |= slotMask; + } + } +} + +void bot_ai::FindMaster() +{ + //totally free + if (!_ownerGuid) + return; + if (me->IsInWorld() && (!_atHome || _evadeMode)) + return; + if (!BotMgr::IsClassEnabled(_botclass)) + return; + + //delay + if (checkMasterTimer > lastdiff) + return; + + checkMasterTimer = urand(1000, 3000); + + //already have master + if (!IAmFree()) + return; + if (HasBotCommandState(BOT_COMMAND_UNBIND)) + return; + + if (Player* player = ObjectAccessor::FindPlayerByLowGUID(_ownerGuid)) + { + //prevent bot being screwed up because of wrong flags + if (player->IsGameMaster() || player->GetSession()->isLogingOut() || player->GetSession()->PlayerLogout()) + return; + + if (!SetBotOwner(player)) + return; //fail + + //if (!IsTempBot()) + // BotWhisper("Hey...", master); + } +} + +uint32 bot_ai::CalculateOwnershipCheckTime() +{ + return std::min(BotMgr::GetOwnershipExpireTime(), urand(58 * MINUTE * IN_MILLISECONDS, 62 * MINUTE * IN_MILLISECONDS)); +} + +bool bot_ai::IAmFree() const +{ + if (!_ownerGuid) + return true; + if (_ownerGuid != master->GetGUID().GetRawValue()) + return true; + //if (!me->HasUnitTypeMask(UNIT_MASK_MINION)) + // return true; + + return false; + //return (!_ownerGuid || _ownerGuid != master->GetGUID() || !me->HasUnitTypeMask(UNIT_MASK_MINION)); + // //has owner and //owner is found and //bound to owner +} + +//UTILITIES +void bot_ai::_AddItemTemplateLink(Player const* forPlayer, ItemTemplate const* item, std::ostringstream &str) const +{ + //color + str << "|c"; + switch (item->Quality) + { + case ITEM_QUALITY_POOR: str << "ff9d9d9d"; break; //GREY + case ITEM_QUALITY_NORMAL: str << "ffffffff"; break; //WHITE + case ITEM_QUALITY_UNCOMMON: str << "ff1eff00"; break; //GREEN + case ITEM_QUALITY_RARE: str << "ff0070dd"; break; //BLUE + case ITEM_QUALITY_EPIC: str << "ffa335ee"; break; //PURPLE + case ITEM_QUALITY_LEGENDARY:str << "ffff8000"; break; //ORANGE + case ITEM_QUALITY_ARTIFACT: str << "ffe6cc80"; break; //LIGHT YELLOW + case ITEM_QUALITY_HEIRLOOM: str << "ffe6cc80"; break; //LIGHT YELLOW + default: str << "ff000000"; break; //UNK BLACK + } + str << "|Hitem:" << uint32(item->ItemId) << ':'; + + //permanent enchantment, 3 gems, 4 unknowns, reporter_level (9) + str << "0:0:0:0:0:0:0:0:0"; + + //name + std::string name = item->Name1; + _LocalizeItem(forPlayer, name, item->ItemId); + str << "|h[" << name << "]|h|r"; + + //max in stack + if (item->BuyCount > 1) + str<< "|cff009900x" << item->BuyCount << ".|r"; + else + str << "|cff009900.|r"; +} +// |TInterface\\Icons\\INV_:|t|color|Hitem:item_id:perm_ench_id:gem1:gem2:gem3:0:random_property:suffix_factor:reporter_level|h[name]|h|r +// |TInterface\\Icons\\INV_Misc_Staff_01:16|t|cffa335ee|Hitem:812:0:0:0:0:0:0:0:70|h[Glowing Brightwood Staff]|h|r +void bot_ai::_AddItemLink(Player const* forPlayer, Item const* item, std::ostringstream &str, bool addIcon) const +{ + ItemTemplate const* proto = item->GetTemplate(); + //ItemRandomSuffixEntry const* item_rand = sItemRandomSuffixStore.LookupEntry(abs(item->GetItemRandomPropertyId())); + uint32 g1 = 0, g2 = 0, g3 = 0; + //uint32 bpoints = 0; + std::string name = proto->Name1; + std::string suffix = ""; + + //icon + if (addIcon) + { + ItemDisplayInfoEntry const* itemDisplayEntry = sItemDisplayInfoStore.LookupEntry(item->GetTemplate()->DisplayInfoID); + if (itemDisplayEntry) + str << "|TInterface\\Icons\\" << itemDisplayEntry->InventoryIcon << ":16|t"; + } + + //color + str << "|c"; + switch (proto->Quality) + { + case ITEM_QUALITY_POOR: str << "ff9d9d9d"; break; //GREY + case ITEM_QUALITY_NORMAL: str << "ffffffff"; break; //WHITE + case ITEM_QUALITY_UNCOMMON: str << "ff1eff00"; break; //GREEN + case ITEM_QUALITY_RARE: str << "ff0070dd"; break; //BLUE + case ITEM_QUALITY_EPIC: str << "ffa335ee"; break; //PURPLE + case ITEM_QUALITY_LEGENDARY:str << "ffff8000"; break; //ORANGE + case ITEM_QUALITY_ARTIFACT: str << "ffe6cc80"; break; //LIGHT YELLOW + case ITEM_QUALITY_HEIRLOOM: str << "ffe6cc80"; break; //LIGHT YELLOW + default: str << "ff000000"; break; //UNK BLACK + } + str << "|Hitem:" << proto->ItemId << ':'; + + //permanent enchantment + str << item->GetEnchantmentId(PERM_ENCHANTMENT_SLOT) << ':'; + //gems 3 + for (uint32 slot = SOCK_ENCHANTMENT_SLOT; slot != SOCK_ENCHANTMENT_SLOT + MAX_ITEM_PROTO_SOCKETS; ++slot) + { + uint32 eId = item->GetEnchantmentId(EnchantmentSlot(slot)); + + switch (slot - SOCK_ENCHANTMENT_SLOT) + { + case 0: g1 = eId; break; + case 1: g2 = eId; break; + case 2: g3 = eId; break; + } + } + str << g1 << ':' << g2 << ':' << g3 << ':'; + //always zero + str << 0 << ':'; + //random property + str << item->GetItemRandomPropertyId() << ':'; + str << item->GetItemSuffixFactor() << ':'; + + //reporter level + str << uint32(me->GetLevel()); + + //name + _LocalizeItem(forPlayer, name, suffix, item); + + str << "|h[" << name; + if (suffix.length() > 0) + str << ' ' << suffix; + str <<"]|h|r"; + + //quantity + if (item->GetCount() > 1) + str << "x" << item->GetCount() << ' '; + + //TC_LOG_ERROR("entities.player", "bot_ai::_AddItemLink(): {}", str.str()); +} +//Unused +void bot_ai::_AddQuestLink(Player const* forPlayer, Quest const* quest, std::ostringstream &str) const +{ + std::string questTitle = quest->GetTitle(); + _LocalizeQuest(forPlayer, questTitle, quest->GetQuestId()); + str << "|cFFEFFD00|Hquest:" << quest->GetQuestId() << ':' << quest->GetQuestLevel() << "|h[" << questTitle << "]|h|r"; +} +//Unsused +void bot_ai::_AddWeaponSkillLink(Player const* forPlayer, SpellInfo const* spellInfo, std::ostringstream &str, uint32 skillid) const +{ + uint32 loc = forPlayer->GetSession()->GetSessionDbcLocale(); + str << "|cff00ffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc] << " : " << master->GetSkillValue(skillid) << " /" << master->GetMaxSkillValue(skillid) << "]|h|r"; +} +//|cff71d5ff|Hspell:21563|h[Command]|h|r +void bot_ai::_AddSpellLink(Player const* forPlayer, SpellInfo const* spellInfo, std::ostringstream &str, bool color/* = true*/) const +{ + uint32 loc = forPlayer->GetSession()->GetSessionDbcLocale(); + str << "|c"; + + if (color) + { + switch (GetFirstSchoolInMask(spellInfo->GetSchoolMask())) + { + case SPELL_SCHOOL_NORMAL: str << "ffffff00"; break; //YELLOW + case SPELL_SCHOOL_HOLY: str << "ffffe680"; break; //LIGHT YELLOW + case SPELL_SCHOOL_FIRE: str << "ffff8000"; break; //ORANGE + case SPELL_SCHOOL_NATURE: str << "ff4dff4d"; break; //GREEN + case SPELL_SCHOOL_FROST: str << "ff80ffff"; break; //LIGHT BLUE + case SPELL_SCHOOL_SHADOW: str << "ff8080ff"; break; //DARK BLUE + case SPELL_SCHOOL_ARCANE: str << "ffff80ff"; break; //LIGHT PURPLE + default: str << "ffffffff"; break; //UNK WHITE + } + } + else + str << "ffffffff"; //default white + + str << "|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[loc] << "]|h|r"; +} +//Unused +void bot_ai::_AddProfessionLink(Player const* forPlayer, SpellInfo const* spellInfo, std::ostringstream &str, uint32 skillId) const +{ + ASSERT(master->HasSkill(skillId)); + // |cffffd000|Htrade:4037:1:150:1:6AAAAAAAAAAAAAAAAAAAAAAOAADAAAAAAAAAAAAAAAAIAAAAAAAAA|h[Engineering]|h|r + uint32 loc = forPlayer->GetSession()->GetSessionDbcLocale(); + SkillLineEntry const* skillInfo = sSkillLineStore.LookupEntry(skillId); + if (skillInfo) + { + uint32 curValue = master->GetPureSkillValue(skillId); + uint32 maxValue = master->GetPureMaxSkillValue(skillId); + str << "|cffffd000|Htrade:" << spellInfo->Id << ':' << curValue << ':' << maxValue << ':' << master->GetGUID().GetCounter() << ":6AAAAAAAAAAAAAAAAAAAAAAOAADAAAAAAAAAAAAAAAAIAAAAAAAAA" << "|h[" << skillInfo->DisplayName[loc] << "]|h|r"; + } +} +//Localization +void bot_ai::_LocalizeItem(Player const* forPlayer, std::string &itemName, uint32 entry) const +{ + uint32 loc = forPlayer->GetSession()->GetSessionDbLocaleIndex(); + std::wstring wnamepart; + + ItemLocale const* itemInfo = sObjectMgr->GetItemLocale(entry); + if (!itemInfo) + return; + + if (itemInfo->Name.size() > loc && !itemInfo->Name[loc].empty()) + { + const std::string name = itemInfo->Name[loc]; + if (Utf8FitTo(name, wnamepart)) + itemName = name; + } +} + +void bot_ai::_LocalizeItem(Player const* forPlayer, std::string &itemName, std::string &suffix, Item const* item) const +{ + uint32 loc = forPlayer->GetSession()->GetSessionDbLocaleIndex(); + std::wstring wnamepart; + + ItemLocale const* itemInfo = sObjectMgr->GetItemLocale(item->GetEntry()); + if (loc > 0 && itemInfo && itemInfo->Name.size() > loc && !itemInfo->Name[loc].empty()) + { + const std::string name = itemInfo->Name[loc]; + if (Utf8FitTo(name, wnamepart)) + itemName = name; + } + + int32 randomPropId = item->GetItemRandomPropertyId(); + //TC_LOG_ERROR("entities.player", "bot_ai::_LocalizeItem(): randomPropId = {}", randomPropId); + if (!randomPropId) + return; + + if (randomPropId > 0) + { + if (ItemRandomPropertiesEntry const* item_rand = sItemRandomPropertiesStore.LookupEntry(randomPropId)) + { + std::array const& suffs = item_rand->Name; + //if (suffs) + { + //for (uint8 i = 0; i != MAX_LOCALES; ++i) + // TC_LOG_ERROR("entities.player", "bot_ai::_LocalizeItem(): rand prop suffix for loc {} = {}", i, suffs[i]); + suffix = suffs[loc]; + } + } + } + else + { + if (ItemRandomSuffixEntry const* item_rand = sItemRandomSuffixStore.LookupEntry(-randomPropId)) + { + std::array const& suffs = item_rand->Name; + //if (suffs) + { + //for (uint8 i = 0; i != MAX_LOCALES; ++i) + // TC_LOG_ERROR("entities.player", "bot_ai::_LocalizeItem(): rand suff suffix for loc {} = {}", i, suffs[i]); + suffix = suffs[loc]; + } + } + } +} + +void bot_ai::_LocalizeQuest(Player const* forPlayer, std::string &questTitle, uint32 entry) const +{ + uint32 loc = forPlayer->GetSession()->GetSessionDbLocaleIndex(); + std::wstring wnamepart; + + QuestLocale const* questInfo = sObjectMgr->GetQuestLocale(entry); + if (!questInfo) + return; + + if (questInfo->Title.size() > loc && !questInfo->Title[loc].empty()) + { + const std::string title = questInfo->Title[loc]; + if (Utf8FitTo(title, wnamepart)) + questTitle = title; + } +} + +void bot_ai::_LocalizeCreature(Player const* forPlayer, std::string &creatureName, uint32 entry) const +{ + uint32 loc = forPlayer->GetSession()->GetSessionDbLocaleIndex(); + std::wstring wnamepart; + + CreatureLocale const* creatureInfo = sObjectMgr->GetCreatureLocale(entry); + if (!creatureInfo) + return; + + if (creatureInfo->Name.size() > loc && !creatureInfo->Name[loc].empty()) + { + const std::string title = creatureInfo->Name[loc]; + if (Utf8FitTo(title, wnamepart)) + creatureName = title; + } +} + +void bot_ai::_LocalizeGameObject(Player const* forPlayer, std::string &gameobjectName, uint32 entry) const +{ + uint32 loc = forPlayer->GetSession()->GetSessionDbLocaleIndex(); + std::wstring wnamepart; + + GameObjectLocale const* gameObjectInfo = sObjectMgr->GetGameObjectLocale(entry); + if (!gameObjectInfo) + return; + + if (gameObjectInfo->Name.size() > loc && !gameObjectInfo->Name[loc].empty()) + { + const std::string title = gameObjectInfo->Name[loc]; + if (Utf8FitTo(title, wnamepart)) + gameobjectName = title; + } +} + +void bot_ai::_LocalizeSpell(Player const* forPlayer, std::string &spellName, uint32 entry) const +{ + uint32 loc = forPlayer->GetSession()->GetSessionDbcLocale(); + std::wstring wnamepart; + + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(entry); + if (!spellInfo) + { + spellName = LocalizedNpcText(forPlayer, BOT_TEXT_UNKNOWN); + return; + } + + std::string title = spellInfo->SpellName[loc]; + if (Utf8FitTo(title, wnamepart)) + spellName = title; + else + spellName = spellInfo->SpellName[sWorld->GetDefaultDbcLocale()]; +} + +void bot_ai::BotJump(Position const* pos, bool count) +{ + if (count) + ++_jumpCount; + + me->BotStopMovement(); + me->GetMotionMaster()->MoveJump(*pos, me->GetExactDist2d(pos->m_positionX, pos->m_positionY), 10.0f); +} + +bool bot_ai::UpdateImpossibleChase(Unit const* target) +{ + if (_chaseTimer > lastdiff || me->isMoving() || !IAmFree()) + return false; + + if (JumpingOrFalling()) + return false; + + if (_jumpCount >= 3) + { + me->AttackStop(); + Evade(); + return true; + } + + if (_unreachableCount < 5) + { + if ((IsRanged() ? me->GetDistance(target) > 40.0f : !me->IsWithinMeleeRange(target)) || + (target->GetTypeId() == TYPEID_UNIT && !me->IsWithinLOSInMap(target, LINEOFSIGHT_ALL_CHECKS, VMAP::ModelIgnoreFlags::M2))) + { + ++_unreachableCount; + ResetChaseTimer(target); + BotMovement(BOT_MOVE_POINT, target, nullptr, false); + } + else + _unreachableCount = 0; + + return true; + } + + if (target->IsPlayer() && (!me->IsWithinDist(target, HasRole(BOT_ROLE_RANGED) ? 65 : 40) || me->IsWithinDist(target, HasRole(BOT_ROLE_RANGED) ? 35 : 10))) + return false; + + _unreachableCount = 0; + + ResetChaseTimer(target); + + BotJump(target); + return true; +} + +void bot_ai::ResetChaseTimer(Position const* /*pos*/) +{ + _chaseTimer = IsWanderer() ? 5000 : 20000; +} + +void bot_ai::ResetChase(Position const* pos) +{ + if (!IAmFree()) + return; + + ResetChaseTimer(pos); + _unreachableCount = 0; + _jumpCount = 0; +} + +void bot_ai::ResetEngageTimer(uint32 delay) +{ + _engageTimer = delay; +} + +void bot_ai::OnStartAttack(Unit const* u) +{ + if (u->GetGUID() != _lastTargetGuid) + { + ResetChase(u); + _lastTargetGuid = u->GetGUID(); + } +} + +bool bot_ai::StartAttack(Unit const* u, bool force) +{ + if (HasBotCommandState(BOT_COMMAND_ATTACK) && !force) + return false; + + SetBotCommandState(BOT_COMMAND_ATTACK); + OnStartAttack(u); + return true; +} + +void bot_ai::JustEnteredCombat(Unit* u) +{ + _atHome = false; + + //clear gossip during combat. See CheckAuras() for restore + if (me->HasNpcFlag(UNIT_NPC_FLAG_GOSSIP)) + me->RemoveNpcFlag(UNIT_NPC_FLAG_GOSSIP); + + _evadeMode = false; + _evadeCount = 0; + evadeDelayTimer = 0; + AbortTeleport(); + + ResetChase(u); + + if (IsLastOrder(BOT_ORDER_PULL, 0, u->GetGUID())) + CompleteOrder(_orders.front()); + + if (IAmFree() && me->GetVictim() && me->GetVictim() != u && + (me->getAttackers().empty() || (me->getAttackers().size() == 1u && *me->getAttackers().begin() == u)) && + me->GetVictim()->GetVictim() != me && !(me->GetVictim()->IsInCombat() || me->GetVictim()->IsInCombatWith(me))) + { + bool byspell = false; + switch (_botclass) + { + case BOT_CLASS_DRUID: + byspell = GetBotStance() == BOT_STANCE_NONE || GetBotStance() == DRUID_MOONKIN_FORM; + break; + case BOT_CLASS_PRIEST: + case BOT_CLASS_MAGE: + case BOT_CLASS_WARLOCK: + case BOT_CLASS_SHAMAN: + case BOT_CLASS_SPHYNX: + case BOT_CLASS_ARCHMAGE: + byspell = true; + break; + default: + break; + } + + if (CanBotAttack(u, byspell)) + { + me->AttackStop(); + me->BotStopMovement(); + SetBotCommandState(BOT_COMMAND_COMBATRESET); //reset AttackStart() + me->Attack(u, !HasRole(BOT_ROLE_RANGED)); + } + } +} +//killer may be NULL +void bot_ai::JustDied(Unit* u) +{ + AbortTeleport(); + AbortAwaitStateRemoval(); + KillEvents(false); + CancelAllOrders(); + + if (me->GetVehicle()) + me->ExitVehicle(); + + if (me->GetTransport()) + { + me->ClearUnitState(UNIT_STATE_IGNORE_PATHFINDING); + me->GetTransport()->RemovePassenger(me); + } + + if (IsTempBot()) + { + //TC_LOG_ERROR("entities.player", "Unsummoning temp bot {} ({}), owner: {} ({})...", + // me->GetName(), me->GetGUID().ToString(), master->GetName(), master->GetGUID().ToString()); + + if (!IAmFree()) + master->GetBotMgr()->RemoveBot(me->GetGUID(), BOT_REMOVE_UNSUMMON); + + me->AddObjectToRemoveList(); + return; + } + else if (!IAmFree()) + { + if (Group* gr = master->GetGroup()) + if (gr->IsMember(me->GetGUID())) + gr->SendUpdate(); + } + + if (IsWanderer() && me->GetMap()->IsBattlegroundOrArena()) + { + if (Battleground const* bg = GetBG()) + { + TeamId my_team = BotDataMgr::GetTeamIdForFaction(me->GetFaction()); + if (WorldSafeLocsEntry const* gy = bg->GetClosestGraveyardForBot(*me, my_team == TEAM_HORDE ? HORDE : ALLIANCE)) + { + Position pos(gy->Loc.X, gy->Loc.Y, gy->Loc.Z, me->GetOrientation()); + Events.AddEventAtOffset([me = me, pos = pos]() { BotMgr::TeleportBot(me, me->GetMap(), &pos, true); }, 5s); + } + } + } + else if (u && (u->IsPvP() || u->IsControlledByPlayer() || u->IsNPCBotOrPet())) + { + TC_LOG_DEBUG("npcbots", "{} {} id {} class {} level {} WAS KILLED BY {} {} id {} class {} level {} on their way to {}!", + IsWanderer() ? "Wandering bot" : "Bot", me->GetName(), me->GetEntry(), uint32(_botclass), uint32(me->GetLevel()), + (u->IsPlayer() ? "player" : u->IsNPCBot() ? u->ToCreature()->GetBotAI()->IsWanderer() ? "wandering bot" : "bot" : u->IsNPCBotPet() ? "botpet" : "creature"), + u->GetName(), u->GetEntry(), uint32(u->GetClass()), uint32(u->GetLevel()), + IsWanderer() ? _travel_node_cur->GetName() : "''"); + } + + _reviveTimer = (IsWanderer() && !(u && u->IsControlledByPlayer())) ? REVIVE_TIMER_MEDIUM : + IAmFree() ? REVIVE_TIMER_DEFAULT : master->InBattleground() ? REVIVE_TIMER_SHORT / 2 : REVIVE_TIMER_SHORT; + _atHome = false; + _evadeMode = false; + spawned = false; + _botAwaitState = BOT_AWAIT_NONE; + + ++_deathsCount; +} +//This is triggered before SetDeathState(JUST_DIED) call +//attacker may be NULL +void bot_ai::OnDeath([[maybe_unused]] Unit* attacker/* = nullptr*/) +{ + if (AuraEffect const* sstone = me->GetDummyAuraEffect(SPELLFAMILY_GENERIC, 92, 0)) + { + uint32 spell_id; + switch (sstone->GetBase()->GetId()) + { + case 20707: spell_id = 3026; break; // rank 1 + case 20762: spell_id = 20758; break; // rank 2 + case 20763: spell_id = 20759; break; // rank 3 + case 20764: spell_id = 20760; break; // rank 4 + case 20765: spell_id = 20761; break; // rank 5 + case 27239: spell_id = 27240; break; // rank 6 + case 47883: spell_id = 47882; break; // rank 7 + default: spell_id = 0; break; + } + _selfrez_spell_id = spell_id; + } + else + _selfrez_spell_id = 0; +} + +void bot_ai::KilledUnit(Unit* u) +{ + ++_killsCount; + if (u->IsControlledByPlayer() || u->IsPvP() || u->IsNPCBotOrPet()) + { + ++_pvpKillsCount; + if (!me->GetMap()->IsBattlegroundOrArena()) + { + if (IsWanderer()) + { + TC_LOG_DEBUG("npcbots", "Wandering bot {} id {} class {} level {} KILLED {} {} id {} class {} level {} on their way to {}!", + me->GetName(), me->GetEntry(), uint32(_botclass), uint32(me->GetLevel()), + (u->IsPlayer() ? "player" : u->IsNPCBot() ? u->ToCreature()->GetBotAI()->IsWanderer() ? "wandering bot" : "bot" : u->IsNPCBotPet() ? "botpet" : "creature"), + u->GetName(), u->GetEntry(), uint32(u->GetClass()), uint32(u->GetLevel()), + _travel_node_cur->GetName()); + } + else if (u->IsNPCBot() && u->ToCreature()->GetBotAI()->IsWanderer()) + { + TC_LOG_DEBUG("npcbots", "Bot {} id {} class {} level {} KILLED wandering bot {} id {} class {} level {} on their way to {}!", + me->GetName(), me->GetEntry(), uint32(_botclass), uint32(me->GetLevel()), + u->GetName(), u->GetEntry(), uint32(u->GetClass()), uint32(u->GetLevel()), + IsWanderer() ? _travel_node_cur->GetName() : "''"); + } + } + } + + //handle BG kill BvP, BvB, BvC + if (me->GetMap()->IsBattleground()) + { + Battleground* bg = GetBG(); + //could be removed from BG + if (bg && bg->GetBots().find(me->GetGUID()) != bg->GetBots().end() && + (u->IsNPCBot() ? bg->GetBots().find(u->GetGUID()) != bg->GetBots().end() : + bg->GetPlayers().find(u->GetGUID()) != bg->GetPlayers().end())) + { + if (u->IsPlayer()) + bg->HandleBotKillPlayer(me, u->ToPlayer()); + else if (u->IsNPCBot()) + bg->HandleBotKillBot(me, u->ToCreature()); + } + else if (bg && u->GetTypeId() == TYPEID_UNIT && !u->IsNPCBotOrPet()) + bg->HandleBotKillUnit(me, u->ToCreature()); + + outdoorsTimer = 0; + } + + if (u->isType(TYPEMASK_PLAYER)) + ++_playerKillsCount; + + if (IsWanderer()) + { + shouldUpdateStats = true; + + if (me->GetMap()->GetEntry()->IsContinent()) + evadeDelayTimer = 3000; + } +} + +void bot_ai::UnsummonCreature(Creature* creature, bool /*save*/) +{ + if (creature) + { + if (bot_pet_ai* petai = creature->GetBotPetAI()) + { + petai->KillEvents(true); + petai->canUpdate = false; + } + + ASSERT_NOTNULL(creature->ToTempSummon())->UnSummon(); + } +} +void bot_ai::UnsummonPet(bool save) +{ + UnsummonCreature(botPet, save); +} + +void bot_ai::MoveInLineOfSight(Unit* /*u*/) +{ +} + +void bot_ai::AttackStart(Unit* /*u*/) +{ +} + +void bot_ai::DamageDealt(Unit* victim, uint32& damage, DamageEffectType /*damageType*/) +{ + if (victim == me) + return; + + if (damage) + { + if (Creature* cre = victim->ToCreature()) + { + if (!cre->hasLootRecipient()) + cre->SetLootRecipient(master); + + //controlled case is handled in Unit::DealDamage + if (IAmFree()) + cre->LowerPlayerDamageReq(std::min(cre->GetHealth(), damage) / (IsWanderer() ? 4 : 2)); + } + } + + if (victim->GetTypeId() == TYPEID_PLAYER) + ResetChase(victim); +} +void bot_ai::OnBotSpellStart(SpellInfo const* spellInfo) +{ + OnClassSpellStart(spellInfo); +} +//This function is called after Spell::SendSpellCooldown() and Spell::DoAllEffects...() call +void bot_ai::OnBotSpellGo(Spell const* spell, bool ok) +{ + SpellInfo const* curInfo = spell->GetSpellInfo(); + + if (ok) + { + if (CanBotAttackOnVehicle()) + { + //Set cooldown + if (!curInfo->IsCooldownStartedOnEvent() && !curInfo->IsPassive()) + { + uint32 rec = curInfo->RecoveryTime ? curInfo->GetRecoveryTime() : GetItemSpellCooldown(curInfo->Id); + uint32 catrec = curInfo->CategoryRecoveryTime; + if (!catrec && curInfo->StartRecoveryCategory == 133 && !curInfo->CalcCastTime()) + catrec = curInfo->StartRecoveryTime; + + if (rec || (!spell->GetCastTime() && curInfo->CalcCastTime())) + ApplyBotSpellCooldownMods(curInfo, rec); + if (catrec && !(curInfo->AttributesEx6 & SPELL_ATTR6_IGNORE_CATEGORY_COOLDOWN_MODS)) + ApplyBotSpellCategoryCooldownMods(curInfo, catrec); + + if (rec || catrec) + SetSpellCooldown(curInfo->GetFirstRankSpell()->Id, rec); + SetSpellCategoryCooldown(curInfo->GetFirstRankSpell(), catrec); + + if (GC_Timer < lastdiff && !IAmFree()) + waitTimer = 0; //allow next cast to be immediate + } + + if (curInfo->Id == PVPTRINKET) + SetSpellCooldown(PVPTRINKET, 120000); + if (IsPotionSpell(curInfo->Id)) + StartPotionTimer(); + if (curInfo->Id == ACTIVATE_SPEC) + SetSpec(_newspec); + if (curInfo->Id == GetSelfRezSpell()) + OnSpellHit(me, curInfo); + + OnClassSpellGo(curInfo); + } + + if (me->GetVehicleCreatureBase() && me->GetVehicleCreatureBase()->HasSpell(curInfo->Id)) + { + //delay next cast to allow cp to proc + if (curInfo->AttributesEx & (SPELL_ATTR1_REQ_COMBO_POINTS1 | SPELL_ATTR1_REQ_COMBO_POINTS2)) + GC_Timer = std::max(2000, curInfo->StartRecoveryTime); + else if (curInfo->Speed > 0.0f) + GC_Timer = std::max(1500, curInfo->StartRecoveryTime); + else + GC_Timer = curInfo->StartRecoveryTime; + } + } + else + GC_Timer = 0; + + if (HasBotCommandState(BOT_COMMAND_ISSUED_ORDER) && + !_orders.empty() && _orders.front()._type == BOT_ORDER_SPELLCAST && + _orders.front().params.spellCastParams.baseSpell == curInfo->GetFirstRankSpell()->Id) + { + if (DEBUG_BOT_ORDERS) + TC_LOG_ERROR("entities.player", "doCast(): ordered spell {} by {} was {}!", + curInfo->Id, me->GetName(), ok ? "successful" : "unsuccessful"); + CompleteOrder(_orders.front()); + } +} + +void bot_ai::OnBotOwnerSpellGo(Spell const* spell, bool ok) +{ + if (!ok) + return; + + SpellInfo const* spellInfo = spell->GetSpellInfo(); + + if (spellInfo->IsPassive()) + return; + + //TC_LOG_ERROR("entities.player", "OnBotOwnerSpellGo(): {} by {}", spellInfo->Id, master->GetName()); + + if (spell->m_targets.HasDst() && HasBotAwaitState(BOT_AWAIT_SEND) && (me->GetTransport() == master->GetTransport())) + { + Position const* spell_dest = spell->m_targets.GetDstPos(); + MoveToSendPosition(*spell_dest); + } + + if (master->GetVehicle() && me->GetVehicle() && !master->HasSpell(spellInfo->Id) && !spell->m_targets.GetGOTargetGUID()) + { + //if (((spellInfo->AttributesCu & SPELL_ATTR0_CU_DIRECT_DAMAGE) || spellInfo->HasAura(SPELL_AURA_PERIODIC_DAMAGE)) && + // (spell->m_targets.GetTargetMask() & TARGET_FLAG_UNIT) && spell->m_targets.GetUnitTargetGUID() && !me->GetVehicleBase()->GetTarget() && + // spell->m_targets.GetUnitTargetGUID() != master->GetVehicleBase()->GetGUID()) + //{ + // //master->GetVehicleBase()->SetTarget(spell->m_targets.GetUnitTargetGUID()); + // me->GetVehicleBase()->SetTarget(spell->m_targets.GetUnitTargetGUID()); + // SetBotCommandState(BOT_COMMAND_ATTACK); + // //hack + // if (!me->GetVehicleBase()->GetVictim()) + // { + // if (Unit* target = ObjectAccessor::GetUnit(*me->GetVehicleBase(), spell->m_targets.GetUnitTargetGUID())) + // me->GetVehicleBase()->Attack(target, false); + // } + //} + + Vehicle const* veh = me->GetVehicle(); + if (veh && veh->GetBase()->GetTypeId() == TYPEID_UNIT && curVehStrat == BOT_VEH_STRAT_GENERIC && + veh->GetBase()->ToCreature()->HasSpell(spellInfo->Id)) + { + SpellCastTargets targets; + if (spell->m_targets.HasDst()) + { + targets.SetDst(spell->m_targets); + veh->GetBase()->ToCreature()->BotStopMovement(); + float destangle = veh->GetBase()->GetAbsoluteAngle(spell->m_targets.GetDstPos()); + if (veh->GetBase()->GetTransport()) + destangle = Position::NormalizeOrientation(destangle - veh->GetBase()->GetTransport()->GetOrientation()); + veh->GetBase()->SetFacingTo(destangle); + //force orientation (inconsistent with SetFacingTo) + veh->GetBase()->SetOrientation(destangle); + } + if (spell->m_targets.GetSpeed() != 0) + targets.SetSpeed(spell->m_targets.GetSpeed()); + if (spell->m_targets.GetElevation() != 0) + targets.SetElevation(spell->m_targets.GetElevation()); + if (spell->m_targets.GetUnitTargetGUID()) + { + if (Unit* target = ObjectAccessor::GetUnit(*veh->GetBase(), spell->m_targets.GetUnitTargetGUID())) + { + targets.SetUnitTarget(target); + veh->GetBase()->SetFacingTo(me->GetAbsoluteAngle(target)); + } + } + + //bug: gameobject damage is not sent to players (not visible in log) + //tempfix: set bot as original caster + Spell* vehspell = new Spell(veh->GetBase(), spellInfo, TRIGGERED_NONE/*, me->GetGUID()*/); + vehspell->prepare(targets); + } + } +} + +void bot_ai::OnBotChannelFinish(Spell const* spell) +{ + OnClassChannelFinish(spell); +} + +void bot_ai::OnBotSpellInterrupted(SpellSchoolMask schoolMask, uint32 unTimeMs) +{ + SpellInfo const* info; + + for (BotSpellMap::const_iterator itr = _spells.begin(); itr != _spells.end(); ++itr) + { + info = sSpellMgr->GetSpellInfo(itr->second->spellId); + if (!info || !(info->GetSchoolMask() & schoolMask)) continue; + if (info->IsCooldownStartedOnEvent()) continue; + if (info->PreventionType != SPELL_PREVENTION_TYPE_SILENCE) continue; + + if (HasBotCommandState(BOT_COMMAND_ISSUED_ORDER) && + !_orders.empty() && _orders.front()._type == BOT_ORDER_SPELLCAST && + _orders.front().params.spellCastParams.baseSpell == itr->first) + { + if (DEBUG_BOT_ORDERS) + TC_LOG_ERROR("entities.player", "doCast(): ordered spell {} was interrupted!", info->Id); + CompleteOrder(_orders.front()); + } + + itr->second->cooldown += unTimeMs; + //TC_LOG_ERROR("entities.player", "OnBotSpellInterrupted(): Adding cooldown ({}, new: {}) to spell {} (id: {}, schoolmask: {}), reqSchoolMask = {}", + // unTimeMs, itr->second.second, info->SpellName[0], info->Id, info->SchoolMask, schoolMask); + } + + GC_Timer = 0; //reset global cooldown since cast is canceled +} + +void bot_ai::CastBotItemCombatSpell(DamageInfo const& damageInfo) +{ + Unit* target = damageInfo.GetVictim(); + if (!target || !target->IsAlive() || target == me) + return; + + if (!me->CanUseAttackType(damageInfo.GetAttackType())) + return; + + Item* item; + ItemTemplate const* proto; + int8 slot; + + for (uint8 i = BOT_SLOT_MAINHAND; i != BOT_INVENTORY_SIZE; ++i) + { + item = _equips[i]; + if (!item) + continue; + + //skip standard items + //if (i < BOT_SLOT_RANGED && einfo->ItemEntry[i] == item->GetEntry()) + // continue; + + proto = item->GetTemplate(); + if (!proto) + continue; + + // Additional check for weapons + if (proto->Class == ITEM_CLASS_WEAPON) + { + // offhand item cannot proc from main hand hit etc + switch (damageInfo.GetAttackType()) + { + case BASE_ATTACK: slot = BOT_SLOT_MAINHAND; break; + case OFF_ATTACK: slot = BOT_SLOT_OFFHAND; break; + case RANGED_ATTACK: slot = BOT_SLOT_RANGED; break; + default: slot = -1; break; + } + if (slot != i) + continue; + } + + CastBotItemCombatSpell(damageInfo, item, proto); + } +} + +void bot_ai::CastBotItemCombatSpell(DamageInfo const& damageInfo, Item* item, ItemTemplate const* proto) +{ + //TODO: custom spell triggers maybe? + + // Can do effect if any damage done to target + bool canTrigger = (damageInfo.GetHitMask() & (PROC_HIT_NORMAL | PROC_HIT_CRITICAL | PROC_HIT_ABSORB)) != 0; + if (canTrigger) + { + for (uint8 i = 0; i != MAX_ITEM_PROTO_SPELLS; ++i) + { + _Spell const& spellData = proto->Spells[i]; + + // no spell + if (!spellData.SpellId) + continue; + + // wrong triggering type + if (spellData.SpellTrigger != ITEM_SPELLTRIGGER_CHANCE_ON_HIT) + continue; + + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellData.SpellId); + if (!spellInfo) + { + //TC_LOG_ERROR("entities.player.items", "WORLD: unknown Item spellid {}", spellData.SpellId); + continue; + } + + float chance = (float)spellInfo->ProcChance; + + if (spellData.SpellPPMRate) + { + uint32 WeaponSpeed = me->GetAttackTime(damageInfo.GetAttackType()); + chance = me->GetPPMProcChance(WeaponSpeed, spellData.SpellPPMRate, spellInfo); + } + else if (chance > 100.0f) + chance = me->GetWeaponProcChance(); + + if (roll_chance_f(chance)) + { + CastSpellExtraArgs args(item); + me->CastSpell(damageInfo.GetVictim(), spellInfo->Id, args); + } + } + } + + // item combat enchantments + for (uint8 e_slot = 0; e_slot != MAX_ENCHANTMENT_SLOT; ++e_slot) + { + uint32 enchant_id = item->GetEnchantmentId(EnchantmentSlot(e_slot)); + SpellItemEnchantmentEntry const* pEnchant = sSpellItemEnchantmentStore.LookupEntry(enchant_id); + if (!pEnchant) + continue; + + for (uint8 s = 0; s != MAX_ITEM_ENCHANTMENT_EFFECTS; ++s) + { + if (pEnchant->Effect[s] != ITEM_ENCHANTMENT_TYPE_COMBAT_SPELL) + continue; + + SpellEnchantProcEntry const* entry = sSpellMgr->GetSpellEnchantProcEvent(enchant_id); + if (entry && entry->HitMask) + { + // Check hit/crit/dodge/parry requirement + if ((entry->HitMask & damageInfo.GetHitMask()) == 0) + continue; + } + else + { + // Can do effect if any damage done to target + if (!canTrigger) + continue; + } + + // check if enchant procs only on white hits + if (entry && (entry->AttributesMask & ENCHANT_PROC_ATTR_WHITE_HIT) && damageInfo.GetSpellInfo()) + continue; + + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(pEnchant->EffectArg[s]); + if (!spellInfo) + continue; + + float chance = pEnchant->EffectPointsMin[s] != 0 ? float(pEnchant->EffectPointsMin[s]) : me->GetWeaponProcChance(); + if (entry) + { + if (entry->ProcsPerMinute) + { + chance = entry->ProcsPerMinute; + //handle SPELLMOD_PROC_PER_MINUTE spellmods + //Envenom + if (GetSpec() == BOT_SPEC_ROGUE_ASSASINATION && me->HasAuraTypeWithFamilyFlags(SPELL_AURA_ADD_FLAT_MODIFIER, SPELLFAMILY_ROGUE, 0x800000)) + chance *= 1.75f; + //Improved Poisons + if (GetSpec() == BOT_SPEC_ROGUE_ASSASINATION && me->GetLevel() >= 25 && (spellInfo->SpellFamilyFlags[0] & 0x2000)) + chance *= 1.5f; + + chance = me->GetPPMProcChance(proto->Delay, chance, spellInfo); + } + else if (entry->Chance) + chance = (float)entry->Chance; + } + + // Apply SPELLMOD_CHANCE_OF_SUCCESS spell mods + //Envenom + if (GetSpec() == BOT_SPEC_ROGUE_ASSASINATION && me->HasAuraTypeWithFamilyFlags(SPELL_AURA_ADD_FLAT_MODIFIER, SPELLFAMILY_ROGUE, 0x800000)) + chance += 15.f; + //Improved Poisons + if (GetSpec() == BOT_SPEC_ROGUE_ASSASINATION && me->GetLevel() >= 25 && (spellInfo->SpellFamilyFlags[0] & 0x10000)) + chance += 20.f; + + // Shiv has 100% chance to apply the poison + if (me->FindCurrentSpellBySpellId(5938) && e_slot == TEMP_ENCHANTMENT_SLOT) + chance = 100.0f; + + if (roll_chance_f(chance)) + { + CastSpellExtraArgs args(item); + me->CastSpell(spellInfo->IsPositive() ? me : damageInfo.GetVictim(), spellInfo->Id, args); + } + } + } +} +//ORDERS +bool bot_ai::AddOrder(BotOrder&& order) +{ + if (_orders.size() >= MAX_BOT_ORDERS_QUEUE_SIZE) + { + TC_LOG_ERROR("scripts", "bot_ai::AddOrder: orders limit reached for {} ({})!", me->GetName(), uint32(_orders.size())); + return false; + } + + _orders.push(std::move(order)); + return true; +} +void bot_ai::CancelOrder(BotOrder const& order) +{ + if (_orders.empty()) + { + TC_LOG_ERROR("scripts", "bot_ai::CancelOrder: {} orders are empty while trying to remove order type {}!", + me->GetName(), uint32(order._type)); + return; + } + if (_orders.front()._type != order._type) + { + TC_LOG_ERROR("scripts", "bot_ai::CancelOrder: {} front order (type {}) is different from cur order (type {})!", + me->GetName(), uint32(_orders.front()._type), uint32(order._type)); + return; + } + + RemoveBotCommandState(BOT_COMMAND_ISSUED_ORDER); + _orders.pop(); +} +void bot_ai::CompleteOrder(BotOrder const& order) +{ + if (_orders.empty()) + { + TC_LOG_ERROR("scripts", "bot_ai::CompleteOrder: {} orders are empty while trying to remove order type {}!", + me->GetName(), uint32(order._type)); + return; + } + if (_orders.front()._type != order._type) + { + TC_LOG_ERROR("scripts", "bot_ai::CompleteOrder: {} front order (type {}) is different from cur order (type {})!", + me->GetName(), uint32(_orders.front()._type), uint32(order._type)); + return; + } + + RemoveBotCommandState(BOT_COMMAND_ISSUED_ORDER); + _orders.pop(); +} +void bot_ai::CancelAllOrders() +{ + RemoveBotCommandState(BOT_COMMAND_ISSUED_ORDER); + while (!_orders.empty()) + _orders.pop(); +} +void bot_ai::_ProcessOrders() +{ + ordersTimer = 500; + + while (!_orders.empty()) + { + BotOrder const& order = _orders.front(); + if (order._timeout <= time(0)) + { + if (DEBUG_BOT_ORDERS) + TC_LOG_DEBUG("npcbots", "bot_ai::_ProcessOrders: {} front order (type {}) expired...", me->GetName(), uint32(order._type)); + CancelOrder(order); + } + else if (order._type == BOT_ORDER_PULL && (!HasRole(BOT_ROLE_DPS) || me->IsInCombat() || !me->getAttackers().empty())) + CompleteOrder(order); + else + break; + } + + if (HasBotCommandState(BOT_COMMAND_ISSUED_ORDER)) + return; + + if (JumpingOrFalling()) + return; + + if (_orders.empty()) + return; + + BotOrder const& order = _orders.front(); + Unit* target = nullptr; + switch (order._type) + { + case BOT_ORDER_SPELLCAST: + { + if (CCed(me)) + break; + + SetBotCommandState(BOT_COMMAND_ISSUED_ORDER); + + ObjectGuid guid(order.params.spellCastParams.targetGuid); + if (guid == me->GetGUID()) + target = me; + else if (guid == master->GetGUID()) + target = master; + else if (guid != 0) + { + if (!IAmFree()) + target = master->GetBotMgr()->GetBot(guid); + if (!target) + target = ObjectAccessor::GetUnit(*me, guid); + } + else + { + TC_LOG_ERROR("scripts", "bot_ai:_ProcessOrders: invalid spellCastParams.targetGuid {}!", order.params.spellCastParams.targetGuid); + CancelOrder(order); + return; + } + + if (!target || !target->IsInWorld()) + { + TC_LOG_ERROR("scripts", "bot_ai:_ProcessOrders: target {} not found!", order.params.spellCastParams.targetGuid); + CancelOrder(order); + return; + } + + if (IsCasting()) + me->InterruptNonMeleeSpells(false); + + doCast(target, _spells[order.params.spellCastParams.baseSpell]->spellId); + break; + } + case BOT_ORDER_PULL: + { + if (me->GetVictim()) + break; + if (CCed(me)) + break; + + SetBotCommandState(BOT_COMMAND_ISSUED_ORDER); + + if (order.params.pullParams.targetGuid) + target = ObjectAccessor::GetUnit(*me, ObjectGuid(order.params.pullParams.targetGuid)); + else + { + TC_LOG_ERROR("scripts", "bot_ai:_ProcessOrders: invalid pullParams.targetGuid {}!", order.params.pullParams.targetGuid); + CancelOrder(order); + return; + } + + if (!target || !target->IsInWorld()) + { + TC_LOG_ERROR("scripts", "bot_ai:_ProcessOrders: target {} not found!", order.params.pullParams.targetGuid); + CancelOrder(order); + return; + } + if (!target->IsAlive() || target->IsInCombat() || !CanBotAttack(target)) + { + TC_LOG_ERROR("scripts", "bot_ai:_ProcessOrders: target {} cannot be pulled!", order.params.pullParams.targetGuid); + CancelOrder(order); + return; + } + break; + } + default: + TC_LOG_ERROR("scripts", "bot_ai:_ProcessOrders: invalid order type {}!", uint32(order._type)); + CancelOrder(order); + return; + } +} +bool bot_ai::IsLastOrder(BotOrderTypes order_type, uint32 param1, ObjectGuid guidparam1) const +{ + if (!_orders.empty()) + { + BotOrder const& order = _orders.front(); + if (order_type == order._type) + { + switch (order_type) + { + case BOT_ORDER_SPELLCAST: + if (!param1 || order.params.spellCastParams.baseSpell == param1) + return true; + break; + case BOT_ORDER_PULL: + if (!guidparam1 || order.params.pullParams.targetGuid == guidparam1.GetRawValue()) + return true; + break; + default: + TC_LOG_ERROR("scripts", "bot_ai:IsLastOrder: invalid order type {}!", uint32(order_type)); + break; + } + } + } + + return false; +} +//VEHICLES +//helpers +bool bot_ai::HasAuraTypeWithValueAtLeast(AuraType auratype, int32 minvalue, Unit const* unit) const +{ + if (!unit) + unit = me; + + Unit::AuraEffectList const& mTotalAuraList = unit->GetAuraEffectsByType(auratype); + for (Unit::AuraEffectList::const_iterator i = mTotalAuraList.begin(); i != mTotalAuraList.end(); ++i) + if ((*i)->GetAmount() >= minvalue) + return true; + + return false; +} +//strats - opponent is always valid +void bot_ai::DoSkytalonVehicleStrats(uint32 diff) +{ + if (GC_Timer > diff) + return; + + GC_Timer = 350; //at least this delay + + if (!CheckVehicleAttackTarget(curVehStrat)) + return; + + Creature* drake = me->GetVehicleCreatureBase(); + Unit* target = nullptr; + uint32 drakespell = 0; + uint32 drakePower = drake->GetPower(POWER_ENERGY); + + //finishers + uint8 finishComboPoints; + uint32 finishPower; + if (GetHealthPCT(drake) <= (40 + 40*drake->HasAuraType(SPELL_AURA_PERIODIC_DAMAGE)) || + HasAuraTypeWithValueAtLeast(SPELL_AURA_PERIODIC_DAMAGE, drake->GetMaxHealth() / 16, drake)) + { + finishComboPoints = 3; + finishPower = 25; + } + //if (HasRole(BOT_ROLE_HEAL)) + //{ + // finishComboPoints = 4; + // finishPower = 50; + //} + else + { + finishComboPoints = 4; + finishPower = 50; + } + + if (vehcomboPoints >= finishComboPoints && (Rand() < 75 + 40*(vehcomboPoints >= 5 || drakePower < finishPower))) + { + if (drakePower >= finishPower) + { + if (GetHealthPCT(drake) < 60 && !drake->HasAuraEffect(drake->m_spells[4], EFFECT_0)) + { + drakespell = drake->m_spells[4]; //flame shield + target = drake; + } + else if (HasRole(BOT_ROLE_HEAL)) + { + drakespell = drake->m_spells[3]; //life burst + bool cast = false; + //find damaged target + if (master->GetVehicle() && GetHealthPCT(master->GetVehicleBase()) < 90 && master->GetVehicleBase()->GetDistance(drake) < 60) + cast = true; + else if (GetHealthPCT(drake) < 90) + cast = true; + else + { + std::vector vec = BotMgr::GetAllGroupMembers(master); + cast = std::any_of(vec.cbegin(), vec.cend(), [drake = drake](Unit const* member) { + return drake->GetMap() == member->FindMap() && member->GetVehicle() && + member->GetVehicleBase()->GetHealthPct() < 90.0f && member->GetVehicleBase()->GetDistance(drake) < 60; + }); + } + if (cast) + target = drake; + } + else + { + drakespell = drake->m_spells[1]; //engulf in flames + target = opponent; + } + } + } + else if (drakePower >= 35) + { + if (HasRole(BOT_ROLE_DPS)) + { + drakespell = drake->m_spells[0]; //flame spike + target = opponent; + } + else if (HasRole(BOT_ROLE_HEAL)) + { + drakespell = drake->m_spells[2]; //revivify + //bots won't care about target they have cp on so choose randomly + std::list targets1; + BotMap const* map; + Group const* gr = master->GetGroup(); + if (gr) + { + bool Bots = false; + for (GroupReference const* itr = gr->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player const* p = itr->GetSource(); + if (!p || me->GetMap() != p->FindMap()) continue; + if (p->HaveBot() && !Bots) + Bots = true; + Unit* u = p->GetVehicleBase(); + if (u && u->IsAlive() && !u->HasUnitState(UNIT_STATE_ISOLATED) && drake->GetDistance(u) < 60.f && + !(GetHealthPCT(u) > 95 && !IsTank(p)) && + (GetHealthPCT(u) < 95 || (u->IsInCombat() && !u->getAttackers().empty()))) + targets1.push_back(u); + } + if (Bots) + { + for (GroupReference const* itr = gr->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player const* p = itr->GetSource(); + if (!p || me->GetMap() != p->FindMap() || !p->HaveBot()) continue; + + map = p->GetBotMgr()->GetBotMap(); + for (BotMap::const_iterator bitr = map->begin(); bitr != map->end(); ++bitr) + { + Unit* u = bitr->second ? bitr->second->GetVehicleBase() : nullptr; + if (u && u->IsAlive() && !u->HasUnitState(UNIT_STATE_ISOLATED) && drake->GetDistance(u) < 60.f && + !(GetHealthPCT(u) > 95 && !IsTank(bitr->second)) && + (GetHealthPCT(u) < 95 || (u->IsInCombat() && !u->getAttackers().empty()))) + targets1.push_back(u); + } + } + } + } + else + { + map = master->GetBotMgr()->GetBotMap(); + for (BotMap::const_iterator itr = map->begin(); itr != map->end(); ++itr) + { + Unit* u = itr->second ? itr->second->GetVehicleBase() : nullptr; + if (u && u->IsAlive() && !u->HasUnitState(UNIT_STATE_ISOLATED) && drake->GetDistance(u) < 60.f && + !(GetHealthPCT(u) > 95 && !IsTank(itr->second)) && + (GetHealthPCT(u) < 95 || (u->IsInCombat() && !u->getAttackers().empty()))) + targets1.push_back(u); + } + } + + uint8 minhppct = 0; + for (std::list::const_iterator ci = targets1.begin(); ci != targets1.end(); ++ci) + if (uint8 hppct = GetHealthPCT(*ci)) + if (minhppct == 0 || minhppct > hppct) + minhppct = hppct; + + if (minhppct <= 75) + targets1.remove_if(BOTAI_PRED::HpPctAboveExclude(float(minhppct + 10))); + + if (!targets1.empty()) + target = Trinity::Containers::SelectRandomContainerElement(targets1); + + if (target && drakePower < 80) + if (Aura const* revi = target->GetAura(57090, drake->GetGUID())) //revivify + if (revi->GetStackAmount() >= 5 && revi->GetDuration() >= 4000) + target = nullptr; + } + } + + //if (!HasRole(BOT_ROLE_TANK|BOT_ROLE_HEAL) && drake->GetMap()->IsRaid() && target && target->GetVictim() == drake) + // target = nullptr; + + if (!target) + return; + + if (!drakespell) + { + TC_LOG_ERROR("scripts", "DoSkytalonVehicleStrats no spell for role mask {} cp {}, power {}, target {}", + GetBotRoles(), uint32(vehcomboPoints), drakePower, target->GetName()); + return; + } + + //TC_LOG_ERROR("scripts", "DoSkytalonVehicleStrats {} on {}", drakespell, target->GetName()); + + SetBotCommandState(BOT_COMMAND_ATTACK); + drake->BotStopMovement(); + drake->SetInFront(target); + drake->CastSpell(target, drakespell); +} +void bot_ai::DoRubyDrakeVehicleStrats(uint32 diff) +{ + if (GC_Timer > diff) + return; + + GC_Timer = 350; //at least this delay + + if (!CheckVehicleAttackTarget(curVehStrat)) + return; + + Creature* drake = me->GetVehicleCreatureBase(); + ASSERT(drake); + Unit* target = nullptr; + uint32 drakespell = 0; + + //IS TANK + //50232 Searing Wrath + //50241 Evasive Charges + //50240 Evasive Maneuvers + //50253 Martyr + + Aura const* evas = drake->GetAura(50241); //Evasive Charges + uint8 vehicles = LivingVehiclesCount(); + //bool eregos = opponent->GetEntry() == CREATURE_BOSS_EREGOS_N || opponent->GetEntry() == CREATURE_BOSS_EREGOS_H; + + bool finalEncounter = master->GetInstanceScript() && master->GetInstanceScript()->GetBossState(2) == DONE; //DATA_UROM + + if (vehicles > 1 && evas) + { + bool canManeuver = !drake->HasAuraType(SPELL_AURA_MOD_ATTACKER_SPELL_HIT_CHANCE) && evas->GetStackAmount() >= 5 && + !drake->HasSpellCooldown(50240); + bool canMartyr = !drake->HasAuraType(SPELL_AURA_SPELL_MAGNET) && + !drake->HasSpellCooldown(50253); + + if ((!finalEncounter || canMartyr) && canManeuver) + { + drakespell = drake->m_spells[1]; //Evasive Maneuvers + target = drake; + } + else if (finalEncounter && canMartyr) + { + drakespell = drake->m_spells[2]; //Martyr + target = drake; + } + } + if (!target) + { + drakespell = drake->m_spells[0]; //Searing Wrath + target = opponent; + } + + if (!target) + return; + + if (!drakespell) + { + TC_LOG_ERROR("scripts", "DoRubyDrakeVehicleStrats no spell for target {}", target->GetName()); + return; + } + + SetBotCommandState(BOT_COMMAND_ATTACK); + drake->BotStopMovement(); + drake->SetInFront(target); + drake->CastSpell(target, drakespell); +} +void bot_ai::DoEmeraldDrakeVehicleStrats(uint32 diff) +{ + if (GC_Timer > diff) + return; + + GC_Timer = 350; //at least this delay + + Creature* drake = me->GetVehicleCreatureBase(); + Unit const* mmover = master->GetVehicle() ? master->GetVehicleBase() : master; + Unit* target = nullptr; + uint32 drakespell = 0; + + //IS HEALER + //50328 Leeching Poison + //50341 Touch the Nightmare + //50344 Dream Funnel + + uint8 drakeHpPct = GetHealthPCT(drake); + + //canceling channel + if (IsCasting(drake)) + { + bool interrupt = false; + if (drakeHpPct <= 30) + interrupt = true; + else if (mmover->isMoving() && drake->GetDistance(mmover) >= 75.f && !mmover->HasInArc(float(M_PI) / 2, drake)) + interrupt = true; + else if (Spell const* funnel = drake->GetCurrentSpell(CURRENT_CHANNELED_SPELL)) + if (ObjectGuid guid = funnel->m_targets.GetUnitTargetGUID()) + if (Unit const* tar = ObjectAccessor::GetUnit(*drake, guid)) + if (GetHealthPCT(tar) > 95) + interrupt = true; + + if (interrupt) + drake->InterruptNonMeleeSpells(false); + else + return; + } + + if (!CheckVehicleAttackTarget(curVehStrat)) + return; + + Aura const* pois = opponent->GetAura(50328, drake->GetGUID()); //Leeching Poison + Aura const* rift = opponent->GetAura(49592); //Temporal Rift + uint8 vehicles = LivingVehiclesCount(); + bool eregos = opponent->GetEntry() == CREATURE_BOSS_EREGOS_N || opponent->GetEntry() == CREATURE_BOSS_EREGOS_H; + + bool finalEncounter = master->GetInstanceScript() && master->GetInstanceScript()->GetBossState(2) == DONE; //DATA_UROM + bool canheal = finalEncounter && vehicles > 1 && (!eregos || drake->GetDistance(mmover) < 30.f) && + drakeHpPct >= (eregos ? 70 : 50) - (pois ? 5 * pois->GetStackAmount() : 0); + + if (canheal && Rand() < 90) + { + drakespell = drake->m_spells[2]; //Dream Funnel + std::list targets1; + BotMap const* map; + Group const* gr = master->GetGroup(); + if (gr) + { + bool Bots = false; + for (GroupReference const* itr = gr->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player const* p = itr->GetSource(); + if (!p || me->GetMap() != p->FindMap()) continue; + if (p->HaveBot() && !Bots) + Bots = true; + Unit* u = p->GetVehicleBase(); + if (u && u->IsAlive() && !u->HasUnitState(UNIT_STATE_ISOLATED) && drake->GetDistance(u) < 60.f && + GetHealthPCT(u) <= (IsTank(p) ? 50 : 35) + (rift ? 15 : 0)) + targets1.push_back(u); + } + if (Bots) + { + for (GroupReference const* itr = gr->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player const* p = itr->GetSource(); + if (!p || me->GetMap() != p->FindMap() || !p->HaveBot()) continue; + + map = p->GetBotMgr()->GetBotMap(); + for (BotMap::const_iterator bitr = map->begin(); bitr != map->end(); ++bitr) + { + Unit* u = bitr->second ? bitr->second->GetVehicleBase() : nullptr; + if (u && u != drake && u->IsAlive() && !u->HasUnitState(UNIT_STATE_ISOLATED) && drake->GetDistance(u) < 60.f && + GetHealthPCT(u) <= (IsTank(bitr->second) ? 50 : 35) + (rift ? 15 : 0)) + targets1.push_back(u); + } + } + } + } + else + { + map = master->GetBotMgr()->GetBotMap(); + for (BotMap::const_iterator itr = map->begin(); itr != map->end(); ++itr) + { + Unit* u = itr->second ? itr->second->GetVehicleBase() : nullptr; + if (u && u != drake && u->IsAlive() && !u->HasUnitState(UNIT_STATE_ISOLATED) && drake->GetDistance(u) < 60.f && + GetHealthPCT(u) <= (IsTank(itr->second) ? 50 : 35) + (rift ? 15 : 0)) + targets1.push_back(u); + } + } + + uint8 minhppct = 0; + for (std::list::const_iterator ci = targets1.begin(); ci != targets1.end(); ++ci) + if (uint8 hppct = GetHealthPCT(*ci)) + if (minhppct == 0 || minhppct > hppct) + minhppct = hppct; + + if (minhppct <= 50) + targets1.remove_if(BOTAI_PRED::HpPctAboveExclude(float(minhppct + 20))); + + if (!targets1.empty()) + target = Trinity::Containers::SelectRandomContainerElement(targets1); + } + if (!target) + { + drakespell = drake->m_spells[1]; //Touch the Nightmare + if (!drake->HasSpellCooldown(drakespell)) + { + if ((eregos && drakeHpPct >= ((opponent->GetHealth() < (rift ? 50000u : 25000u)) ? 31 : 80)) || + (drakeHpPct >= 90 && Rand() < 50 && !opponent->HasAura(drakespell))) + target = opponent; + } + } + if (!target) + { + drakespell = drake->m_spells[0]; //Leeching Poison + if (!pois || pois->GetStackAmount() < 3 || pois->GetDuration() < 4000) + target = opponent; + else + { + //random target + std::list targets; + Trinity::AnyUnfriendlyUnitInObjectRangeCheck check(drake, drake, 60.f); + Trinity::UnitListSearcher searcher(drake, targets, check); + //drake->VisitNearbyObject(60.f, searcher); + Cell::VisitAllObjects(drake, searcher, 60.f); + targets.remove_if(BOTAI_PRED::UnitExclude(opponent)); + targets.remove_if(BOTAI_PRED::UnitCombatStateExclude(false)); + targets.remove_if(BOTAI_PRED::AuraedTargetExcludeByCaster(drakespell, drake->GetGUID(), 3)); + targets.remove_if(BOTAI_PRED::AuraedTargetExclude(49836, 5)); //Shock Charge 1-shots + + if (!targets.empty()) + target = Trinity::Containers::SelectRandomContainerElement(targets); + } + } + + if (!target) + return; + + if (!drakespell) + { + TC_LOG_ERROR("scripts", "DoEmeraldDrakeVehicleStrats no spell for target {}", target->GetName()); + return; + } + + SetBotCommandState(BOT_COMMAND_ATTACK); + drake->BotStopMovement(); + drake->SetInFront(target); + drake->CastSpell(target, drakespell); +} +void bot_ai::DoAmberDrakeVehicleStrats(uint32 diff) +{ + if (GC_Timer > diff) + return; + + GC_Timer = 350; //at least this delay + + if (!CheckVehicleAttackTarget(curVehStrat)) + return; + + Creature* drake = me->GetVehicleCreatureBase(); + ASSERT(drake); + Unit const* mmover = master->GetVehicle() ? master->GetVehicleBase() : master; + Unit* target = nullptr; + uint32 drakespell = 0; + + //IS DPS + //49840 Shock Lance + //49838 Stop Time + //49592 Temporal Rift + //49836 Shock Charge + + Aura const* shoc = opponent->GetAura(49836, drake->GetGUID()); //Shock Charge + + //canceling channel + if (IsCasting(drake)) + { + bool interrupt = false; + + if (mmover->isMoving() && drake->GetDistance(mmover) >= 60.f && !mmover->HasInArc(float(M_PI) / 2, drake)) + interrupt = true; + else if (shoc && shoc->GetStackAmount() >= 10) + interrupt = true; + + if (interrupt) + drake->InterruptNonMeleeSpells(false); + else + return; + } + + Aura const* rift = opponent->GetAura(49592); //Temporal Rift + uint8 vehicles = LivingVehiclesCount(); + bool eregos = opponent->GetEntry() == CREATURE_BOSS_EREGOS_N || opponent->GetEntry() == CREATURE_BOSS_EREGOS_H; + + bool finalEncounter = master->GetInstanceScript() && master->GetInstanceScript()->GetBossState(2) == DONE; //DATA_UROM + + if (eregos && !drake->HasSpellCooldown(drake->m_spells[1]) && IsCasting(opponent) && + opponent->HasAuraType(SPELL_AURA_MOD_CASTING_SPEED_NOT_STACK) && opponent->GetDiminishing(DIMINISHING_STUN) <= DIMINISHING_LEVEL_2) + { + drakespell = drake->m_spells[1]; //Stop Time + target = drake; + } + if (!target && shoc && shoc->GetStackAmount() >= 5) + { + drakespell = drake->m_spells[0]; //Shock Lance + if (eregos && shoc->GetStackAmount() >= 10 && (rift || shoc->GetDuration() < 25000 || Rand() < 30)) + target = opponent; + else + { + //random 1-shot target + std::list targets; + Trinity::AnyUnfriendlyUnitInObjectRangeCheck check(drake, drake, 60.f); + Trinity::UnitListSearcher searcher(drake, targets, check); + //drake->VisitNearbyObject(60.f, searcher); + Cell::VisitAllObjects(drake, searcher, 60.f); + targets.remove_if(BOTAI_PRED::UnitExclude(opponent)); + + if (!targets.empty()) + target = Trinity::Containers::SelectRandomContainerElement(targets); + } + } + if (!target) + { + if (finalEncounter && vehicles > 1) + { + drakespell = drake->m_spells[2]; //Temporal Rift + target = opponent; + } + else + { + drakespell = drake->m_spells[0]; //Shock Lance + target = opponent; + } + } + + if (!target) + return; + + if (!drakespell) + { + TC_LOG_ERROR("scripts", "DoAmberDrakeVehicleStrats no spell for target {}", target->GetName()); + return; + } + + SetBotCommandState(BOT_COMMAND_ATTACK); + drake->BotStopMovement(); + drake->SetInFront(target); + drake->CastSpell(target, drakespell); +} +void bot_ai::DoArgentMountVehicleStrats(uint32 diff) +{ + if (GC_Timer > diff) + return; + + GC_Timer = 200; //at least this delay + + Creature* mount = me->GetVehicleCreatureBase(); + + //Tounament spells + //62544 Thrust + //62575 Shield-Breaker + //62960 Charge + //62552 Defend + //64077 Refresh Mount + + //ToC5 spells + //68505 Thrust + //62575 Shield-Breaker + //68282 Charge + //62552 Defend + + if (!mount->HasSpellCooldown(mount->m_spells[3])) //Defend + { + Aura const* myde = mount->GetAura(mount->m_spells[3]); + if (!myde || myde->GetStackAmount() < myde->GetSpellInfo()->StackAmount || myde->GetDuration() <= 8000) + { + mount->CastSpell(mount, mount->m_spells[3]); + return; + } + } + + if (!CheckVehicleAttackTarget(curVehStrat)) + return; + + //Unit const* mmover = master->GetVehicle() ? master->GetVehicleBase() : master; + Unit* target = nullptr; + uint32 mountspell = 0; + + if (mount->GetDistance(opponent) > 5.f) + { + //Defend + AuraEffect const* def = opponent->GetAuraEffect(SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN,SPELLFAMILY_GENERIC, 2007, EFFECT_0); + if (!def) + def = opponent->GetAuraEffect(SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN,SPELLFAMILY_GENERIC, 2007, EFFECT_2); + if ((!def || def->GetBase()->GetStackAmount() < 2) && !mount->HasSpellCooldown(mount->m_spells[2])) + { + mountspell = mount->m_spells[2]; //Charge + target = opponent; + } + else + { + mountspell = mount->m_spells[1]; //Shield-Breaker + target = opponent; + } + } + else + { + mountspell = mount->m_spells[0]; //Thrust + target = opponent; + } + + if (!target) + return; + + if (!mountspell) + { + TC_LOG_ERROR("scripts", "DoArgentMountVehicleStrats no spell for target {}", target->GetName()); + return; + } + + SetBotCommandState(BOT_COMMAND_ATTACK); + mount->BotStopMovement(); + mount->SetInFront(target); + mount->CastSpell(target, mountspell); +} +void bot_ai::DoDemolisherVehicleStrats(uint32 diff) +{ + if (GC_Timer > diff) + return; + + GC_Timer = 350; //at least this delay + + if (!CheckVehicleAttackTarget(curVehStrat)) + return; +} +void bot_ai::DoSiegeEngineVehicleStrats(uint32 diff) +{ + if (GC_Timer > diff) + return; + + GC_Timer = 350; //at least this delay + + if (!CheckVehicleAttackTarget(curVehStrat)) + return; +} +void bot_ai::DoChopperVehicleStrats(uint32 diff) +{ + if (GC_Timer > diff) + return; + + GC_Timer = 350; //at least this delay + + if (!CheckVehicleAttackTarget(curVehStrat)) + return; +} +void bot_ai::DoGenericVehicleStrats(uint32 diff) +{ + if (GC_Timer > diff) + return; + + GC_Timer = 350; //at least this delay + + if (!CheckVehicleAttackTarget(curVehStrat)) + return; +} +void bot_ai::DoVehicleStrats(BotVehicleStrats strat, uint32 diff) +{ + if (curVehStrat != strat) + { + //TC_LOG_ERROR("scripts", "DoVehicleStrats doing strat {}", uint32(strat)); + curVehStrat = strat; + } + + //if (!master->GetVehicle() || me->GetVehicle()->GetCreatureEntry() != master->GetVehicle()->GetCreatureEntry()) + //{ + // TC_LOG_ERROR("scripts", "DoVehicleStrats cannot do strats: master not on vehicle or on different one"); + // return; + //} + + switch (strat) + { + case BOT_VEH_STRAT_WYRMREST_SKYTALON: + DoSkytalonVehicleStrats(diff); + break; + case BOT_VEH_STRAT_RUBY_DRAKE: + DoRubyDrakeVehicleStrats(diff); + break; + case BOT_VEH_STRAT_EMERALD_DRAKE: + DoEmeraldDrakeVehicleStrats(diff); + break; + case BOT_VEH_STRAT_AMBER_DRAKE: + DoAmberDrakeVehicleStrats(diff); + break; + case BOT_VEH_STRAT_TOC5_MOUNT: + DoArgentMountVehicleStrats(diff); + break; + case BOT_VEH_STRAT_ULDUAR_DEMOLISHER: + DoDemolisherVehicleStrats(diff); + break; + case BOT_VEH_STRAT_ULDUAR_SIEGEENGINE: + DoSiegeEngineVehicleStrats(diff); + break; + case BOT_VEH_STRAT_ULDUAR_CHOPPER: + DoChopperVehicleStrats(diff); + break; + case BOT_VEH_STRAT_GENERIC: + DoGenericVehicleStrats(diff); + break; + default: + TC_LOG_ERROR("scripts", "Unhandled vehicle strat {}", uint32(strat)); + break; + } +} +void bot_ai::DoVehicleActions(uint32 diff) +{ + if (!me->GetVehicle()) + return; + + //choose strat + BotVehicleStrats strat; + switch (me->GetVehicleBase()->GetEntry()) + { + case CREATURE_NEXUS_SKYTALON_1: + case CREATURE_EOE_SKYTALON_N: + case CREATURE_EOE_SKYTALON_H: + strat = BOT_VEH_STRAT_WYRMREST_SKYTALON; + break; + case CREATURE_OCULUS_DRAKE_RUBY: + strat = BOT_VEH_STRAT_RUBY_DRAKE; + break; + case CREATURE_OCULUS_DRAKE_EMERALD: + strat = BOT_VEH_STRAT_EMERALD_DRAKE; + break; + case CREATURE_OCULUS_DRAKE_AMBER: + strat = BOT_VEH_STRAT_AMBER_DRAKE; + break; + //case CREATURE_TOC_STEED_QUELDOREI: + //case CREATURE_TOC_NIGHTSABER: + //case CREATURE_TOC_STEED_STORMWIND: + //case CREATURE_TOC_MECHANOSTRIDER: + //case CREATURE_TOC_RAM: + //case CREATURE_TOC_ELEKK: + //case CREATURE_TOC_HAWKSTRIDER_SUNREAVER: + //case CREATURE_TOC_RAPTOR: + //case CREATURE_TOC_WARHORSE: + //case CREATURE_TOC_WOLF: + //case CREATURE_TOC_HAWKSTRIDER_SILVERMOON: + //case CREATURE_TOC_KODO: + case CREATURE_TOC5_WARHORSE: + case CREATURE_TOC5_BATTLEWORG: + strat = BOT_VEH_STRAT_TOC5_MOUNT; + break; + /* + case CREATURE_ULDUAR_DEMOLISHER: + strat = BOT_VEH_STRAT_ULDUAR_DEMOLISHER; + break; + case CREATURE_ULDUAR_SIEGE_ENGINE: + strat = BOT_VEH_STRAT_ULDUAR_SIEGEENGINE; + break; + case CREATURE_ULDUAR_CHOPPER: + case CREATURE_ULDUAR_CHOPPER1: + strat = BOT_VEH_STRAT_ULDUAR_CHOPPER; + break; + */ + default: + strat = BOT_VEH_STRAT_GENERIC; + if (curVehStrat != strat) + TC_LOG_DEBUG("scripts", "bot_ai DoVehicleActions: {} has to use generic strat for vehicle creature {} ({})", + me->GetName(), me->GetVehicleBase()->GetName(), me->GetVehicleBase()->GetEntry()); + break; + } + + DoVehicleStrats(strat, diff); +} +bool bot_ai::CheckVehicleAttackTarget(BotVehicleStrats strat) +{ + opponent = _getVehicleTarget(strat); + + if (!opponent) + { + if (me->GetVehicleBase()->GetTarget()) + { + me->GetVehicleBase()->AttackStop(); + me->GetVehicleBase()->SetTarget(ObjectGuid::Empty); + } + else if (!master->GetVehicle() && me->GetVehicleBase()->GetDistance(master) < ((Rand() < 25) ? 35 : 15)) + { + //if (VehicleSeatEntry const* seat = me->GetVehicle()->GetSeatForPassenger(me)) + //{ + //if (seat->CanEnterOrExit()) + me->ExitVehicle(); + //me->BotStopMovement(); + //} + } + + return false; + } + + if (opponent->GetGUID() != me->GetVehicleBase()->GetTarget()) + { + me->GetVehicleBase()->SetTarget(opponent->GetGUID()); + SetBotCommandState(BOT_COMMAND_ATTACK); + me->GetVehicleBase()->Attack(opponent, false); + } + + return true; +} + +bool bot_ai::HasVehicleRoleOverride(uint32 role) const +{ + if (Creature const* veh = me->GetVehicleCreatureBase()) + { + switch (veh->GetEntry()) + { + case CREATURE_NEXUS_SKYTALON_1: + case CREATURE_EOE_SKYTALON_N: + case CREATURE_EOE_SKYTALON_H: + case CREATURE_OCULUS_DRAKE_RUBY: + case CREATURE_OCULUS_DRAKE_EMERALD: + case CREATURE_OCULUS_DRAKE_AMBER: + //case CREATURE_TOC_STEED_QUELDOREI: + //case CREATURE_TOC_NIGHTSABER: + //case CREATURE_TOC_STEED_STORMWIND: + //case CREATURE_TOC_MECHANOSTRIDER: + //case CREATURE_TOC_RAM: + //case CREATURE_TOC_ELEKK: + //case CREATURE_TOC_HAWKSTRIDER_SUNREAVER: + //case CREATURE_TOC_RAPTOR: + //case CREATURE_TOC_WARHORSE: + //case CREATURE_TOC_WOLF: + //case CREATURE_TOC_HAWKSTRIDER_SILVERMOON: + //case CREATURE_TOC_KODO: + case CREATURE_TOC5_WARHORSE: + case CREATURE_TOC5_BATTLEWORG: + case CREATURE_ULDUAR_DEMOLISHER: + switch (role) + { + case BOT_ROLE_RANGED: + return true; + default: + break; + } + break; + default: + break; + } + } + + return false; +} +float bot_ai::GetVehicleAttackDistanceOverride() const +{ + if (Creature const* veh = me->GetVehicleCreatureBase()) + { + switch (veh->GetEntry()) + { + case CREATURE_NEXUS_SKYTALON_1: + case CREATURE_EOE_SKYTALON_N: + case CREATURE_EOE_SKYTALON_H: + case CREATURE_OCULUS_DRAKE_RUBY: + case CREATURE_OCULUS_DRAKE_EMERALD: + case CREATURE_OCULUS_DRAKE_AMBER: + return 30.f; + //case CREATURE_TOC_STEED_QUELDOREI: + //case CREATURE_TOC_NIGHTSABER: + //case CREATURE_TOC_STEED_STORMWIND: + //case CREATURE_TOC_MECHANOSTRIDER: + //case CREATURE_TOC_RAM: + //case CREATURE_TOC_ELEKK: + //case CREATURE_TOC_HAWKSTRIDER_SUNREAVER: + //case CREATURE_TOC_RAPTOR: + //case CREATURE_TOC_WARHORSE: + //case CREATURE_TOC_WOLF: + //case CREATURE_TOC_HAWKSTRIDER_SILVERMOON: + //case CREATURE_TOC_KODO: + case CREATURE_TOC5_WARHORSE: + case CREATURE_TOC5_BATTLEWORG: + return 15.f; + case CREATURE_ULDUAR_DEMOLISHER: + return 40.f; + default: + break; + } + } + + return 0.0f; +} +uint8 bot_ai::LivingVehiclesCount(uint32 entry) const +{ + if (IAmFree()) + return 0; + + uint8 count = 0; + if (master->GetVehicle()) + ++count; + BotMap const* map = master->GetBotMgr()->GetBotMap(); + for (BotMap::const_iterator itr = map->begin(); itr != map->end(); ++itr) + if (itr->second && itr->second->GetVehicle() && (!entry || entry == itr->second->GetVehicleBase()->GetEntry())) + ++count; + + return count; +} +//GLOBAL UPDATE +void bot_ai::UpdateDeadAI(uint32 diff) +{ + // group update + if (_groupUpdateTimer <= diff) + SendUpdateToOutOfRangeBotGroupMembers(); + + // soulstone + if (GetSelfRezSpell() && (IAmFree() || !master->GetBotMgr()->IsPartyInCombat()) && Rand() < 15) + me->CastSpell(me, GetSelfRezSpell()); +} +//opponent unsafe +bool bot_ai::GlobalUpdate(uint32 diff) +{ + if (!BotMgr::IsNpcBotModEnabled() || !BotDataMgr::AllBotsLoaded()) + return false; + + if (IsWanderer()) + { + if (Battleground* bg = GetBG()) + { + if (bg->GetStatus() == STATUS_WAIT_LEAVE) + { + if (std::find_if(bg->GetPlayers().cbegin(), bg->GetPlayers().cend(), [](auto const& kv) { return kv.first.IsPlayer(); }) == bg->GetPlayers().cend()) + bg->RemoveBotAtLeave(me->GetGUID()); + return false; + } + } + } + else + { + if (_checkOwershipTimer && _checkOwershipTimer <= diff) + { + if (IAmFree()) + { + NpcBotData const* npcBotData = BotDataMgr::SelectNpcBotData(me->GetEntry()); + if (npcBotData->owner != 0) + { + CheckOwnerExpiry(); + if (npcBotData->owner == 0) + { + _checkOwershipTimer = 0; + return false; + } + } + } + _checkOwershipTimer = CalculateOwnershipCheckTime(); + } + } + + //db saves with cd + // 1) disabled spells + if (_saveDisabledSpells && _saveDisabledSpellsTimer <= diff) + { + _saveDisabledSpells = false; + _saveDisabledSpellsTimer = 5000; + + if (!IsTempBot()) + { + NpcBotData* npcBotData = const_cast(BotDataMgr::SelectNpcBotData(me->GetEntry())); + BotDataMgr::UpdateNpcBotData(me->GetEntry(), NPCBOT_UPDATE_DISABLED_SPELLS, &npcBotData->disabled_spells); + } + } + + if (_updateTimerEx2 <= diff) + { + _updateTimerEx2 = urand(2000, 4000); + + if (BotMgr::HideBotSpawns() && IAmFree() && !IsWanderer()) + { + // !!bot may be out of world!! + Map* mymap = me->FindMap(); + if (mymap) + { + std::list plist; + Trinity::AnyPlayerInPositionRangeCheck pcheck(me, 15.0f, false); + Trinity::PlayerListSearcher searcher(me, plist, pcheck); + Cell::VisitWorldObjects(me, searcher, 20.f); + _canAppearInWorld = std::any_of(plist.cbegin(), plist.cend(), [](Player const* pl) { return pl->GetSession()->GetSecurity() > SEC_PLAYER; }); + if (!CanAppearInWorld() && !IsDuringTeleport()) + BotMgr::TeleportBot(me, mymap, me, true); + } + else + { + _canAppearInWorld = false; + TC_LOG_ERROR("npcbots", "Bot {} tried to check hide status but doesn't have a valid map set", me->GetEntry()); + } + } + else + _canAppearInWorld = true; + } + + ReduceCD(diff); + + UpdateContestedPvP(); + + lastdiff = diff; + + FindMaster(); + + if (IsDuringTeleport()) + return false; + + if (_updateTimerMedium <= diff) + { + _updateTimerMedium = 500; + + //Medium-timed updates + + //send stats update for group frames + if (me->IsInWorld() && !IAmFree()) + { + //update pvp state + if (me->GetByteValue(UNIT_FIELD_BYTES_2, 1) != master->GetByteValue(UNIT_FIELD_BYTES_2, 1)) + me->SetByteValue(UNIT_FIELD_BYTES_2, 1, master->GetByteValue(UNIT_FIELD_BYTES_2, 1)); + + //vehicle enter delayed + if (shouldEnterVehicle && master->IsInWorld() && me->IsAlive() &&/* !HasBotCommandState(BOT_COMMAND_MASK_UNMOVING) &&*/ + !me->GetVehicle() && master->GetVehicle() && !CCed(me, true) && !IsCasting()) + { + shouldEnterVehicle = false; + AfterBotOwnerEnterVehicle(); + } + + //gossip availability check + if (HasBotCommandState(BOT_COMMAND_NOGOSSIP) && me->HasNpcFlag(UNIT_NPC_FLAG_GOSSIP)) + me->RemoveNpcFlag(UNIT_NPC_FLAG_GOSSIP); + } + + if (me->IsInWorld() && me->IsAlive() && IAmFree()) + { + if (me->HasAuraType(SPELL_AURA_MOUNTED) && IsIndoors() && sWorld->getBoolConfig(CONFIG_VMAP_INDOOR_CHECK)) + me->RemoveAurasWithAttribute(SPELL_ATTR0_OUTDOORS_ONLY); + } + } + + if (!me->IsAlive()) + return false; + + if (!me->IsInWorld()) + { + if (IAmFree()) + TC_LOG_ERROR("scripts", "bot_ai::GlobalUpdate is called for free bot not in world: {} ({}) class {} level {}", + me->GetName(), me->GetEntry(), uint32(_botclass), uint32(me->GetLevel())); + return false; + } + + if (doHealth) + { + doHealth = false; + _OnHealthUpdate(); + } + if (doMana) + { + doMana = false; + _OnManaUpdate(); + } + + // group update + if (_groupUpdateTimer <= diff) + SendUpdateToOutOfRangeBotGroupMembers(); + + if (ordersTimer <= diff) + _ProcessOrders(); + + //if (me->HasInvisibilityAura() || me->HasStealthAura()) + // return false; + + //Check current cast state: interrupt casts that became pointless + if (me->HasUnitState(UNIT_STATE_CASTING) && !HasBotCommandState(BOT_COMMAND_ISSUED_ORDER) && urand(1,100) <= 75) + { + bool interrupt; + Unit const* target; + for (uint8 i = CURRENT_FIRST_NON_MELEE_SPELL; i != CURRENT_MAX_SPELL; ++i) + { + interrupt = false; + Spell* spell = me->GetCurrentSpell(CurrentSpellTypes(i)); + if (!spell) + continue; + + SpellInfo const* info = spell->GetSpellInfo(); + if (!info->CastTimeEntry) + continue; + + if (info->Id == SHOOT_WAND && me->isMoving()) + interrupt = true; + else + { + // not interrupted yet, next checks require target, ensure validity + // kidna expensive but prevents invalid targets + if (spell->m_targets.GetObjectTargetGUID().IsAnyTypeCreature()) + spell->m_targets.Update(me); + target = spell->m_targets.GetUnitTarget(); + if (!target) + continue; + } + + if (!interrupt && !info->IsPositive()) + { + if (!target->IsAlive() && info->Id != SPELL_CORPSE_EXPLOSION && info->Id != SPELL_RAISE_DEAD) + interrupt = true; + else if ((info->Mechanic == MECHANIC_POLYMORPH || info->Mechanic == MECHANIC_SHACKLE || + info->Mechanic == MECHANIC_DISORIENTED || info->Mechanic == MECHANIC_SLEEP || + info->Mechanic == MECHANIC_CHARM || info->Mechanic == MECHANIC_BANISH || + info->Mechanic == MECHANIC_STUN || info->Mechanic == MECHANIC_FREEZE) && + info->GetFirstRankSpell()->Id != 710 && info->GetFirstRankSpell()->Id != SPELL_SLEEP && + !target->getAttackers().empty() && !IsCasting(target)) + interrupt = true; //useless control (except banish, checked inside class ai) + else if (target->HasAuraType(SPELL_AURA_PERIODIC_DAMAGE) && !IsCasting(target) && + (info->AuraInterruptFlags & AURA_INTERRUPT_FLAG_TAKE_DAMAGE) && _botclass != BOT_CLASS_MAGE) + interrupt = true; //useless control breaks immediately (skip glyphed poly) + else if (info->DmgClass == SPELL_DAMAGE_CLASS_MAGIC && !(info->Attributes & SPELL_ATTR0_ABILITY) && + !(info->AttributesEx & SPELL_ATTR1_CANT_BE_REFLECTED) && + !(info->Attributes & SPELL_ATTR0_UNAFFECTED_BY_INVULNERABILITY) && + (target->GetTotalAuraModifier(SPELL_AURA_REFLECT_SPELLS) > 60 || + target->GetTotalAuraModifierByMiscMask(SPELL_AURA_REFLECT_SPELLS_SCHOOL, info->GetSchoolMask()) > 60)) + interrupt = true; // reflect + else if ( + info->Id != 64382 && //shattering throw + info->Id != 32375 && //mass dispel + info->GetFirstRankSpell()->Id != 710 && //banish + target->IsImmunedToSpell(info, me)) + interrupt = true; // immune + else if (i != CURRENT_CHANNELED_SPELL && //channeled targeted spells will be interrupted in Spell::update() + spell->GetCastTime() < spell->GetTimer() * 3 && // >=33% cast time remains + !me->IsWithinLOSInMap(target, LINEOFSIGHT_ALL_CHECKS, VMAP::ModelIgnoreFlags::M2)) + interrupt = true; //LoS + else if (info->Id == 64382 && !target->HasAuraWithMechanic(1<GetDistance(target) < INTERACTION_DISTANCE) && + info->HasEffect(SPELL_EFFECT_HEAL) && GetHealthPCT(target) > 90 && !IsPointedHealTarget(target)) + { + bool isAreaSpell = false; + for (uint8 j = 0; j != 3 && isAreaSpell == false; ++j) + { + if (info->_effects[j].IsEffect() && info->_effects[j].TargetA.GetTarget()) + { + if (info->_effects[j].TargetA.GetSelectionCategory() == TARGET_SELECT_CATEGORY_NEARBY || + info->_effects[j].TargetA.GetSelectionCategory() == TARGET_SELECT_CATEGORY_AREA) + isAreaSpell = true; + if (!isAreaSpell) + { + switch (info->_effects[j].TargetA.GetTarget()) + { + case TARGET_UNIT_CASTER_AREA_PARTY: + case TARGET_DEST_CHANNEL_TARGET: + isAreaSpell = true; + break; + case TARGET_UNIT_TARGET_CHAINHEAL_ALLY: + //Healing Wave falls under TARGET_UNIT_TARGET_CHAINHEAL_ALLY + if (info->GetFirstRankSpell()->Id == 331) + break; + isAreaSpell = true; + break; + default: + break; + } + } + break; + } + } + + if (isAreaSpell == false) + { + if (!IsTank(target) || target->getAttackers().empty() || + spell->GetTimer() * 4 <= spell->GetCastTime()) //<=25% cast time remains + interrupt = true; + } + } + if (!interrupt && (info->HasEffect(SPELL_EFFECT_RESURRECT) || info->HasEffect(SPELL_EFFECT_RESURRECT_NEW)) && + (target->IsAlive() || (target->GetTypeId() == TYPEID_PLAYER && target->ToPlayer()->IsResurrectRequested()))) + interrupt = true; + if (!interrupt && checkAurasTimer <= diff && me->GetMap()->IsDungeon() && !CCed(me, true) && IsWithinAoERadius(*me)) + interrupt = true; + + if (interrupt) + { + me->InterruptSpell(CurrentSpellTypes(i)); + GC_Timer = 0; + break; + } + } + } + + if (_updateTimerEx1 <= diff) + { + _updateTimerEx1 = urand(2000, 2500); + + //Ex1-timed updates + + //DEBUG + /* + Sometimes bots are affected by zone (instance) scripts + Good example is CoT: Battle for Mount Hyjal + */ + //Faction + //ensure master is not controlled + ChrRacesEntry const* rEntry = sChrRacesStore.LookupEntry(master->GetRace()); + uint32 fac = rEntry ? rEntry->FactionID : 0; + if (me->GetFaction() != master->GetFaction() && master->GetFaction() == fac) + { + //std::ostringstream msg; + //msg << "Something changed my faction (now " << me->GetFaction() << "), changing back to " << fac << "!"; + //BotWhisper(msg.str().c_str()); + me->SetFaction(fac); + } + //Visibility + if (!me->IsVisible() && master->IsVisible()) + { + //BotWhisper("Something changed my visibility status! Making visible..."); + me->SetVisible(true); + } + if (me->IsVisible() && !master->IsVisible()) + { + //BotWhisper("Something changed my visibility status! Making invisible..."); + me->SetVisible(false); + } + //Phase + if (me->GetPhaseMask() != master->GetPhaseMask()) + { + //BotWhisper("Somehow we are not is same phase! Fixing that..."); + me->SetPhaseMask(master->GetPhaseMask(), true); + } + //Vehicle state + //if (me->GetVehicle() && !master->GetVehicle()) + //{ + // BotWhisper("Somehow i'm still in the vehicle! Exiting..."); + // OnBotOwnerExitVehicle(nullptr); + //} + ////Vehicle speed + //if (me->GetVehicle() && master->GetVehicle() && me->GetVehicleBase()->GetTypeId() == TYPEID_UNIT && + // (me->GetVehicleBase()->ToCreature()->GetCreatureTemplate()->InhabitType & INHABIT_AIR) && + // me->GetVehicleBase()->GetSpeed(MOVE_FLIGHT) != master->GetVehicleBase()->GetSpeed(MOVE_FLIGHT)) + //{ + // me->GetVehicleBase()->SetSpeed(MOVE_FLIGHT, master->GetVehicleBase()->GetSpeedRate(MOVE_FLIGHT) * 1.37f); + // me->GetVehicleBase()->SetSpeed(MOVE_RUN, master->GetVehicleBase()->GetSpeedRate(MOVE_FLIGHT) * 1.37f); + //} + //Transport state + if (me->GetTransport() != master->GetTransport()) + { + if (master->GetTransport()) + { + if (me->GetDistance2d(master) < 20.f) + { + master->GetTransport()->AddPassenger(me); + me->m_movementInfo.transport.pos.Relocate(master->GetTransOffset()); + me->Relocate(GetAbsoluteTransportPosition(master)); + me->AddUnitState(UNIT_STATE_IGNORE_PATHFINDING); + } + } + else + { + me->ClearUnitState(UNIT_STATE_IGNORE_PATHFINDING); + me->GetTransport()->RemovePassenger(me); + } + } + //Model size / Combat reach + if (me->GetDisplayId() == me->GetNativeDisplayId()) + { + me->SetFloatValue(UNIT_FIELD_BOUNDINGRADIUS, DEFAULT_PLAYER_BOUNDING_RADIUS * me->GetObjectScale()); + me->SetFloatValue(UNIT_FIELD_COMBATREACH, DEFAULT_PLAYER_COMBAT_REACH * me->GetObjectScale()); + + //debug: restore offhand visual if needed + if (me->GetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + uint32(BOT_SLOT_OFFHAND)) == 0 && _canUseOffHand()) + { + EquipmentInfo const* einfo = BotDataMgr::GetBotEquipmentInfo(me->GetEntry()); + if (CanChangeEquip(BOT_SLOT_OFFHAND) && _equips[BOT_SLOT_OFFHAND]) + { + NpcBotTransmogData const* transmogData = BotDataMgr::SelectNpcBotTransmogs(me->GetEntry()); + if (einfo->ItemEntry[BOT_SLOT_OFFHAND] != _equips[BOT_SLOT_OFFHAND]->GetEntry() && + transmogData && BotMgr::IsTransmogEnabled() && (transmogData->transmogs[BOT_SLOT_OFFHAND].first == _equips[BOT_SLOT_OFFHAND]->GetEntry() || BotMgr::TransmogUseEquipmentSlots()) && + transmogData->transmogs[BOT_SLOT_OFFHAND].second >= 0) + me->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + uint32(BOT_SLOT_OFFHAND), uint32(transmogData->transmogs[BOT_SLOT_OFFHAND].second)); + else + me->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + uint32(BOT_SLOT_OFFHAND), _equips[BOT_SLOT_OFFHAND]->GetEntry()); + } + else + { + me->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + uint32(BOT_SLOT_OFFHAND), einfo->ItemEntry[BOT_SLOT_OFFHAND]); + } + } + } + //end DEBUG + + //Check if moving through air + //if (me->IsInWorld() && !JumpingFlyingOrFalling() && + // !me->HasUnitMovementFlag((MOVEMENTFLAG_ONTRANSPORT)|(MOVEMENTFLAG_DISABLE_GRAVITY)|(MOVEMENTFLAG_ROOT)|(MOVEMENTFLAG_SWIMMING))) + //{ + // //skip case such as moving back up from abyss (movement glitches) + // float x,y,z; + // if (!me->GetMotionMaster()->GetDestination(x,y,z) || z < me->GetPositionZ()) + // { + // float groundz = me->GetMap()->GetHeight(me->GetPhaseMask(), me->GetPositionX(), me->GetPositionY(), me->GetPositionZ(), true, MAX_FALL_DISTANCE); + // if (groundz > INVALID_HEIGHT) + // { + // me->GetMotionMaster()->MoveFall(); + // } + // else if (GetBotCommandState() != BOT_COMMAND_STAY && !me->isMoving()) + // { + // SetBotCommandState(BOT_COMMAND_ABANDON); //reset movement after + // } + // } + //} + + //Zone / Area / WMOArea + if (me->IsInWorld()) + { + uint32 newzone, newarea; + me->GetZoneAndAreaId(newzone, newarea); + + if (_lastZoneId != newzone) + _OnZoneUpdate(newzone, newarea); // also updates area + else// if (_lastAreaId != newarea) + _OnAreaUpdate(newarea); + + if (_wmoAreaUpdateTimer <= diff) + _UpdateWMOArea(); + } + + //Meeting Stone + if (me->IsInWorld() && !IAmFree() && !me->IsInCombat() && !master->IsInCombat() && IsChanneling(master) && !CCed(me) && !IsCasting() && !me->GetVehicle()) + { + if (Spell const* curMasterSpell = master->GetCurrentSpell(CURRENT_CHANNELED_SPELL)) + { + if (curMasterSpell->m_spellInfo->Id == SUMMONING_STONE_EFFECT) + { + if (GameObject* portal = master->GetGameObject(SUMMONING_STONE_EFFECT)) + { + portal->Use(me); + } + } + } + } + + //Gathering + if (me->IsInWorld() && !IAmFree() && HasRole(BOT_ROLE_MASK_GATHERING) && !me->IsInCombat() && !master->IsInCombat() && !master->IsMounted() && !CCed(me) && + master->GetLootGUID().IsEmpty() && !me->isMoving() && !master->isMoving() && master->IsStandState() && !Feasting() && !IsCasting() && !IsCasting(master) && + !HasBotCommandState(BOT_COMMAND_MASK_UNMOVING) && !me->GetVehicle()) + { + //TC_LOG_ERROR("spells", "bot_ai:UpdateEx by {}, found free master, my skills: {}:", me->GetName(), uint32(_roleMask & BOT_ROLE_MASK_GATHERING)); + std::list woList; + NearbyObjectBySkillCheck check(master, INTERACTION_DISTANCE - 1.0f, (_roleMask & BOT_ROLE_MASK_GATHERING)); + Trinity::WorldObjectListSearcher searcher(me, woList, check, GRID_MAP_TYPE_MASK_CREATURE|GRID_MAP_TYPE_MASK_GAMEOBJECT); + Cell::VisitAllObjects(me, searcher, 20.f); + //me->VisitNearbyObject(20.f, searcher); + //TC_LOG_ERROR("spells", "bot_ai:UpdateEx: list is {}", woList.empty() ? "empty" : "not empty"); + if (!woList.empty()) + { + WorldObject* wo = nullptr; + float minangle = float(M_PI); + for (WorldObject* wob : woList) + { + float angle = me->GetAbsoluteAngle(wob); + if (me->GetDistance(wob) <= INTERACTION_DISTANCE * 0.5f && angle < minangle) + { + minangle = angle; + wo = wob; + } + } + + wo = wo ? wo : Trinity::Containers::SelectRandomContainerElement(woList); + //TC_LOG_ERROR("spells", "bot_ai:UpdateEx: processing {}", wo->GetName()); + if (me->GetDistance(wo) <= INTERACTION_DISTANCE * 0.5f && me->HasInArc(float(M_PI), wo)) + { + //cosmetic + CastSpellExtraArgs args(TRIGGERED_FULL_MASK); + me->CastSpell(wo->GetTypeId() == TYPEID_UNIT ? wo->ToUnit() : me, SPELL_COMBAT_SPECIAL_2H_ATTACK, args); + + if (wo->GetTypeId() == TYPEID_UNIT) + wo->ToUnit()->SetDynamicFlag(UNIT_DYNFLAG_LOOTABLE); + + master->SendLoot(wo->GetGUID(), LOOT_SKINNING); + + if (wo->GetTypeId() == TYPEID_UNIT) + wo->ToUnit()->RemoveUnitFlag(UNIT_FLAG_SKINNABLE); + + _updateTimerEx1 = urand(1500, 2100); + } + else + { + std::ostringstream msg; + std::string name = wo->GetName(); + if (wo->GetTypeId() == TYPEID_UNIT) + { + _LocalizeCreature(master, name, wo->GetEntry()); + msg << name << "..."; // TODO + } + else + { + _LocalizeGameObject(master, name, wo->GetEntry()); + msg << name << "!"; + } + BotWhisper(msg.str().c_str()); + + if (me->GetDistance(wo) > INTERACTION_DISTANCE * 0.5f) + { + Position pos; + wo->GetNearPoint(me, pos.m_positionX, pos.m_positionY, pos.m_positionZ, CONTACT_DISTANCE, wo->GetAbsoluteAngle(me)); + BotMovement(BOT_MOVE_POINT, &pos, nullptr, false); + //me->GetMotionMaster()->MovePoint(me->GetMapId(), pos, false); + } + else + { + me->SetFacingTo(me->GetAbsoluteAngle(wo)); + _updateTimerEx1 = urand(1500, 1800); + } + } + } + } + + //Looting + if (me->IsInWorld() && !IAmFree() && HasRole(BOT_ROLE_AUTOLOOT) && HasRole(BOT_ROLE_MASK_LOOTING) && + !me->GetVictim() && !master->IsMounted() && !CCed(me) && !Feasting() && !IsCasting() && + !HasBotCommandState(BOT_COMMAND_MASK_UNMOVING)) + { + //TC_LOG_ERROR("scripts", "bot_ai Looting by {}, my mask: {}:", me->GetName(), uint32(_roleMask & BOT_ROLE_MASK_LOOTING)); + std::list crList; + NearbyLootableCreatureCheck check(master, std::min(30.f, std::max(5.f, sWorld->getFloatConfig(CONFIG_GROUP_XP_DISTANCE) - 10.f))); + Trinity::UnitListSearcher searcher(me, crList, check); + Cell::VisitAllObjects(me, searcher, 40.f); + //me->VisitNearbyObject(40.f, searcher); + for (std::list::iterator itr = crList.begin(); itr != crList.end();) + { + if (!_canLootCreature((*itr)->ToCreature())) + { + //TC_LOG_ERROR("scripts", "bot_ai Looting: cannot loot {} ({})", (*itr)->GetName(), (*itr)->GetGUIDLow()); + crList.erase(itr++); + continue; + } + ++itr; + } + + if (!crList.empty()) + { + Unit* un = Trinity::Containers::SelectRandomContainerElement(crList); + + std::ostringstream msg; + std::string name = un->GetName(); + _LocalizeCreature(master, name, un->GetEntry()); + msg << LocalizedNpcText(master, BOT_TEXT_LOOTING) << ' ' << name; + BotWhisper(msg.str().c_str()); + + _autoLootCreature(un->ToCreature()); + + if (crList.size() > 1) + _updateTimerEx1 = urand(400, 600); + /*//TC_LOG_ERROR("scripts", "bot_ai Looting: processing {}", un->GetName()); + if (me->GetDistance(un) < INTERACTION_DISTANCE + 2.f) + { + //TC_LOG_ERROR("scripts", "bot_ai Looting: looting {}", un->GetName()); + _autoLootCreature(un->ToCreature()); + _updateTimerEx1 = urand(500, 1000); + } + else + { + //TC_LOG_ERROR("scripts", "bot_ai Looting: moving towards {}", un->GetName()); + Position pos; + un->GetNearPoint(me, pos.m_positionX, pos.m_positionY, pos.m_positionZ, 0.f, CONTACT_DISTANCE, un->GetAbsoluteAngle(me)); + me->GetMotionMaster()->MovePoint(me->GetMapId(), pos, false); + }*/ + } + } + } + + if (!IsTempBot()) + Regenerate(); + + //update flags + if (!me->IsInCombat() && ((!_evadeMode && _atHome) || IsWanderer())) + { + if (!me->HasNpcFlag(UNIT_NPC_FLAG_GOSSIP) && !HasBotCommandState(BOT_COMMAND_NOGOSSIP)) + me->SetNpcFlag(UNIT_NPC_FLAG_GOSSIP); + if (me->HasUnitFlag(UNIT_FLAG_PET_IN_COMBAT)) + me->RemoveUnitFlag(UNIT_FLAG_PET_IN_COMBAT); + } + + if (!me->GetVictim()) + Evade(); + + if (HasBotCommandState(BOT_COMMAND_FULLSTOP)) + return false; + + if (!IsTempBot()) + _updateRations(); //safe + + if (checkAurasTimer <= lastdiff) + { + checkAurasTimer += uint32(__rand + __rand + (IAmFree() ? 1000 : 40 * (1 + master->GetNpcBotsCount()))); + + //group demand + if (!IAmFree() && HasRole(BOT_ROLE_PARTY) && (!master->GetGroup() || !master->GetGroup()->IsMember(me->GetGUID()))) + { + //TC_LOG_ERROR("entities.player", "CheckAuras(): adding {} to group", me->GetName()); + master->GetBotMgr()->AddBotToGroup(me); + } + + Unit* mover = me->GetVehicle() ? me->GetVehicleBase() : me; + if (!HasBotCommandState(BOT_COMMAND_MASK_UNCHASE) && !CCed(mover, true) && + (IAmFree() || master->GetBotMgr()->GetBotAllowCombatPositioning()) && + (!mover->isMoving() || Rand() < 50) && !IsCasting(mover) && !IsShootingWand(mover)) + { + if (Unit* victim = CanBotAttackOnVehicle() ? me->GetVictim() : mover->GetTarget() ? ObjectAccessor::GetUnit(*mover, mover->GetTarget()) : nullptr) + { + _aoeSpots.clear(); + if (IAmFree()) + CalculateAoeSpots(me, _aoeSpots); + + //TC_LOG_ERROR("scripts", "GetInPos prepare by {}", me->GetName()); + if (!IAmFree() && master->GetBotMgr()->GetBotAttackRangeMode() == BOT_ATTACK_RANGE_EXACT && + master->GetBotMgr()->GetBotExactAttackRange() == 0 && !GetVehicleAttackDistanceOverride() && + !(!IAmFree() && !GetAoeSpots().empty())) + { + GetInPosition(true, victim); + } + else if (!HasRole(BOT_ROLE_RANGED) && !HasVehicleRoleOverride(BOT_ROLE_RANGED) && + !(!IAmFree() && !GetAoeSpots().empty())) + { + if (me->GetDistance(victim) > 1.5f) + GetInPosition(true, victim); + } + else + { + //TC_LOG_ERROR("scripts", "{} calculates attack pos to attack {}", me->GetName(), victim->GetName()); + bool force = false; + CalculateAttackPos(victim, attackpos, force); + if (mover->GetExactDist2d(&attackpos) > (force ? 0.1f : 4.f) || (force && IsWanderer())) + { + //TC_LOG_ERROR("scripts", "{} moving to x {} y {} z {} to attack {}", + // me->GetName(), attackpos.m_positionX, attackpos.m_positionY, attackpos.m_positionZ, victim->GetName()); + GetInPosition(true, victim, &attackpos); + } + } + } + } + if (shouldUpdateStats && me->GetPhaseMask() == master->GetPhaseMask()) + SetStats(false); + else if (_powersTimer <= lastdiff && !IsTempBot()) + { + _powersTimer += REGEN_CD; //do not mistake for regen, this is only for updating max health/mana + UpdateHealth(); + UpdateMana(); + } + } + + if (Wait()) + return false; + + GenerateRand(); + + if (CanBotAttackOnVehicle()) + BreakCC(diff); + + if (!me->GetVehicle() && CCed(me)) + return false; + + //opponent unsafe + if ((IsWanderer() || (!IAmFree() && (!opponent || !master->GetBotMgr()->GetBotAllowCombatPositioning()))) && + !HasBotCommandState(BOT_COMMAND_STAY) && + (!me->GetVehicle() || (!CCed(me->GetVehicleBase(), true) && !me->GetVehicleBase()->GetTarget()))) + { + Unit const* mover = me->GetVehicle() ? me->GetVehicleBase() : me; + + if (!master->IsAlive()) + { + //If ghost move to corpse, else move to dead player + if (master->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST)) + { + Corpse const* corpse = master->GetCorpse(); + if (corpse && me->GetMap() == corpse->FindMap() && !me->IsInCombat() && !me->HasUnitState(UNIT_STATE_MOVING) && + !IsCasting() && !IsShootingWand() && me->GetDistance(corpse) > 5) + BotMovement(BOT_MOVE_POINT, corpse); + //me->GetMotionMaster()->MovePoint(corpse->GetMapId(), *corpse); + } + else + { + if (!HasBotCommandState(BOT_COMMAND_FOLLOW) || me->GetDistance(master) > 30 - 20 * (!me->IsWithinLOSInMap(master))) + SetBotCommandState(BOT_COMMAND_FOLLOW, true); + } + } + else if (!IsCasting(mover) && (!IsShootingWand(mover) || Rand() < 10)) + { + Unit const* mmover = !IAmFree() ? master : nullptr; + if (!mmover && me->GetMap()->IsBattleground() && !IsFlagCarrier(me)) + { + //GET BG FOLLOW UNIT + static const std::function flag_carrier_pred = [](Unit const* u) { + return bot_ai::IsFlagCarrier(u); + }; + + Unit* nmover = nullptr; + Trinity::UnitSearcher searcher(me, nmover, flag_carrier_pred); + Cell::VisitAllObjects(me, searcher, 80.0f); + if (nmover) + mmover = nmover; + } + + if (mmover) + { + float speed = 0.0f; + _calculatePos(mmover, movepos, &speed); + float maxdist = std::max((mmover->IsPlayer() ? mmover->ToPlayer()->GetBotMgr()->GetBotFollowDist() : BotMgr::GetBotFollowDistDefault() / 2) * + ((mmover->m_movementInfo.GetMovementFlags() & MOVEMENTFLAG_FORWARD) ? 0.125f : mmover->isMoving() ? 0.03125f : 0.25f), 3.f); + Position destPos; + if (me->isMoving()) + me->GetMotionMaster()->GetDestination(destPos.m_positionX, destPos.m_positionY, destPos.m_positionZ); + else + destPos = me->GetPosition(); + + if (!HasBotCommandState(BOT_COMMAND_FOLLOW) || destPos.GetExactDist(&movepos) > maxdist) + SetBotCommandState(BOT_COMMAND_FOLLOW, true, &movepos, &speed); + } + else + RemoveBotCommandState(BOT_COMMAND_FOLLOW); + } + } + + if (!IsCasting() && !IsShootingWand()) + { + if ((me->IsInCombat() && !me->IsSitState() && CanBotAttackOnVehicle()) || !CanSheath()) + { + if (_botclass == BOT_CLASS_HUNTER || _botclass == BOT_CLASS_DARK_RANGER || _botclass == BOT_CLASS_SEA_WITCH) + { + if (me->GetSheath() != SHEATH_STATE_RANGED) + me->SetSheath(SHEATH_STATE_RANGED); + } + else + { + //classes which don't display weapons + if (_botclass == BOT_CLASS_DREADLORD || _botclass == BOT_CLASS_SPELLBREAKER || _botclass == BOT_CLASS_CRYPT_LORD) + { + if (me->GetSheath() != SHEATH_STATE_UNARMED) + me->SetSheath(SHEATH_STATE_UNARMED); + } + else if (me->GetSheath() != SHEATH_STATE_MELEE) + me->SetSheath(SHEATH_STATE_MELEE); + } + } + else if (me->IsStandState() && me->GetSheath() != SHEATH_STATE_UNARMED && Rand() < 50) + { + if (me->GetSheath() == SHEATH_STATE_RANGED) + me->SetSheath(SHEATH_STATE_MELEE); + else + me->SetSheath(SHEATH_STATE_UNARMED); + } + } + + _updateMountedState(); + _updateStandState(); + + if (HasBotCommandState(BOT_COMMAND_INACTION)) + return false; + + return true; +} + +void bot_ai::CommonTimers(uint32 diff) +{ + Events.Update(diff); + SpellTimers(diff); + + if (GC_Timer > diff) GC_Timer -= diff; + if (checkAurasTimer > diff) checkAurasTimer -= diff; + if (waitTimer > diff) waitTimer -= diff; + if (_moveBehindTimer > diff) _moveBehindTimer -= diff; + if (itemsAutouseTimer > diff) itemsAutouseTimer -= diff; + if (evadeDelayTimer > diff) evadeDelayTimer -= diff; + if (roleTimer > diff) roleTimer -= diff; + if (ordersTimer > diff) ordersTimer -= diff; + if (checkMasterTimer > diff) checkMasterTimer -= diff; + if (_checkOwershipTimer > diff) _checkOwershipTimer -= diff; + + if (_powersTimer > diff) _powersTimer -= diff; + if (_chaseTimer > diff) _chaseTimer -= diff; + if (_engageTimer > diff) _engageTimer -= diff; + + if (_potionTimer > diff && (_potionTimer < POTION_CD || !me->IsInCombat())) _potionTimer -= diff; + + if (IAmFree()) + UpdateReviveTimer(diff); + + if (me->IsInWorld()) + { + if (_wmoAreaUpdateTimer > diff) _wmoAreaUpdateTimer -= diff; + + if (me->IsOutdoors()) + { + outdoorsTimer += diff; + if (indoorsTimer) + indoorsTimer = 0; + } + else + { + indoorsTimer += diff; + if (outdoorsTimer) + outdoorsTimer = 0; + } + } + + if (_contestedPvPTimer > diff) _contestedPvPTimer -= diff; + + if (_groupUpdateTimer > diff) _groupUpdateTimer -= diff; + else if (_groupUpdateTimer) _groupUpdateTimer = 0; + + if (_updateTimerMedium > diff) _updateTimerMedium -= diff; + if (_updateTimerEx1 > diff) _updateTimerEx1 -= diff; + if (_updateTimerEx2 > diff) _updateTimerEx2 -= diff; + + if (_saveDisabledSpellsTimer > diff) _saveDisabledSpellsTimer -= diff; +} + +void bot_ai::UpdateReviveTimer(uint32 diff) +{ + if (me->IsAlive()) + return; + + if (_reviveTimer > diff) _reviveTimer -= diff; + else + { + if (IAmFree()) + { + BotMgr::ReviveBot(me); + + if (IsWanderer()) + { + outdoorsTimer = 0; + if (me->GetMap()->GetEntry()->IsContinent() || me->GetMap()->IsBattleground()) + { + Position safePos(*me); + WanderNode const* nextNode = GetNextTravelNode(&safePos, true); + if (!nextNode) + { + TC_LOG_FATAL("scripts", "Bot {} ({}) is unable to get next travel node (1)! cur {}, last {}, position: {}. BOT WAS DISABLED", + me->GetName(), me->GetEntry(), _travel_node_cur->GetWPId(), _travel_node_last ? _travel_node_last->GetWPId() : 0, me->GetPosition().ToString()); + canUpdate = false; + return; + } + + homepos.Relocate(nextNode); + if (me->GetMap()->GetEntry()->IsContinent()) + BotMgr::TeleportBot(me, sMapMgr->CreateBaseMap(nextNode->GetMapId()), nextNode, true); + + TC_LOG_TRACE("npcbots", "Bot {} id {} class {} level {} died on the way from node {} to {} ('{}'), NEW {} ('{}'), {}, dist {} yd!", + me->GetName(), me->GetEntry(), uint32(_botclass), uint32(me->GetLevel()), _travel_node_last ? _travel_node_last->GetWPId() : 0, _travel_node_cur->GetWPId(), + _travel_node_cur->GetName(), nextNode->GetWPId(), nextNode->GetName(), homepos.ToString(), safePos.GetExactDist(homepos)); + + _travel_node_last = _travel_node_cur; + _travel_node_cur = nextNode; + return; + } + } + } + else + if (_reviveTimer > 0) _reviveTimer = 0; + } +} + +void bot_ai::Evade() +{ + if (_atHome && !_evadeMode) + return; + if (evadeDelayTimer > lastdiff) + return; + if (me->GetVictim()) + return; + if (IAmFree() && HasBotCommandState(BOT_COMMAND_FOLLOW)) + return; + if (IsWanderer() && Feasting()) + return; + if (JumpingOrFalling()) + return; + if (IsCasting()) + return; + if (CCed(me, true)) + return; + + if (!IAmFree() || IsTempBot()) + { + _atHome = true; + _evadeMode = false; + return; + } + + if (HasBotCommandState(BOT_COMMAND_MASK_UNMOVING)) + return; + + //delay evade + if (evadeDelayTimer == 0 && me->GetMap()->GetEntry()->IsContinent()) + { + evadeDelayTimer = 5000; + return; + } + + uint16 mapid; + Position pos; + GetHomePosition(mapid, &pos); + + float dist = me->GetExactDist2d(pos); + + if (IsWanderer()) + { + if (mapid != me->GetMap()->GetId() || _evadeCount >= 50 || me->GetExactDist2d(pos) > MAX_WANDER_NODE_DISTANCE || + me->GetPositionZ() <= INVALID_HEIGHT || (me->GetExactDist2d(pos) < 20.0f && me->GetExactDist(pos) > 100.0f)) + { + TC_LOG_DEBUG("npcbots", "Bot {} id {} class {} level {} map {} TELEPORTING to node {} ('{}') map {}, {}, dist {} yd!", + me->GetName(), me->GetEntry(), uint32(_botclass), uint32(me->GetLevel()), me->GetMapId(), _travel_node_cur->GetWPId(), + _travel_node_cur->GetName(), uint32(mapid), pos.ToString(), me->GetExactDist(pos)); + + evadeDelayTimer = 12000; + me->CastSpell(me, WANDERER_HEARTHSTONE); + return; + } + } + else if (mapid != me->GetMapId() || _evadeCount >= 10 || me->GetDistance(pos) > float(SIZE_OF_GRIDS * 0.5f) || !CanAppearInWorld()) + { + if (!teleHomeEvent || !teleHomeEvent->IsActive()) + { + teleHomeEvent = new TeleportHomeEvent(this, false); + Events.AddEvent(teleHomeEvent, Events.CalculateTime(std::chrono::seconds(5))); + + //if bot has been removed manually and while in dungeon + if (mapid != me->GetMapId()) + { + teleHomeEvent->ScheduleAbort(); + teleHomeEvent->Execute(0,0); + } + } + return; + } + + if (botPet && !me->IsWithinDist2d(botPet, 20.0f)) + return; + + if (!IsWanderer()) + _atHome = true; + else if (!me->IsInCombat() && me->GetMap()->GetEntry()->IsContinent() && GetHealthPCT(me) < 90) + return; + + if (dist > 1.5f || IsWanderer()) + { + _evadeMode = true; + + if (!me->isMoving()) + { + ++_evadeCount; + + if (dist > (me->GetMap()->GetEntry()->IsContinent() ? 15.0f : 3.0f)) + { + bool use_path = + !(_travel_node_cur && _travel_node_last && + _travel_node_cur->HasFlag(BotWPFlags::BOTWP_FLAG_MOVEMENT_IGNORES_PATHING) && + _travel_node_last->HasFlag(BotWPFlags::BOTWP_FLAG_MOVEMENT_IGNORES_PATHING)); + GetNextEvadeMovePoint(pos, use_path); + if (pos.m_positionZ <= INVALID_HEIGHT) + { + TC_LOG_ERROR("npcbots", "Bot {} '{}' class {} level {} evade move point has invalid height {} (usepath: {})!\nWPs: cur {}, last {}\nPositions:\ncurrent: {}\ntarget: {}", + me->GetEntry(), me->GetName(), uint32(_botclass), uint32(me->GetLevel()), pos.m_positionZ, uint32(use_path), + _travel_node_cur->GetWPId(), _travel_node_last ? _travel_node_last->GetWPId() : 0, me->GetPosition().ToString(), pos.ToString()); + _evadeCount = 100; + return; + } + + //if (TempSummon* wpc = me->GetMap()->SummonCreature(VISUAL_WAYPOINT, pos, nullptr, 20000)) + // wpc->SetTempSummonType(TEMPSUMMON_TIMED_DESPAWN); + + movepos.Relocate(me); + BotMovement(BOT_MOVE_POINT, &pos, nullptr, use_path); + return; + } + else if (IsWanderer()) + { + OnWanderNodeReached(); + + WanderNode const* nextNode = GetNextTravelNode(&pos, false); + if (!nextNode) + { + TC_LOG_FATAL("npcbots", "Bot {} ({}) is unable to get next travel node! cur {}, last {}, position: {}. BOT WAS DISABLED", + me->GetName(), me->GetEntry(), _travel_node_cur->GetWPId(), _travel_node_last ? _travel_node_last->GetWPId() : 0, me->GetPosition().ToString()); + canUpdate = false; + return; + } + + if (nextNode == _travel_node_cur) + { + //same node: mill about + float angle = Position::NormalizeOrientation(me->GetRelativeAngle(nextNode) + frand(float(-M_PI_2), float(M_PI_2))); + Position cnpos = me->GetFirstCollisionPosition(frand(8.0f, 15.0f), angle); + homepos.Relocate(cnpos); + } + else + homepos.Relocate(nextNode); + + TC_LOG_TRACE("npcbots", "Bot {} id {} class {} level {} wandered from node {} to {}, next {} ('{}'), {}, dist {} yd!", + me->GetName(), me->GetEntry(), uint32(_botclass), uint32(me->GetLevel()), _travel_node_last ? _travel_node_last->GetWPId() : 0, _travel_node_cur->GetWPId(), + nextNode->GetWPId(), nextNode->GetName(), homepos.ToString(), pos.GetExactDist(homepos)); + + if (me->GetMap()->GetEntry()->IsContinent()) + evadeDelayTimer = urand(3000, 7000); + else if (nextNode == _travel_node_cur) + evadeDelayTimer = urand(4000, 6000); + + _travel_node_last = _travel_node_cur; + _travel_node_cur = nextNode; + _evadeCount = 0; + return; + } + + movepos.Relocate(me); + BotMovement(BOT_MOVE_POINT, &pos); + } + + return; + } + + if (me->isMoving()) + return; + + _evadeMode = false; + _evadeCount = 0; + evadeDelayTimer = 0; + + me->SetFacingTo(pos.GetOrientation()); + me->SetFaction(me->GetCreatureTemplate()->faction); +} +void bot_ai::GetNextEvadeMovePoint(Position& pos, bool& use_path) const +{ + //const uint8 evade_jump_threshold = me->HasUnitMovementFlag(MOVEMENTFLAG_SWIMMING) ? 50 : 25; + const float base_angle = me->GetRelativeAngle(pos); + const float orig_z = pos.m_positionZ; + float ground, floor; + + float fulldist = std::min(me->GetExactDist2d(pos), float((MAX_POINT_PATH_LENGTH - 1) * SMOOTH_PATH_STEP_SIZE - 2.0f)); + PathGenerator path(me); + while (path.GetPathType() == PATHFIND_BLANK || (path.GetPathType() & (PATHFIND_NOPATH | PATHFIND_SHORTCUT | PATHFIND_SHORT))) + { + if (std::fabs(fulldist - me->GetExactDist2d(pos)) > 15.0f) + { + pos.Relocate(me->m_positionX, me->m_positionY, me->m_positionZ); + pos.m_positionX += fulldist * std::cos(me->ToAbsoluteAngle(base_angle)); + pos.m_positionY += fulldist * std::sin(me->ToAbsoluteAngle(base_angle)); + + Trinity::NormalizeMapCoord(pos.m_positionX); + Trinity::NormalizeMapCoord(pos.m_positionY); + ground = me->GetMapHeight(pos.m_positionX, pos.m_positionY, MAX_HEIGHT, true, MAX_FALL_DISTANCE); + floor = me->GetMapHeight(pos.m_positionX, pos.m_positionY, pos.m_positionZ); + pos.m_positionZ = std::fabs(ground - pos.m_positionZ) <= std::fabs(floor - pos.m_positionZ) ? ground : floor; + if (pos.m_positionZ <= INVALID_HEIGHT) + { + me->UpdateGroundPositionZ(pos.m_positionX, pos.m_positionY, pos.m_positionZ); + if (pos.m_positionZ <= INVALID_HEIGHT) + pos.m_positionZ = orig_z; + } + } + + path.CalculatePath(pos.m_positionX, pos.m_positionY, pos.m_positionZ); + if (path.GetPathType() == PATHFIND_BLANK || + (path.GetPathType() == PATHFIND_INCOMPLETE && path.GetPath().size() > 2) || + (path.GetPathType() & (PATHFIND_NORMAL | PATHFIND_NOT_USING_PATH | PATHFIND_FARFROMPOLY_START))) + break; + + fulldist *= 0.72f; + + if (fulldist < 25.0f) + break; + } + + if ((path.GetPathType() & (PATHFIND_NORMAL | PATHFIND_NOT_USING_PATH)) == (PATHFIND_NORMAL | PATHFIND_NOT_USING_PATH) && + path.GetPath().size() > 4) + return; + + if (me->IsInWater()) + TC_LOG_TRACE("npcbots", "Bot {} id {} class {} level {} is pathing from water!", me->GetName(), me->GetEntry(), uint32(_botclass), uint32(me->GetLevel())); + + switch (path.GetPathType()) + { + case PATHFIND_NOT_USING_PATH: //swimming + case PATHFIND_NORMAL: //found path + if (me->GetExactDist(Vector3ToPosition(path.GetEndPosition())) > 15.0f) + path.ShortenPathUntilDist(path.GetEndPosition(), frand(7.5f, 15.0f)); + return; + case PATHFIND_BLANK: // invalid coords + case PATHFIND_NOPATH: + case PATHFIND_SHORTCUT: + case PATHFIND_SHORT: + case PATHFIND_INCOMPLETE: + case PATHFIND_FARFROMPOLY: // invalid coords + case PATHFIND_FARFROMPOLY_START: //invalid start coords + case PATHFIND_FARFROMPOLY_END: //invalid end coords + if (path.GetPath().size() > 4) + { + path.ShortenPathUntilDist(path.GetEndPosition(), frand(5.0f, 15.0f)); + return; + } + //log error and use direct point movement + TC_LOG_DEBUG("npcbots", "Bot {} id {} class {} level {} can't find full path to node {} (res {}) from pos {}, falling back to default PF!", + me->GetName(), me->GetEntry(), uint32(_botclass), uint32(me->GetLevel()), IsWanderer() ? _travel_node_cur->GetWPId() : 0, uint32(path.GetPathType()), + me->GetPosition().ToString()); + break; + default: + break; + } + + use_path = false; + + // No path: proceed to destination in small steps, maybe it's just a fluke... Move to surface if needed + Position mypos = me->GetPosition(); + float movedist = std::min(fulldist * 0.25f, 15.0f); + mypos.m_positionX += movedist * std::cos(me->ToAbsoluteAngle(base_angle)); + mypos.m_positionY += movedist * std::sin(me->ToAbsoluteAngle(base_angle)); + Trinity::NormalizeMapCoord(mypos.m_positionX); + Trinity::NormalizeMapCoord(pos.m_positionY); + + ground = me->GetMapHeight(mypos.m_positionX, mypos.m_positionY, MAX_HEIGHT, true, MAX_FALL_DISTANCE); + floor = me->GetMapHeight(mypos.m_positionX, mypos.m_positionY, mypos.m_positionZ); + mypos.m_positionZ = std::fabs(ground - mypos.m_positionZ) <= std::fabs(floor - mypos.m_positionZ) ? ground : floor; + LiquidData ldata; + ZLiquidStatus lstatus = me->GetMap()->GetLiquidStatus( + me->GetPhaseMask(), mypos.m_positionX, mypos.m_positionY, mypos.m_positionZ, MAP_ALL_LIQUIDS, &ldata, me->GetCollisionHeight()); + if (me->IsInWater() != !!(lstatus & MAP_LIQUID_STATUS_IN_CONTACT)) + mypos.m_positionZ = std::max(ldata.level, mypos.m_positionZ); + if (mypos.m_positionZ <= INVALID_HEIGHT) + mypos.m_positionZ = orig_z; + pos.Relocate(mypos); +} +bool bot_ai::CanAppearInWorld() const +{ + return _canAppearInWorld; +} +void bot_ai::TeleportHomeStart(bool reset) +{ + AbortTeleport(); + + //if no master - will teleport to spawn position + //otherwise - will teleport to master + teleHomeEvent = new TeleportHomeEvent(this, reset); + Events.AddEvent(teleHomeEvent, Events.CalculateTime(0ms)); //make sure event will be deleted + if (teleHomeEvent->IsActive()) + teleHomeEvent->ScheduleAbort(); //make sure event will not be executed twice + teleHomeEvent->Execute(0,0); +} +//TeleportHome() ONLY CALLED THROUGH EVENTPROCESSOR +void bot_ai::TeleportHome(bool reset) +{ + ASSERT(teleHomeEvent); + //ASSERT(IAmFree()); + + AbortTeleport(); + + uint16 mapid; + Position pos; + GetHomePosition(mapid, &pos); + + Map* map = sMapMgr->CreateBaseMap(mapid); + ASSERT(!map->Instanceable(), "%s", map->GetDebugInfo().c_str()); + BotMgr::TeleportBot(me, map, &pos, false, reset, this); + + spawned = false; + _evadeCount = 0; +} +//FinishTeleport(uint32, float, float, float, float) ONLY CALLED THROUGH EVENTPROCESSOR +bool bot_ai::FinishTeleport(bool reset) +{ + ASSERT(teleFinishEvent); + //ASSERT(!IAmFree()); + ASSERT(!me->IsInWorld()); + + AbortTeleport(); + + //1) Cannot teleport: master disappeared - return home + if (IAmFree()/* || master->GetSession()->isLogingOut()*/) + { + uint16 mapid = uint16(me->GetMapId()); + Position pos; + if (BotMgr::HideBotSpawns() && !CanAppearInWorld()) + GetHomePosition(mapid, &pos); + if (BotMgr::HideBotSpawns() && !CanAppearInWorld() && me->GetMapId() == mapid && me->GetExactDist2d(pos) < 10.0f) + { + TeleportFinishEvent* delayedTeleportEvent = new TeleportFinishEvent(this, reset); + std::chrono::milliseconds delay(urand(5000, 8000)); + Events.AddEvent(delayedTeleportEvent, Events.CalculateTime(delay)); + SetTeleportFinishEvent(delayedTeleportEvent); + } + else + TeleportHomeStart(!BotMgr::HideBotSpawns()); + + _evadeMode = false; + return false; + } + + BotLogger::Log(NPCBOT_LOG_TELEPORT_FINISH, me, me->IsInGrid(), me->IsWandererBot(), CanAppearInWorld()); + + BotMgr::AddDelayedTeleportCallback([this, reset]() { + Map* map = master->FindMap(); + //2) Cannot teleport: map not found or forbidden - delay teleport + if (!map || !master->IsAlive() || master->GetBotMgr()->RestrictBots(me, true)) + { + //ChatHandler ch(master->GetSession()); + //ch.PSendSysMessage("Your bot %s cannot teleport to you. Restricted bot access on this map...", me->GetName().c_str()); + teleFinishEvent = new TeleportFinishEvent(this, reset); + Events.AddEvent(teleFinishEvent, Events.CalculateTime(std::chrono::seconds(5))); + return; + } + + if (me->FindMap()) + me->ResetMap(); + + me->SetMap(map); + if (master->GetTransport()) + { + master->GetTransport()->AddPassenger(me); + me->m_movementInfo.transport.pos.Relocate(master->GetTransOffset()); + me->Relocate(GetAbsoluteTransportPosition(master)); + me->AddUnitState(UNIT_STATE_IGNORE_PATHFINDING); + } + else + { + Position destpos; + _calculatePos(master, destpos); + me->Relocate(destpos); + } + + map->AddToMap(me); + me->BotStopMovement(); + if (reset) + this->Reset(); + //bot->SetAI(oldAI); + //me->IsAIEnabled = true; + canUpdate = true; + outdoorsTimer = 0; + + //master->m_Controlled.insert(me); + if (me->IsAlive()) + { + CastSpellExtraArgs args(TRIGGERED_FULL_MASK); + me->CastSpell(me, COSMETIC_TELEPORT_EFFECT, args); + } + //me->CastSpell(me, HONORLESS_TARGET, true); + + //update group member online state + if (Group* gr = master->GetGroup()) + if (gr->IsMember(me->GetGUID())) + gr->SendUpdate(); + + //map hooks + if (InstanceScript* iscr = master->GetInstanceScript()) + iscr->OnNPCBotEnter(me); + + SetIsDuringTeleport(false); + }); + + return true; +} + +void bot_ai::AbortTeleport() +{ + if (teleHomeEvent) + { + if (teleHomeEvent->IsActive()) + teleHomeEvent->ScheduleAbort(); + teleHomeEvent = nullptr; + } + + if (teleFinishEvent) + { + if (teleFinishEvent->IsActive()) + teleFinishEvent->ScheduleAbort(); + teleFinishEvent = nullptr; + } +} + +void bot_ai::GetHomePosition(uint16& mapid, Position* pos) const +{ + if (IsWanderer()) + { + mapid = _travel_node_cur->GetMapId(); + pos->Relocate(homepos); + } + else + { + CreatureData const* data = me->GetCreatureData(); + mapid = data->mapId; + pos->Relocate(data->spawnPoint); + } +} + +WanderNode const* bot_ai::GetNextTravelNode(Position const* from, bool random) const +{ + ASSERT(IsWanderer()); + + int8 mylevelbonus = BotDataMgr::GetLevelBonusForBotRank(me->GetCreatureTemplate()->rank); + uint8 mylevelbase = std::max(int8(me->GetLevel()) - mylevelbonus, int8(BotDataMgr::GetMinLevelForBotClass(_botclass))); + + if (!random) + { + if (WanderNode const* bgNode = GetNextBGTravelNode()) + return bgNode; + } + + return BotDataMgr::GetNextWanderNode(_travel_node_cur, _travel_node_last, from, me, mylevelbase, random); +} + +WanderNode const* bot_ai::GetNextBGTravelNode() const +{ + if (!me->GetMap()->IsBattleground() || !GetBG() || !GetGroup() || _travel_node_cur->GetLinks().size() <= 1) + return nullptr; + + Battleground* bg = GetBG(); + switch (bg->GetTypeID()) + { + case BATTLEGROUND_AV: + { + using NodeList = std::list; + + constexpr uint32 CRETYPE_CAPTAIN_A = AV_CPLACE_MAX + 61; + constexpr uint32 CRETYPE_CAPTAIN_H = AV_CPLACE_MAX + 59; + constexpr uint32 CRETYPE_BOSS_A = AV_CPLACE_MAX + 60; + constexpr uint32 CRETYPE_BOSS_H = AV_CPLACE_MAX + 122; + + static const std::function boss_room_wp_pred_a = [](WanderNode const* wp) { return wp->HasFlag(BotWPFlags::BOTWP_FLAG_ALLIANCE_BOSS_ROOM); }; + static const std::function boss_room_wp_pred_h = [](WanderNode const* wp) { return wp->HasFlag(BotWPFlags::BOTWP_FLAG_HORDE_BOSS_ROOM); }; + + uint32 faction = me->GetFaction(); + TeamId myTeamId = bg->GetBotTeamId(me->GetGUID()); + std::vector const team_members = BotMgr::GetAllGroupMembers(me); + uint32 myTeam = myTeamId == TEAM_ALLIANCE ? ALLIANCE : HORDE; + WanderNode const* curNode = _travel_node_cur; + NodeList links; + for (WanderNode const* wp : curNode->GetLinks()) + { + if (BotDataMgr::IsWanderNodeAvailableForBotFaction(wp, faction, false)) + links.push_back(wp); + } + if (links.size() > 1 && _travel_node_last && !curNode->HasFlag(BotWPFlags::BOTWP_FLAG_CAN_BACKTRACK_FROM)) + links.remove(_travel_node_last); + + //if (links.size() == 1) + // return links.front(); + + BattlegroundAV* av = dynamic_cast(bg); + // Above all: check conditions to rush final boss + for (TeamId teamId : { TEAM_ALLIANCE, TEAM_HORDE }) + { + if (myTeamId != teamId) + continue; + + //Condition 1: all bunkers/towers destroyed + bool all_tb_down = true; + for (BG_AV_Nodes counter = BG_AV_NODES_DUNBALDAR_SOUTH; counter <= BG_AV_NODES_FROSTWOLF_WTOWER; ++counter) + { + BG_AV_NodeInfo const& c = av->GetNodes()[counter]; + switch (counter) + { + case BG_AV_NODES_DUNBALDAR_SOUTH: + case BG_AV_NODES_DUNBALDAR_NORTH: + case BG_AV_NODES_ICEWING_BUNKER: + case BG_AV_NODES_STONEHEART_BUNKER: + if (teamId == TEAM_HORDE && c.State != BG_AV_States::POINT_DESTROYED) + all_tb_down = false; + break; + case BG_AV_NODES_ICEBLOOD_TOWER: + case BG_AV_NODES_TOWER_POINT: + case BG_AV_NODES_FROSTWOLF_ETOWER: + case BG_AV_NODES_FROSTWOLF_WTOWER: + if (teamId == TEAM_ALLIANCE && c.State != BG_AV_States::POINT_DESTROYED) + all_tb_down = false; + break; + default: + break; + } + } + if (all_tb_down) + { + //Condition 2: boss node is in reach + auto const& pred = teamId == TEAM_ALLIANCE ? boss_room_wp_pred_h : boss_room_wp_pred_a; + Creature const* boss = ASSERT_NOTNULL(av->GetBGCreature(teamId == TEAM_ALLIANCE ? CRETYPE_BOSS_H : CRETYPE_BOSS_A)); + WanderNode const* bossWP = ASSERT_NOTNULL(WanderNode::FindInAreaWPs(boss->GetAreaId(), pred)); + if (curNode->HasLink(bossWP)) + { + //Condition 3: team is ready OR boss is already engaged + bool team_ready = boss->IsInCombat(); + if (!team_ready) + { + uint32 ready_count = 0; + for (Unit const* member : team_members) + { + if (!member->IsAlive()) + continue; + if (member->IsPlayer()) + { + if (member->IsWithinDist2d(me, 40.0f) || member->ToPlayer()->GetTarget() == boss->GetGUID()) + ++ready_count; + } + else if (member->ToCreature()->GetBotAI()->_travel_node_cur == bossWP || member->GetVictim() == boss || + (!member->GetVictim() && member->IsWithinDist2d(me, 30.0f))) + ++ready_count; + } + team_ready = ready_count >= team_members.size() / 4u * 3u; + } + if (team_ready) + return bossWP; + else + return curNode; + } + } + } + // Firstly: check a boss room to defend + for (auto const& p : { std::pair{TEAM_ALLIANCE, CRETYPE_BOSS_A}, std::pair{TEAM_HORDE, CRETYPE_BOSS_H} }) + { + if (myTeamId != p.first) + continue; + Creature const* boss = ASSERT_NOTNULL(av->GetBGCreature(p.second)); + if (boss->IsInCombat()) + { + auto const& pred = p.first == TEAM_ALLIANCE ? boss_room_wp_pred_a : boss_room_wp_pred_h; + WanderNode const* bossWP = ASSERT_NOTNULL(WanderNode::FindInAreaWPs(boss->GetAreaId(), pred)); + NodeList vlinks = curNode->GetShortestPathLinks(bossWP, links); + if (!vlinks.empty()) + return vlinks.size() == 1u ? vlinks.front() : Trinity::Containers::SelectRandomContainerElement(vlinks); + } + } + // Secondly: check captain room to defend + for (auto const& p : { std::pair{TEAM_ALLIANCE, CRETYPE_CAPTAIN_A}, std::pair{TEAM_HORDE, CRETYPE_CAPTAIN_H} }) + { + if (myTeamId != p.first) + continue; + Creature const* captain = ASSERT_NOTNULL(av->GetBGCreature(p.second)); + if (captain->IsAlive() && captain->IsInCombat()) + { + WanderNode const* cap_node = nullptr; + float mindist = 50000.0f; + WanderNode::DoForAllAreaWPs(captain->GetAreaId(), [&cap_node, &mindist, fac = faction, pos = captain](WanderNode const* wp) { + float dist = pos->GetExactDist2d(wp); + if (dist < mindist && BotDataMgr::IsWanderNodeAvailableForBotFaction(wp, fac, false)) + { + mindist = dist; + cap_node = wp; + } + }); + if (cap_node && curNode->HasLink(cap_node)) + return cap_node; + } + } + // Thirdly: find next defend point + // Fourthly: find a currently assaulted point by our team and make sure someone defends it + // Ex. some GYs to base on are beyound directly accessible tower/bunker or captain room + // Fithly: find a GY/tower/bunker in non-assaulted state in reach to assault + for (TeamId teamId : { TEAM_ALLIANCE, TEAM_HORDE }) + { + if (myTeamId != teamId) + continue; + + constexpr std::array defend_priority_a{ 9, 7, 6, 3, 4, 2, 1, 8, 8, 5, 5, 0, 0, 0, 0 }; + constexpr std::array defend_priority_h{ 1, 2, 4, 3, 6, 7, 9, 0, 0, 0, 0, 5, 5, 8, 8 }; + + static const std::function flag_wp_pred = [](WanderNode const* wp) { return wp->HasFlag(BotWPFlags::BOTWP_FLAG_BG_FLAG_PICKUP_TARGET); }; + static const std::function bunker_wp_pred = [](WanderNode const* wp) { return wp->HasFlag(BotWPFlags::BOTWP_FLAG_BG_FLAG_PICKUP_TARGET) && wp->HasFlag(BotWPFlags::BOTWP_FLAG_ALLIANCE_ONLY); }; + static const std::function tower_wp_pred = [](WanderNode const* wp) { return wp->HasFlag(BotWPFlags::BOTWP_FLAG_BG_FLAG_PICKUP_TARGET) && wp->HasFlag(BotWPFlags::BOTWP_FLAG_HORDE_ONLY); }; + + auto const& def_prio = teamId == TEAM_ALLIANCE ? defend_priority_a : defend_priority_h; + auto const& defe_pred = teamId == TEAM_ALLIANCE ? bunker_wp_pred : tower_wp_pred; + auto const& assa_pred = teamId == TEAM_ALLIANCE ? tower_wp_pred : bunker_wp_pred; + + std::pair defNode{}; + NodeList assdlist; + NodeList assalist; + std::set> defendable_nodes; + std::set> assaulted_nodes; + std::set> assaultable_nodes; + NodeList accessible_nodes = links; //copy + accessible_nodes.push_back(curNode); + for (BG_AV_Nodes counter = BG_AV_NODES_FIRSTAID_STATION; counter < BG_AV_NODES_MAX; ++counter) + { + BG_AV_NodeInfo const& c = av->GetNodes()[counter]; + if (c.State == BG_AV_States::POINT_ASSAULTED) + { + if (c.Owner != myTeam && def_prio[counter] > 0) + defendable_nodes.insert({ uint8(counter), &c }); + else if (c.Owner == myTeam) + assaulted_nodes.insert({ av->GetObjectThroughNodeForBot(counter), &c }); + } + else if (c.State == BG_AV_States::POINT_NEUTRAL || (c.State == BG_AV_States::POINT_CONTROLED && c.Owner != myTeam)) + { + if (c.Tower) + assaultable_nodes.insert({ av->GetObjectThroughNodeForBot(counter), &c }); + else + assaultable_nodes.insert({ av->GetObjectThroughNodeForBot(counter), &c }); + } + else if (counter == (teamId == TEAM_ALLIANCE ? BG_AV_NODES_FROSTWOLF_HUT : BG_AV_NODES_FIRSTAID_STATION)) + assaultable_nodes.insert({ av->GetObjectThroughNodeForBot(counter), &c }); + } + WanderNode::DoForAllMapWPs(av->GetMapId(), [&](WanderNode const* wp) { + if (defe_pred(wp)) + { + for (auto const& vt : defendable_nodes) + { + if (defNode.second != nullptr && def_prio[vt.first] < def_prio[defNode.first]) + continue; + uint32 objType = av->GetObjectThroughNodeForBot(BG_AV_Nodes(vt.first)); + if (GameObject const* go = av->BgObjects[objType] ? av->GetBGObject(objType) : nullptr) + { + if (go->IsWithinDist2d(wp, INTERACTION_DISTANCE * 2.0f)) + { + defNode = { vt.first, wp }; + break; + } + } + } + } + }); + if (WanderNode const* dnode = defNode.second) + { + NodeList defLinks = curNode->GetShortestPathLinks(dnode, links); + if (!defLinks.empty()) + return defLinks.size() == 1u ? defLinks.front() : Trinity::Containers::SelectRandomContainerElement(defLinks); + } + WanderNode::DoForContainerWPs(accessible_nodes, [&](WanderNode const* wp) { + if (flag_wp_pred(wp)) + { + for (auto const& vt : assaulted_nodes) + { + uint32 defenders_count = 0; + for (Unit const* member : team_members) + { + if (member != me && member->IsAlive() && (member->GetExactDist2d(wp) < 40.0f || + (member->IsNPCBot() && member->ToCreature()->GetBotAI()->_travel_node_cur == wp))) + ++defenders_count; + } + if (defenders_count >= 2) + continue; + if (GameObject const* go = av->BgObjects[vt.first] ? av->GetBGObject(vt.first) : nullptr) + { + if (go->IsWithinDist2d(wp, INTERACTION_DISTANCE * 2.0f)) + { + assdlist.push_back(wp); + break; + } + } + } + if (assa_pred(wp)) + { + for (auto const& vt : assaultable_nodes) + { + if (vt.first == BG_AV_OBJECT_FLAG_N_SNOWFALL_GRAVE) + { + uint32 attackers_count = 0; + for (Unit const* member : team_members) + if (member != me && member->IsAlive() && member->IsNPCBot() && member->ToCreature()->GetBotAI()->_travel_node_cur == wp) + ++attackers_count; + if (attackers_count >= 3) + continue; + } + if (GameObject const* go = av->BgObjects[vt.first] ? av->GetBGObject(vt.first) : nullptr) + { + if (go->IsWithinDist2d(wp, INTERACTION_DISTANCE * 2.0f)) + { + assalist.push_back(wp); + break; + } + } + } + } + } + }); + if (!assdlist.empty()) + { + if (std::find(assdlist.cbegin(), assdlist.cend(), curNode) != assdlist.cend()) + return curNode; + //remove non-empty points + assdlist.remove_if([&team_members, except_wp = curNode](WanderNode const* wp) { + if (wp != except_wp) + { + for (Unit const* member : team_members) + { + if (member->IsAlive() && (member->GetExactDist2d(wp) < 40.0f || + (member->IsNPCBot() && member->ToCreature()->GetBotAI()->_travel_node_cur == wp))) + return true; + } + } + return false; + }); + } + if (!assdlist.empty()) + return assdlist.size() == 1u ? assdlist.front() : Trinity::Containers::SelectRandomContainerElement(assdlist); + if (!assalist.empty()) + return assalist.size() == 1u ? assalist.front() : Trinity::Containers::SelectRandomContainerElement(assalist); + } + //Last thing: try to capture the mines (2 people at most) + for (auto const& p : { std::pair{TEAM_ALLIANCE, std::array{AV_CPLACE_MINE_N_3, AV_CPLACE_MINE_S_3}}, std::pair{TEAM_HORDE, std::array{AV_CPLACE_MINE_S_3, AV_CPLACE_MINE_N_3}} }) + { + if (myTeamId != p.first) + continue; + + static const std::function mine_pred = [](WanderNode const* wp) { return wp->HasFlag(BotWPFlags::BOTWP_FLAG_BG_MISC_OBJECTIVE_1); }; + + for (BG_AV_CreaturePlace sptype : p.second) + { + Creature const* mboss = ASSERT_NOTNULL(av->GetBGCreature(uint32(sptype))); + if (mboss->IsAlive() && !mboss->IsInCombat() && me->IsWithinDist2d(mboss, SIZE_OF_GRIDS * 0.75f)) + { + WanderNode const* mineWP = ASSERT_NOTNULL(WanderNode::FindInMapWPs(mboss->GetMapId(), mine_pred)); + WanderNode const* mineLink = mineWP->GetLinks().front(); + NodeList mlinks = curNode->GetShortestPathLinks(mineWP, links); + if (!mlinks.empty()) + { + uint32 attackers_count = 0; + for (Unit const* member : team_members) + { + if (member == me || !member->IsAlive() || !member->IsNPCBot()) + continue; + WanderNode const* mwp = member->ToCreature()->GetBotAI()->_travel_node_cur; + if (!mwp) + continue; + if (mwp == mineWP || mwp == mineLink || member->GetVictim() == mboss || + std::find(mlinks.cbegin(), mlinks.cend(), mwp) != mlinks.cend() || + (!mwp->GetLinks().empty() && std::find(mwp->GetLinks().cbegin(), mwp->GetLinks().cend(), mineLink) != mwp->GetLinks().cend())) + ++attackers_count; + } + if (attackers_count <= 1) + { + TC_LOG_DEBUG("npcbots", "Bot {} {} team {} goes for a mine! Cur node: {} {}", + me->GetName(), me->GetEntry(), uint32(myTeamId), curNode->GetWPId(), curNode->GetName()); + return mlinks.size() == 1u ? mlinks.front() : Trinity::Containers::SelectRandomContainerElement(mlinks); + } + } + } + } + } + //No immediate target: rush enemy captain + for (TeamId teamId : { TEAM_ALLIANCE, TEAM_HORDE }) + { + if (myTeamId != teamId) + continue; + Creature const* captain = av->GetBGCreature(teamId == TEAM_ALLIANCE ? CRETYPE_CAPTAIN_H : CRETYPE_CAPTAIN_A); + if (captain && captain->IsAlive()) + { + WanderNode const* cap_node = nullptr; + float mindist = 50000.0f; + WanderNode::DoForAllAreaWPs(captain->GetAreaId(), [&cap_node, &mindist, fac = faction, pos = captain](WanderNode const* wp) { + float dist = pos->GetExactDist2d(wp); + if (dist < mindist && BotDataMgr::IsWanderNodeAvailableForBotFaction(wp, fac, false)) + { + mindist = dist; + cap_node = wp; + } + }); + if (cap_node && curNode->HasLink(cap_node)) + return cap_node; + } + } + //No immediate target: find a point next to enemy boss and try going there + for (TeamId teamId : { TEAM_ALLIANCE, TEAM_HORDE }) + { + if (myTeamId != teamId) + continue; + if (Creature const* boss = av->GetBGCreature(teamId == TEAM_ALLIANCE ? CRETYPE_BOSS_H : CRETYPE_BOSS_A)) + { + auto const& pred = teamId == TEAM_ALLIANCE ? boss_room_wp_pred_h : boss_room_wp_pred_a; + WanderNode const* bossWP = ASSERT_NOTNULL(WanderNode::FindInAreaWPs(boss->GetAreaId(), pred)); + NodeList vlinks = curNode->GetShortestPathLinks(bossWP->GetLinks().front(), links); + if (!vlinks.empty()) + return vlinks.size() == 1u ? vlinks.front() : Trinity::Containers::SelectRandomContainerElement(vlinks); + } + } + + if (links.size() > 1) + { + TC_LOG_DEBUG("npcbots", "Bot {} {} team {} has no target point in BG_AV! Falling back to random ({} links)!. Cur node: {} {}", + me->GetName(), me->GetEntry(), uint32(myTeamId), uint32(curNode->GetLinks().size()), curNode->GetWPId(), curNode->GetName()); + } + + break; + } + case BATTLEGROUND_WS: + case BATTLEGROUND_AB: + default: + break; + } + + return nullptr; +} + +void bot_ai::OnWanderNodeReached() +{ + ASSERT(me->IsInWorld()); + ASSERT(_travel_node_cur != nullptr, "%s", me->GetGUID().ToString().c_str()); + + if (Battleground* bg = GetBG()) + { + if (_travel_node_cur->HasFlag(BotWPFlags::BOTWP_FLAG_BG_FLAG_DELIVER_TARGET) && IsFlagCarrier(me, bg->GetTypeID())) + { + switch (bg->GetTypeID()) + { + case BATTLEGROUND_WS: + if (bg->GetBotTeamId(me->GetGUID()) == TEAM_ALLIANCE && _travel_node_cur->HasFlag(BotWPFlags::BOTWP_FLAG_ALLIANCE_ONLY)) + bg->HandleBotAreaTrigger(me, 3646); + if (bg->GetBotTeamId(me->GetGUID()) == TEAM_HORDE && _travel_node_cur->HasFlag(BotWPFlags::BOTWP_FLAG_HORDE_ONLY)) + bg->HandleBotAreaTrigger(me, 3647); + break; + default: + break; + } + } + if (_travel_node_cur->HasFlag(BotWPFlags::BOTWP_FLAG_BG_FLAG_PICKUP_TARGET) && !IsFlagCarrier(me, bg->GetTypeID())) + { + switch (bg->GetTypeID()) + { + case BATTLEGROUND_AV: + { + static const uint32 SPELL_OPENING_FLAG = 21651u; + + GameObject* obj = nullptr; + + BattlegroundAV* av = dynamic_cast(bg); + for (BG_AV_Nodes counter = BG_AV_NODES_FIRSTAID_STATION; counter < BG_AV_NODES_MAX; ++counter) + { + BG_AV_NodeInfo const& c = av->GetNodes()[counter]; + if (c.State == BG_AV_States::POINT_DESTROYED) + continue; + if (c.State == BG_AV_States::POINT_NEUTRAL || c.Owner != bg->GetBotTeam(me->GetGUID())) + { + uint32 node_type = av->GetObjectThroughNodeForBot(counter); + GameObject* go = bg->BgObjects[node_type] ? bg->GetBGObject(node_type) : nullptr; + if (go && me->IsWithinDistInMap(go, 10.0f)) + { + obj = go; + break; + } + } + } + + if (!obj) + break; + + bool already_used = false; + for (Unit const* member : BotMgr::GetAllGroupMembers(me)) + { + if (member->GetGUID() == me->GetGUID()) + continue; + if (Spell const* curSpell = member->GetCurrentSpell(CURRENT_GENERIC_SPELL)) + { + if (curSpell->m_spellInfo->Id == SPELL_OPENING_FLAG && curSpell->m_targets.GetGOTargetGUID() == obj->GetGUID()) + { + already_used = true; + break; + } + } + } + if (already_used) + break; + + //TC_LOG_ERROR("npcbots", "OnWanderNodeReached: [AV] Bot {} USES flag {} at node {}", me->GetName(), obj->GetName(), node); + + if (me->IsMounted()) + DismountBot(); + me->CastSpell(obj, SPELL_OPENING_FLAG); + + break; + } + case BATTLEGROUND_WS: + { + if (bg->GetBotTeamId(me->GetGUID()) == TEAM_ALLIANCE && _travel_node_cur->HasFlag(BotWPFlags::BOTWP_FLAG_HORDE_ONLY)) + { + if (GameObject* go = bg->GetBGObject(BG_WS_OBJECT_H_FLAG, true)) + { + //TC_LOG_ERROR("npcbots", "OnWanderNodeReached: [WSG] Horde flag dist: {}", me->GetExactDist(go)); + if (me->IsMounted()) + DismountBot(); + bg->EventBotClickedOnFlag(me, go); + } + } + if (bg->GetBotTeamId(me->GetGUID()) == TEAM_HORDE && _travel_node_cur->HasFlag(BotWPFlags::BOTWP_FLAG_ALLIANCE_ONLY)) + { + if (GameObject* go = bg->GetBGObject(BG_WS_OBJECT_A_FLAG, true)) + { + //TC_LOG_ERROR("npcbots", "OnWanderNodeReached: [WSG] Alliance flag dist: {}", me->GetExactDist(go)); + if (me->IsMounted()) + DismountBot(); + bg->EventBotClickedOnFlag(me, go); + } + } + break; + } + case BATTLEGROUND_AB: + { + static const uint32 SPELL_OPENING_FLAG = 21651u; + + uint8 node = BG_AB_NODE_STABLES; + GameObject* obj = bg->GetBGObject(node*8+BG_AB_OBJECT_BANNER_NEUTRAL); + while (node < BG_AB_DYNAMIC_NODES_COUNT && (!obj || !me->IsWithinDistInMap(obj, 10.0f))) + { + ++node; + obj = bg->GetBGObject(node*8+BG_AB_OBJECT_BANNER_NEUTRAL); + } + if (node < BG_AB_DYNAMIC_NODES_COUNT) + { + TeamId teamId = bg->GetBotTeamId(me->GetGUID()); + BattlegroundAB const* bgab = dynamic_cast(bg); + + if (bgab->IsNodeOccupied(node, teamId) || bgab->IsNodeContested(node, teamId)) + break; + + //at this point node is either neutral or owned/contested by other team + uint8 new_bg_obj_type; + if (bgab->IsNodeOccupied(node, bg->GetOtherTeamId(teamId))) + new_bg_obj_type = BG_AB_OBJECT_BANNER_HORDE; + else if (bgab->IsNodeContested(node, bg->GetOtherTeamId(teamId))) + new_bg_obj_type = BG_AB_OBJECT_BANNER_CONT_H; + else + new_bg_obj_type = BG_AB_OBJECT_BANNER_NEUTRAL; + + obj = bg->GetBGObject(node*8+new_bg_obj_type); + ASSERT(obj != nullptr); + + bool already_used = false; + for (Unit const* member : BotMgr::GetAllGroupMembers(me)) + { + if (member->GetGUID() == me->GetGUID()) + continue; + if (Spell const* curSpell = member->GetCurrentSpell(CURRENT_GENERIC_SPELL)) + { + if (curSpell->m_spellInfo->Id == SPELL_OPENING_FLAG && curSpell->m_targets.GetGOTargetGUID() == obj->GetGUID()) + { + already_used = true; + break; + } + } + } + if (already_used) + break; + + //TC_LOG_ERROR("npcbots", "OnWanderNodeReached: [AB] Bot {} USES flag {} at node {}", + // me->GetName(), obj->GetName(), uint32(node)); + if (me->IsMounted()) + DismountBot(); + me->CastSpell(obj, SPELL_OPENING_FLAG); + } + break; + } + default: + break; + } + } + } +} + +void bot_ai::OnBotEnterBattleground() +{ + Battleground* bg = ASSERT_NOTNULL(GetBG()); + + if (bg->GetStatus() != STATUS_IN_PROGRESS && IsWanderer()) + { + uint32 mapId = bg->GetBgMap()->GetId(); + float mindist = 50000.0f; + WanderNode const* startNode = nullptr; + WanderNode::DoForAllMapWPs(mapId, [pos = me->GetPosition(), &mindist, &startNode](WanderNode const* wp) { + if (wp->HasFlag(BotWPFlags::BOTWP_FLAG_SPAWN)) + { + float dist = pos.GetExactDist2d(wp); + if (dist < mindist) + { + startNode = wp; + mindist = dist; + } + } + }); + + SetBotCommandState(BOT_COMMAND_STAY); + if (startNode) + { + if (TempSummon* wpc = me->GetMap()->SummonCreature(VISUAL_WAYPOINT, *startNode, nullptr, 1000)) + { + wpc->SetTempSummonType(TEMPSUMMON_TIMED_DESPAWN); + float angle = bg->GetTypeID() == BATTLEGROUND_WS ? frand(float(M_PI * 0.75), float(M_PI * 1.25)) : frand(0.001f, float(M_PI * 1.995)); + Position myStartPos = wpc->GetFirstCollisionPosition(frand(5.0f, 20.0f), angle); + BotMovement(BOT_MOVE_POINT, &myStartPos); + } + } + } +} + +void bot_ai::SetWanderer() +{ + if (IAmFree()) + { + _wanderer = true; + if (botPet) + botPet->GetBotPetAI()->SetWanderer(); + } +} + +void bot_ai::KillEvents(bool force) +{ + Events.KillAllEvents(force); +} + +void bot_ai::OnBotEnterVehicle(Vehicle const* vehicle) +{ + if (VehicleSeatEntry const* seat = vehicle->GetSeatForPassenger(me)) + { + if (seat->Flags & VEHICLE_SEAT_FLAG_CAN_CONTROL) + { + vehicle->GetBase()->SetFaction(master->GetFaction()); + //vehicle->GetBase()->SetOwnerGUID(master->GetGUID()); + vehicle->GetBase()->SetCreator(master); + vehicle->GetBase()->SetUnitFlag(UNIT_FLAG_POSSESSED); + vehicle->GetBase()->SetUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED); + vehicle->GetBase()->SetByteValue(UNIT_FIELD_BYTES_2, 1, master->GetByteValue(UNIT_FIELD_BYTES_2, 1)); + ASSERT(vehicle->GetBase()->SetCharmedBy(me, CHARM_TYPE_VEHICLE)); + vehicle->GetBase()->SetControlledByPlayer(true); + + vehcomboPoints = 0; + //flight mode + switch (vehicle->GetBase()->GetEntry()) + { + case CREATURE_NEXUS_SKYTALON_1: + case CREATURE_EOE_SKYTALON_N: + case CREATURE_EOE_SKYTALON_H: + case CREATURE_OCULUS_DRAKE_RUBY: + case CREATURE_OCULUS_DRAKE_EMERALD: + case CREATURE_OCULUS_DRAKE_AMBER: + UnsummonAll(false); + vehicle->GetBase()->SetCanFly(true); + vehicle->GetBase()->SetDisableGravity(true); + break; + default: + break; + } + } + + if (Unit* oVeh = master->GetVehicleBase()) + { + CreatureTemplate const* vehTemplate = vehicle->GetBase()->GetTypeId() == TYPEID_UNIT ? vehicle->GetBase()->ToCreature()->GetCreatureTemplate() : nullptr; + ////Set hp and mana percent to avoid abuse + //vehicle->GetBase()->SetHealth(vehicle->GetBase()->GetMaxHealth() * oVeh->GetHealthPct() / 100.f + 0.5f); + //if (oVeh->GetPowerType() == POWER_MANA) + //{ + // float mpPct = oVeh->GetPower(POWER_MANA) * 100.f / oVeh->GetMaxPower(POWER_MANA); + // vehicle->GetBase()->SetPower(POWER_MANA, vehicle->GetBase()->GetMaxPower(POWER_MANA) * mpPct / 100.f + 0.5f); + //} + //speed + if (vehTemplate && + (vehTemplate->Movement.Flight == CreatureFlightMovementType::CanFly || + vehTemplate->Movement.Flight == CreatureFlightMovementType::DisableGravity)) + { + //hack to use vehicle speed + vehicle->GetBase()->RemoveAurasByType(SPELL_AURA_MOD_INCREASE_VEHICLE_FLIGHT_SPEED); + vehicle->GetBase()->RemoveAurasByType(SPELL_AURA_MOD_VEHICLE_SPEED_ALWAYS); + vehicle->GetBase()->RemoveAurasByType(SPELL_AURA_MOD_FLIGHT_SPEED_NOT_STACK); + vehicle->GetBase()->RemoveAurasByType(SPELL_AURA_MOD_MOUNTED_SPEED_NOT_STACK); + vehicle->GetBase()->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_INCREASE_VEHICLE_FLIGHT_SPEED, true); + vehicle->GetBase()->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_VEHICLE_SPEED_ALWAYS, true); + vehicle->GetBase()->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_FLIGHT_SPEED_NOT_STACK, true); + vehicle->GetBase()->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_MOUNTED_SPEED_NOT_STACK, true); + vehicle->GetBase()->SetSpeedRate(MOVE_FLIGHT, oVeh->GetSpeedRate(MOVE_FLIGHT) * 1.17f); + vehicle->GetBase()->SetSpeedRate(MOVE_RUN, oVeh->GetSpeedRate(MOVE_FLIGHT) * 1.17f); + vehicle->GetBase()->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_DECREASE_SPEED, true); + vehicle->GetBase()->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_SPEED_SLOW_ALL, true); + } + } + } +} + +void bot_ai::OnBotExitVehicle(Vehicle const* vehicle) +{ + if (VehicleSeatEntry const* seat = vehicle->GetSeatForPassenger(me)) + { + if (seat->Flags & VEHICLE_SEAT_FLAG_CAN_CONTROL) + { + vehicle->GetBase()->SetControlledByPlayer(false); + vehicle->GetBase()->RemoveCharmedBy(me); + vehicle->GetBase()->RestoreFaction(); + //vehicle->GetBase()->SetOwnerGUID(ObjectGuid::Empty); + vehicle->GetBase()->SetCreator(nullptr); + vehicle->GetBase()->RemoveUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED); + if (vehicle->GetBase()->GetTypeId() == TYPEID_UNIT) + vehicle->GetBase()->RemoveUnitFlag(UNIT_FLAG_POSSESSED); + vehicle->GetBase()->SetByteValue(UNIT_FIELD_BYTES_2, 1, 0); + + curVehStrat = BOT_VEH_STRAT_NONE; + if (vehicle->GetBase()->IsSummon()) + vehicle->GetBase()->ToCreature()->DespawnOrUnsummon(1ms); + } + } +} + +void bot_ai::AfterBotOwnerEnterVehicle() +{ + if (!me->GetVehicle() && master->GetVehicleCreatureBase() && master->GetVehicleCreatureBase()->m_spells[0] && + master->GetVehicleBase()->IsControlledByPlayer()) + { + VehicleSeatEntry const* seat = master->GetVehicle()->GetSeatForPassenger(master); + if (seat && seat->CanEnterOrExit()) + { + uint32 creEntry = 0; + uint32 vehEntry; + + ChooseVehicleForEncounter(creEntry, vehEntry); + if (!creEntry) + { + TC_LOG_DEBUG("scripts", "OnBotOwnerEnterVehicle: no vehicle selected for bot master veh {}!", + master->GetVehicleCreatureBase()->GetName()); + return; + } + Unit* veh = SpawnVehicle(creEntry, vehEntry); + ASSERT(veh); + + removeShapeshiftForm(); + //me->BotStopMovement(); + + //DO NOT use spellclick here, this is undefined behaviour if target selection is not explicit + //veh->HandleSpellClick(me); + switch (creEntry) //select vehicle ride spell + { + //TODO + case CREATURE_EOE_SKYTALON_N: + case CREATURE_EOE_SKYTALON_H: + me->CastSpell(veh, 56071); //Ride Red Dragon Buddy + break; + case CREATURE_OCULUS_DRAKE_RUBY: + case CREATURE_OCULUS_DRAKE_EMERALD: + case CREATURE_OCULUS_DRAKE_AMBER: + { + me->EnterVehicle(veh); //cannot cast ride spell (49464, 49346, 49460) due to targeting (TARGET_UNIT_NEARBY_ENTRY) + CastSpellExtraArgs args(true); + args.SetOriginalCaster(me->GetGUID()); + veh->CastSpell(veh, 66667, args); //Gear Scaling + break; + } + //case CREATURE_TOC_STEED_QUELDOREI: + //case CREATURE_TOC_NIGHTSABER: + //case CREATURE_TOC_STEED_STORMWIND: + //case CREATURE_TOC_MECHANOSTRIDER: + //case CREATURE_TOC_RAM: + //case CREATURE_TOC_ELEKK: + //case CREATURE_TOC_HAWKSTRIDER_SUNREAVER: + //case CREATURE_TOC_RAPTOR: + //case CREATURE_TOC_WARHORSE: + //case CREATURE_TOC_WOLF: + //case CREATURE_TOC_HAWKSTRIDER_SILVERMOON: + //case CREATURE_TOC_KODO: + // me->CastSpell(veh, 63151); //Ride Vehicle + // break; + case CREATURE_TOC5_WARHORSE: + case CREATURE_TOC5_BATTLEWORG: + me->CastSpell(veh, 67830); //Ride Vehicle + break; + default: + me->EnterVehicle(veh); + break; + } + return; + } + } + + //TC_LOG_ERROR("scripts", "OnBotOwnerEnterVehicle: master not in vehicle or no veh found for bot {}!", me->GetName()); +} + +void bot_ai::OnBotOwnerEnterVehicle(Vehicle const* /*vehicle*/) +{ + shouldEnterVehicle = true; +} + +void bot_ai::OnBotOwnerExitVehicle(Vehicle const* /*vehicle*/) +{ + shouldEnterVehicle = false; + if (me->GetVehicle()) + { + if (me->GetMapId() == 631) // Icecrown Citadel + { + me->ExitVehicle(); + me->BotStopMovement(); + } + } +} + +Unit* bot_ai::SpawnVehicle(uint32 creEntry, uint32 vehEntry) +{ + ASSERT(sObjectMgr->GetCreatureTemplate(creEntry)); + if (vehEntry != 0) + ASSERT(sVehicleStore.LookupEntry(vehEntry)); + + Map* map = me->GetMap(); + float x, y, z, o; + TempSummon* vc; + if (!me->GetTransport()) + { + o = master->GetOrientation(); + me->GetClosePoint(x, y, z, me->GetCombatReach()); + vc = new TempSummon(nullptr, me, false); + ASSERT(vc->Create(map->GenerateLowGuid(), map, master->GetPhaseMask(), creEntry, Position(x,y,z,o), nullptr, vehEntry, true)); + vc->SetTempSummonType(TEMPSUMMON_CORPSE_DESPAWN); + vc->InitStats(0); + ASSERT(map->AddToMap(vc->ToCreature())); + vc->InitSummon(); //not needed really + } + else + { + if (master->GetVehicle()) + o = master->GetVehicleBase()->GetTransOffsetO(); + else + o = master->GetTransOffsetO(); + x = me->GetTransOffsetX(); + y = me->GetTransOffsetY(); + z = me->GetTransOffsetZ(); + Position vehpos(x, y, z, o); + me->GetTransport()->CalculatePassengerPosition(x, y, z, &o); + vc = new TempSummon(nullptr, me, false); + ASSERT(vc->Create(map->GenerateLowGuid(), map, master->GetPhaseMask(), creEntry, Position(x,y,z,o), nullptr, vehEntry, true)); + + //vc->SetTransport(me->GetTransport()); + //vc->AddUnitMovementFlag(MOVEMENTFLAG_ONTRANSPORT); + //vc->m_movementInfo.transport.guid = GetGUID(); + me->GetTransport()->AddPassenger(vc); + + vc->m_movementInfo.transport.pos.Relocate(vehpos); + vc->Relocate(x, y, z, o); + vc->SetHomePosition(x, y, z, o); + vc->SetTransportHomePosition(vehpos); + + vc->AddUnitState(UNIT_STATE_IGNORE_PATHFINDING); + vc->InitStats(0); + ASSERT(map->AddToMap(vc->ToCreature())); + vc->InitSummon(); //not needed really + vc->SetTempSummonType(TEMPSUMMON_CORPSE_DESPAWN); + //vc = me->GetTransport()->SummonPassenger(creEntry, vehpos, TEMPSUMMON_CORPSE_DESPAWN); + } + + return vc; +} + +void bot_ai::ChooseVehicleForEncounter(uint32 &creEntry, uint32 &vehEntry) const +{ + Vehicle* mVeh = master->GetVehicle(); + ASSERT_NODEBUGINFO(mVeh); + ASSERT_NODEBUGINFO(mVeh->GetBase()->GetTypeId() == TYPEID_UNIT); + + vehEntry = 0; // will be chosen at creature spawn + switch (mVeh->GetBase()->GetEntry()) + { + //TODO + case CREATURE_NEXUS_SKYTALON_1: + case CREATURE_EOE_SKYTALON_N: + case CREATURE_EOE_SKYTALON_H: + case CREATURE_ULDUAR_DEMOLISHER: + case CREATURE_ULDUAR_SIEGE_ENGINE: + case CREATURE_ULDUAR_CHOPPER: + case CREATURE_ULDUAR_CHOPPER1: + //case CREATURE_TOC_STEED_QUELDOREI: + //case CREATURE_TOC_HAWKSTRIDER_SUNREAVER: + case CREATURE_TOC5_WARHORSE: + case CREATURE_TOC5_BATTLEWORG: + creEntry = mVeh->GetBase()->GetEntry(); + break; + case CREATURE_OCULUS_DRAKE_RUBY: + case CREATURE_OCULUS_DRAKE_EMERALD: + case CREATURE_OCULUS_DRAKE_AMBER: + creEntry = (HasRole(BOT_ROLE_TANK)/* && mVeh->GetBase()->GetEntry() != CREATURE_OCULUS_DRAKE_RUBY*/) ? CREATURE_OCULUS_DRAKE_RUBY : + (HasRole(BOT_ROLE_HEAL)/* && mVeh->GetBase()->GetEntry() != CREATURE_OCULUS_DRAKE_EMERALD*/) ? CREATURE_OCULUS_DRAKE_EMERALD : + CREATURE_OCULUS_DRAKE_AMBER; + break; + //case CREATURE_TOC_NIGHTSABER: + //case CREATURE_TOC_STEED_STORMWIND: + //case CREATURE_TOC_MECHANOSTRIDER: + //case CREATURE_TOC_RAM: + //case CREATURE_TOC_ELEKK: + //case CREATURE_TOC_RAPTOR: + //case CREATURE_TOC_WARHORSE: + //case CREATURE_TOC_WOLF: + //case CREATURE_TOC_HAWKSTRIDER_SILVERMOON: + //case CREATURE_TOC_KODO: + // switch (GetPlayerRace()) + // { + // case RACE_HUMAN: creEntry = CREATURE_TOC_STEED_STORMWIND; break; + // case RACE_ORC: creEntry = CREATURE_TOC_WOLF; break; + // case RACE_DWARF: creEntry = CREATURE_TOC_RAM; break; + // case RACE_NIGHTELF: creEntry = CREATURE_TOC_NIGHTSABER; break; + // case RACE_UNDEAD_PLAYER: creEntry = CREATURE_TOC_WARHORSE; break; + // case RACE_TAUREN: creEntry = CREATURE_TOC_KODO; break; + // case RACE_GNOME: creEntry = CREATURE_TOC_MECHANOSTRIDER; break; + // case RACE_TROLL: creEntry = CREATURE_TOC_RAPTOR; break; + // case RACE_BLOODELF: creEntry = CREATURE_TOC_HAWKSTRIDER_SILVERMOON; break; + // case RACE_DRAENEI: creEntry = CREATURE_TOC_ELEKK; break; + // default: creEntry = CREATURE_TOC_STEED_QUELDOREI; break; + // } + // break; + case CREATURE_ICC_GUNSHIPCANNON_ALLIANCE: + case CREATURE_ICC_GUNSHIPCANNON_HORDE: + //limited amount of cannons + if (!IsTank() && HasRole(BOT_ROLE_DPS) && + master->GetBotMgr()->GetNpcBotsCountByVehicleEntry(mVeh->GetBase()->GetEntry()) < + std::max(master->GetBotMgr()->GetNpcBotsCount() / 2, 8)) + creEntry = mVeh->GetBase()->GetEntry(); + break; + case CREATURE_ICC_MUTATED_ABOMINATION1: + case CREATURE_ICC_MUTATED_ABOMINATION2: + case CREATURE_ICC_MUTATED_ABOMINATION3: + case CREATURE_ICC_MUTATED_ABOMINATION4: + case CREATURE_ICC_MUTATED_ABOMINATION5: + case CREATURE_ICC_MUTATED_ABOMINATION6: + case CREATURE_ICC_MUTATED_ABOMINATION7: + case CREATURE_ICC_MUTATED_ABOMINATION8: + //no abomination bots + break; + default: + if (VehicleSeatEntry const* seat = mVeh->GetSeatForPassenger(master)) + { + if (seat->Flags & VEHICLE_SEAT_FLAG_CAN_CONTROL) + { + //can use generic strat for that + creEntry = mVeh->GetBase()->GetEntry(); + break; + } + } + + TC_LOG_ERROR("scripts", "ChooseVehicleForEncounter: unhandled master vehicle creature {} ({})", + master->GetVehicleBase()->GetName(), master->GetVehicleBase()->GetEntry()); + return; + } + + if (creEntry && creEntry != mVeh->GetBase()->GetEntry()) + { + CreatureTemplate const* cProto = sObjectMgr->GetCreatureTemplate(creEntry); + ASSERT_NODEBUGINFO(cProto); + vehEntry = cProto->VehicleId; + ASSERT_NODEBUGINFO(sVehicleStore.LookupEntry(vehEntry)); + } +} + +Position bot_ai::GetAbsoluteTransportPosition(WorldObject const* object) +{ + if (!object->GetTransport()) + return object->GetPosition(); + + Position p = object->GetTransport()->GetPosition(); + Position t = object->GetTransOffset(); + t.m_positionX += p.m_positionX; + t.m_positionY += p.m_positionY; + t.m_positionZ += p.m_positionZ; + t.SetOrientation(Position::NormalizeOrientation(t.GetOrientation() + p.GetOrientation())); + + return t; +} + +int32 bot_ai::GetBotResistanceBonus(SpellSchoolMask mask) const +{ + int32 resist = 0; + for (uint8 i = SPELL_SCHOOL_HOLY; i != MAX_SPELL_SCHOOL; ++i) + if ((mask & (1 << i)) && (resist == 0 || resist > resistbonus[i-1])) + resist = resistbonus[i-1]; + + return resist; +} + +MeleeHitOutcome bot_ai::BotRollCustomMeleeOutcomeAgainst(Unit const* victim, WeaponAttackType attType) const +{ + if (GetNextAttackMeleeOutCome() != MELEE_HIT_CRUSHING) + return GetNextAttackMeleeOutCome(); + return me->RollMeleeOutcomeAgainst(victim, attType); +} + +void bot_ai::BotJumpInPlaceInFrontOf(Position const* pos, float speedXY, float maxHeight) +{ + float sign = (me->GetPositionX() < pos->GetPositionX()) ? 1.f : -1.f; + float x = me->GetPositionX() + 0.14f * sign; + sign = (me->GetPositionY() < pos->GetPositionY()) ? 1.f : -1.f; + float y = me->GetPositionY() + 0.14f * sign; + float z = me->GetPositionZ() - 0.01f; + //float floorz = Map::GetHeight(x, y, z, true, 5.f); + speedXY = std::max(speedXY, speedXY / me->m_modAttackSpeedPct[BASE_ATTACK]); + + //me->AttackStop(); + //me->BotStopMovement(); + me->GetMotionMaster()->MoveJump(x, y, z, me->GetOrientation(), speedXY, maxHeight); +} + +void bot_ai::DismountBot() +{ + const_cast(me->GetCreatureTemplate())->Movement.Flight = CreatureFlightMovementType::None; + me->SetCanFly(false); + me->m_movementInfo.RemoveMovementFlag(MOVEMENTFLAG_HOVER | MOVEMENTFLAG_CAN_FLY); + me->SetDisableGravity(false); + me->RemoveAurasByType(SPELL_AURA_MOUNTED); + me->Dismount(); + me->BotStopMovement(); +} + +//DPS TRACKER +uint32 bot_ai::GetDPSTaken(Unit const* u) const +{ + return IAmFree() ? 0 : master->GetBotMgr()->GetDPSTaken(u); +} +int32 bot_ai::GetHPSTaken(Unit const* u) const +{ + return IAmFree() ? 0 : master->GetBotMgr()->GetHPSTaken(u); +} +//Health per second +int32 bot_ai::GetHPS(Unit const* u) const +{ + return IAmFree() ? 0 : GetHPSTaken(u) - GetDPSTaken(u); +} +//Health percent per second +int32 bot_ai::GetHPPCTPS(Unit const* u) const +{ + return int32(GetHPS(u) * 100.f / float(u->GetMaxHealth())); +} +//%health unit is going to have after x ms +//0-100 +uint8 bot_ai::GetExpectedHPPCT(Unit const* u, uint32 mseconds) const +{ + if (IAmFree()) + return GetHealthPCT(u); + + int32 pct = int32(GetHealthPCT(u)) + int32(GetHPPCTPS(u) * (mseconds * 0.001f)); + + if (pct > 100) + pct = 100; + else if (pct < 0) + pct = 0; + + return uint8(pct); +} + +//Moved from header +bool bot_ai::IsChanneling(Unit const* u/* = nullptr*/) const +{ + if (!u) + u = me; + return u->GetCurrentSpell(CURRENT_CHANNELED_SPELL); +} +bool bot_ai::IsCasting(Unit const* u/* = nullptr*/) const +{ + if (!u) + u = me; + return (u->HasUnitState(UNIT_STATE_CASTING) || IsChanneling(u) || u->IsNonMeleeSpellCast(false, false, true, false, false)); +} +bool bot_ai::JumpingFlyingOrFalling() const +{ + return Jumping() || JumpingOrFalling() || me->HasUnitMovementFlag(MOVEMENTFLAG_SPLINE_ELEVATION); +} +bool bot_ai::JumpingOrFalling() const +{ + return Jumping() || me->IsFalling() || me->HasUnitMovementFlag(MOVEMENTFLAG_PITCH_UP|MOVEMENTFLAG_PITCH_DOWN|MOVEMENTFLAG_FALLING_SLOW); +} +bool bot_ai::Jumping() const +{ + return me->HasUnitState(UNIT_STATE_JUMPING); +} +bool bot_ai::IsIndoors() const +{ + return indoorsTimer >= INOUTDOORS_ENSURE_TIMER && outdoorsTimer == 0; +} +bool bot_ai::IsOutdoors() const +{ + return outdoorsTimer >= INOUTDOORS_ENSURE_TIMER && indoorsTimer == 0; +} +bool bot_ai::IsInContactWithWater() const +{ + return me->IsInWorld() && + (me->GetMap()->GetLiquidStatus(me->GetPhaseMask(), me->GetPositionX(), me->GetPositionY(), me->GetPositionZ(), + MAP_LIQUID_TYPE_WATER | MAP_LIQUID_TYPE_OCEAN) & MAP_LIQUID_STATUS_IN_CONTACT); +} + +bool bot_ai::IsTempBot() const +{ + return me->GetEntry() == BOT_ENTRY_MIRROR_IMAGE_BM; +} + +uint32 bot_ai::GetLostHP(Unit const* unit) +{ + return unit->GetMaxHealth() - unit->GetHealth(); +} +uint8 bot_ai::GetHealthPCT(Unit const* u) +{ + if (!u || !u->IsAlive() || u->GetMaxHealth() <= 1) + return 100; + return uint8(((float(u->GetHealth()))/u->GetMaxHealth()) * 100); +} +uint8 bot_ai::GetManaPCT(Unit const* u) +{ + if (!u || !u->IsAlive() || u->GetMaxPower(POWER_MANA) <= 1) + return 100; + return (u->GetPower(POWER_MANA)*10/(1 + u->GetMaxPower(POWER_MANA)/10)); +} + +MeleeHitOutcome bot_ai::GetNextAttackMeleeOutCome() const +{ + return MELEE_HIT_CRUSHING; +} + +uint8 bot_ai::GetBotStance() const +{ + return BOT_STANCE_NONE; +} + +uint8 bot_ai::GetPlayerClass() const +{ + return BotMgr::GetBotPlayerClass(_botclass); +} +uint8 bot_ai::GetPlayerRace() const +{ + return BotMgr::GetBotPlayerRace(_botclass, me->GetRace()); +} + +uint8 bot_ai::GetBotComboPoints() const +{ + return me->GetVehicle() ? vehcomboPoints : uint8(GetAIMiscValue(BOTAI_MISC_COMBO_POINTS)); +} + +float bot_ai::GetBotAmmoDPS() const +{ + if (CanUseAmmo()) + { + for (int8 i = MAX_AMMO_LEVEL - 1; i >= 0; --i) + if (me->GetLevel() >= AmmoDPSForLevel[i][0]) + return float(AmmoDPSForLevel[i][1]); + + return float(AmmoDPSForLevel[0][1]); + } + + return 0.0f; +} + +uint32 bot_ai::GetPetOriginalEntry(uint32 entry) +{ + switch (entry) + { + case BOT_PET_IMP: + return ORIGINAL_ENTRY_IMP; + case BOT_PET_VOIDWALKER: + return ORIGINAL_ENTRY_VOIDWALKER; + case BOT_PET_SUCCUBUS: + return ORIGINAL_ENTRY_SUCCUBUS; + case BOT_PET_FELHUNTER: + return ORIGINAL_ENTRY_FELHUNTER; + case BOT_PET_FELGUARD: + return ORIGINAL_ENTRY_FELGUARD; + case BOT_PET_WATER_ELEMENTAL: + return ORIGINAL_ENTRY_WATER_ELEMENTAL; + case BOT_PET_GHOUL: + //doesn't have pet template + //return ORIGINAL_ENTRY_GHOUL; + case BOT_PET_SHADOWFIEND: + //return ORIGINAL_ENTRY_SHADOWFIEND; + case BOT_PET_SPIRIT_WOLF: + //return ORIGINAL_ENTRY_SPIRIT_WOLF; + case BOT_PET_FORCE_OF_NATURE: + //return ORIGINAL_ENTRY_FORCE_OF_NATURE; + default: + return ORIGINAL_ENTRY_HUNTER_PET; + } +} + +bool bot_ai::IsPetMelee(uint32 entry) +{ + switch (entry) + { + case BOT_PET_IMP: + case BOT_PET_WATER_ELEMENTAL: + case BOT_PET_AWATER_ELEMENTAL: + return false; + default: + return true; + } +} + +bool bot_ai::IsMeleeClass(uint8 m_class) +{ + return + (m_class == CLASS_WARRIOR || m_class == CLASS_ROGUE || m_class == CLASS_PALADIN || + m_class == CLASS_DEATH_KNIGHT || m_class == BOT_CLASS_BM || m_class == BOT_CLASS_DREADLORD || + m_class == BOT_CLASS_SPELLBREAKER || m_class == BOT_CLASS_CRYPT_LORD); +} +bool bot_ai::IsTankingClass(uint8 m_class) +{ + return (m_class == CLASS_WARRIOR || m_class == CLASS_PALADIN || + m_class == CLASS_DEATH_KNIGHT || m_class == BOT_CLASS_SPHYNX || + m_class == BOT_CLASS_SPELLBREAKER || m_class == BOT_CLASS_CRYPT_LORD); +} +bool bot_ai::IsBlockingClass(uint8 m_class) +{ + return (m_class == CLASS_WARRIOR || m_class == CLASS_PALADIN || m_class == CLASS_SHAMAN || + m_class == BOT_CLASS_SPELLBREAKER); +} +bool bot_ai::IsCastingClass(uint8 m_class) +{ + //Class can benefit from spellpower + return (m_class == CLASS_PALADIN || m_class == CLASS_PRIEST || m_class == CLASS_SHAMAN || + m_class == CLASS_MAGE || m_class == CLASS_WARLOCK || m_class == CLASS_DRUID || + m_class == BOT_CLASS_SPHYNX || m_class == BOT_CLASS_ARCHMAGE || m_class == BOT_CLASS_DREADLORD || + m_class == BOT_CLASS_SPELLBREAKER || m_class == BOT_CLASS_DARK_RANGER || m_class == BOT_CLASS_NECROMANCER || + m_class == BOT_CLASS_SEA_WITCH); +} +bool bot_ai::IsHealingClass(uint8 m_class) +{ + return + (m_class == BOT_CLASS_PRIEST || m_class == BOT_CLASS_DRUID || + m_class == BOT_CLASS_SHAMAN || m_class == BOT_CLASS_PALADIN || + m_class == BOT_CLASS_SPHYNX); +} +bool bot_ai::IsHumanoidClass(uint8 m_class) +{ + return m_class != BOT_CLASS_SPHYNX; +} +bool bot_ai::IsHeroExClass(uint8 m_class) +{ + return m_class == BOT_CLASS_BM || m_class == BOT_CLASS_ARCHMAGE || m_class == BOT_CLASS_DREADLORD || + m_class == BOT_CLASS_DARK_RANGER || m_class == BOT_CLASS_SEA_WITCH || m_class == BOT_CLASS_CRYPT_LORD; +} +bool bot_ai::IsMelee() const +{ + return !IsRanged() && HasRole(BOT_ROLE_DPS|BOT_ROLE_TANK); +} +bool bot_ai::IsRanged() const +{ + return HasRole(BOT_ROLE_RANGED) || HasVehicleRoleOverride(BOT_ROLE_RANGED); +} + +bool bot_ai::IsShootingWand(Unit const* u) const +{ + if (!u) u = me; + + Spell const* spell = u->GetCurrentSpell(CURRENT_AUTOREPEAT_SPELL); + return spell && spell->GetSpellInfo()->Id == SHOOT_WAND; +} + +void bot_ai::StartPotionTimer() +{ + _potionTimer = POTION_CD * (BotMgr::IsWanderingWorldBot(me) ? std::max(uint32(Rand()) >> 3, 1u) : 1u); +} + +bool bot_ai::CanBlock() const +{ + return me->CanUseAttackType(OFF_ATTACK) && + (_botclass == BOT_CLASS_SPELLBREAKER || !(me->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_NO_BLOCK)); +} +bool bot_ai::CanParry() const +{ + if (me->GetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID) && me->CanUseAttackType(BASE_ATTACK)) + { + switch (_botclass) + { + case BOT_CLASS_SPHYNX: + case BOT_CLASS_SPELLBREAKER: + case BOT_CLASS_SEA_WITCH: + return true; + case BOT_CLASS_WARRIOR: + case BOT_CLASS_PALADIN: + case BOT_CLASS_ROGUE: + case BOT_CLASS_HUNTER: + return me->GetLevel() >= 10; + case BOT_CLASS_SHAMAN: + return me->GetLevel() >= 30; + case BOT_CLASS_DEATH_KNIGHT: + return me->GetLevel() >= 55; + default: + break; + } + } + return false; +} +bool bot_ai::CanDodge() const +{ + return _botclass != BOT_CLASS_BM; +} +bool bot_ai::CanCrit() const +{ + return _botclass != BOT_CLASS_BM; +} +bool bot_ai::CanMiss() const +{ + return _botclass < BOT_CLASS_EX_START; +} +bool bot_ai::CanSheath() const +{ + return _botclass < BOT_CLASS_EX_START; +} +bool bot_ai::CanSit() const +{ + return _botclass < BOT_CLASS_EX_START || _botclass == BOT_CLASS_DARK_RANGER; +} +bool bot_ai::CanEat() const +{ + return _botclass != BOT_CLASS_SPHYNX; +} +bool bot_ai::CanDrink() const +{ + return _botclass < BOT_CLASS_EX_START; +} +bool bot_ai::CanRegenInCombat() const +{ + return _botclass == BOT_CLASS_SPHYNX; +} +bool bot_ai::CanMount() const +{ + switch (_botclass) + { + case BOT_CLASS_BM: + case BOT_CLASS_SPELLBREAKER: + case BOT_CLASS_DARK_RANGER: + case BOT_CLASS_NECROMANCER: + return true; + default: + return _botclass < BOT_CLASS_EX_START; + } +} +bool bot_ai::CanUseAmmo() const +{ + if ((_botclass == BOT_CLASS_HUNTER || _botclass == BOT_CLASS_ROGUE || + _botclass == BOT_CLASS_WARRIOR || _botclass == BOT_CLASS_DARK_RANGER || + _botclass == BOT_CLASS_SEA_WITCH) && + _equips[BOT_SLOT_RANGED]) + { + ItemTemplate const* ranged = _equips[BOT_SLOT_RANGED]->GetTemplate(); + if (ranged->Class == ITEM_CLASS_WEAPON && + (ranged->SubClass == ITEM_SUBCLASS_WEAPON_BOW || + ranged->SubClass == ITEM_SUBCLASS_WEAPON_CROSSBOW || + ranged->SubClass == ITEM_SUBCLASS_WEAPON_GUN)) + return true; + } + return false; +} + +bool bot_ai::RespectEquipsAttackTime() const +{ + return _botclass < BOT_CLASS_EX_START; +} +bool bot_ai::CanChangeEquip(uint8 slot) const +{ + return (_botclass != BOT_CLASS_BM && _botclass != BOT_CLASS_ARCHMAGE && + _botclass != BOT_CLASS_DREADLORD && _botclass != BOT_CLASS_SPELLBREAKER && + _botclass != BOT_CLASS_DARK_RANGER && _botclass != BOT_CLASS_NECROMANCER && + _botclass != BOT_CLASS_SEA_WITCH && _botclass != BOT_CLASS_CRYPT_LORD) || + slot > BOT_SLOT_RANGED; +} +bool bot_ai::CanDisplayNonWeaponEquipmentChanges() const +{ + return (_botclass < BOT_CLASS_EX_START || _botclass == BOT_CLASS_ARCHMAGE); +} +bool bot_ai::IsValidTransmog(uint8 slot, ItemTemplate const* source) const +{ + ASSERT(slot < BOT_TRANSMOG_INVENTORY_SIZE); + + if (!CanChangeEquip(slot)) + return false; + + Item const* item = _equips[slot]; + if (!item) + return false; + + ItemTemplate const* target = item->GetTemplate(); + + if (target->ItemId == source->ItemId) + return false; + if (target->Class != source->Class) + return false; + + switch (target->InventoryType) + { + case INVTYPE_RELIC: + case INVTYPE_NECK: + case INVTYPE_FINGER: + case INVTYPE_TRINKET: + case INVTYPE_THROWN: + return false; + default: + break; + } + switch (source->InventoryType) + { + case INVTYPE_RELIC: + case INVTYPE_NECK: + case INVTYPE_FINGER: + case INVTYPE_TRINKET: + case INVTYPE_THROWN: + case INVTYPE_BAG: + case INVTYPE_AMMO: + case INVTYPE_QUIVER: + case INVTYPE_NON_EQUIP: + return false; + default: + break; + } + + if (target->SubClass != source->SubClass) + { + if (target->Class == ITEM_CLASS_WEAPON && !BotMgr::MixWeaponClasses()) + return false; + if (target->Class == ITEM_CLASS_ARMOR && !BotMgr::MixArmorClasses()) + return false; + } + + if (target->InventoryType != source->InventoryType) + { + if (target->Class == ITEM_CLASS_ARMOR) + { + if (!((target->InventoryType == INVTYPE_ROBE || target->InventoryType == INVTYPE_CHEST) && + (source->InventoryType == INVTYPE_ROBE || source->InventoryType == INVTYPE_CHEST))) + return false; + } + if (target->Class == ITEM_CLASS_WEAPON && !BotMgr::MixWeaponInventoryTypes()) + return false; + } + + NpcBotTransmogData const* transmogData = BotDataMgr::SelectNpcBotTransmogs(me->GetEntry()); + if (transmogData && transmogData->transmogs[slot].second == int32(source->ItemId)) + return false; + + return true; +} + +bool bot_ai::OnGossipHello(Player* player) +{ + return OnGossipHello(player, 0); +} +bool bot_ai::OnGossipSelect(Player* player, uint32 /*menuId*/, uint32 gossipListId) +{ + uint32 sender = player->PlayerTalkClass->GetGossipOptionSender(gossipListId); + uint32 action = player->PlayerTalkClass->GetGossipOptionAction(gossipListId); + return OnGossipSelect(player, me, sender, action); +} +bool bot_ai::OnGossipSelectCode(Player* player, uint32 /*menuId*/, uint32 gossipListId, char const* code) +{ + uint32 sender = player->PlayerTalkClass->GetGossipOptionSender(gossipListId); + uint32 action = player->PlayerTalkClass->GetGossipOptionAction(gossipListId); + return OnGossipSelectCode(player, me, sender, action, code); +} + +bool bot_ai::IsDamagingSpell(SpellInfo const* spellInfo) +{ + for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) + { + if (spellInfo->_effects[i].IsEffect()) + { + switch (spellInfo->_effects[i].Effect) + { + case SPELL_EFFECT_WEAPON_DAMAGE: + case SPELL_EFFECT_WEAPON_DAMAGE_NOSCHOOL: + case SPELL_EFFECT_NORMALIZED_WEAPON_DMG: + case SPELL_EFFECT_WEAPON_PERCENT_DAMAGE: + case SPELL_EFFECT_SCHOOL_DAMAGE: + case SPELL_EFFECT_ENVIRONMENTAL_DAMAGE: + case SPELL_EFFECT_HEALTH_LEECH: + return true; + default: + break; + } + } + } + + return false; +} + +bool bot_ai::IsImmunedToMySpellEffect(Unit const* unit, SpellInfo const* spellInfo, SpellEffIndex index) const +{ + return unit->IsImmunedToSpellEffect(spellInfo, spellInfo->GetEffect(index), me); +} + +//CONTESTED PVP +bool bot_ai::IsContestedPvP() const +{ + return me->HasUnitState(UNIT_STATE_ATTACK_PLAYER); +} +void bot_ai::SetContestedPvP() +{ + _contestedPvPTimer = 30000; + if (!me->HasUnitState(UNIT_STATE_ATTACK_PLAYER)) + { + me->AddUnitState(UNIT_STATE_ATTACK_PLAYER); + Trinity::AIRelocationNotifier notifier(*me); + Cell::VisitWorldObjects(me, notifier, me->GetVisibilityRange()); + } + if (botPet && !botPet->HasUnitState(UNIT_STATE_ATTACK_PLAYER)) + { + botPet->AddUnitState(UNIT_STATE_ATTACK_PLAYER); + Trinity::AIRelocationNotifier notifier(*botPet); + Cell::VisitWorldObjects(me, notifier, me->GetVisibilityRange()); + } +} +void bot_ai::ResetContestedPvP() +{ + _contestedPvPTimer = 0; + me->ClearUnitState(UNIT_STATE_ATTACK_PLAYER); + if (botPet && botPet->HasUnitState(UNIT_STATE_ATTACK_PLAYER)) + botPet->ClearUnitState(UNIT_STATE_ATTACK_PLAYER); +} +void bot_ai::UpdateContestedPvP() +{ + if (_contestedPvPTimer > 0 && _contestedPvPTimer <= lastdiff && !me->IsInCombat()) + ResetContestedPvP(); +} + +void bot_ai::SetGroup(Group* group, int8 subgroup) +{ + if (group == nullptr) + _group.unlink(); + else + { + // never use SetGroup without a subgroup unless you specify NULL for group + ASSERT(subgroup >= 0); + _group.link(group, me); + _group.setSubGroup((uint8)subgroup); + } + + me->UpdateObjectVisibility(false); +} +void bot_ai::SetBattlegroundOrBattlefieldRaid(Group* group, int8 subgroup) +{ + SetOriginalGroup(GetGroup(), GetSubGroup()); + _group.unlink(); + _group.link(group, me); + _group.setSubGroup((uint8)subgroup); +} +void bot_ai::RemoveFromBattlegroundOrBattlefieldRaid() +{ + _group.unlink(); + if (Group* group = GetOriginalGroup()) + { + _group.link(group, me); + _group.setSubGroup(GetOriginalSubGroup()); + } + SetOriginalGroup(nullptr, -1); +} +void bot_ai::SetOriginalGroup(Group* group, int8 subgroup) +{ + if (group == nullptr) + _originalGroup.unlink(); + else + { + ASSERT(subgroup >= 0); + _originalGroup.link(group, me); + _originalGroup.setSubGroup((uint8)subgroup); + } +} + +void bot_ai::SendUpdateToOutOfRangeBotGroupMembers() +{ + _groupUpdateTimer = BOT_GROUP_UPDATE_TIMER; + + if (_groupUpdateMask == GROUP_UPDATE_FLAG_NONE) + return; + if (Group* group = GetGroup()) + group->UpdateBotOutOfRange(me); + + _groupUpdateMask = GROUP_UPDATE_FLAG_NONE; + _auraRaidUpdateMask = 0; + if (botPet) + botPet->GetBotPetAI()->ResetAuraUpdateMaskForRaid(); +} + +//BATTLEGROUNDS +bool bot_ai::IsFlagCarrier(Unit const* unit, BattlegroundTypeId bgTypeId) +{ + if (unit->IsInWorld() && unit->GetMap()->IsBattleground() && unit->HasAuraType(SPELL_AURA_EFFECT_IMMUNITY)) + { + uint32 spellId = unit->GetAuraEffectsByType(SPELL_AURA_EFFECT_IMMUNITY).front()->GetBase()->GetId(); + switch (bgTypeId) + { + case BATTLEGROUND_TYPE_NONE: //must contain all possible checks + switch (spellId) + { + case 23333: // Warsong Flag (WSG) + case 23335: // Silverwing Flag (WSG) + return true; + default: + break; + } + break; + case BATTLEGROUND_AV: + break; + case BATTLEGROUND_WS: + switch (spellId) + { + case 23333: // Warsong Flag (WSG) + case 23335: // Silverwing Flag (WSG) + return true; + default: + break; + } + break; + case BATTLEGROUND_AB: + case BATTLEGROUND_EY: + case BATTLEGROUND_SA: + case BATTLEGROUND_IC: + default: + break; + } + } + + return false; +} + +#ifdef _MSC_VER +# pragma warning(pop) +#endif diff --git a/src/server/game/AI/NpcBots/bot_ai.h b/src/server/game/AI/NpcBots/bot_ai.h new file mode 100644 index 000000000..07cc65dc6 --- /dev/null +++ b/src/server/game/AI/NpcBots/bot_ai.h @@ -0,0 +1,828 @@ +#ifndef _BOT_AI_H +#define _BOT_AI_H + +#include "botcommon.h" + +#include "CreatureAI.h" +#include "EventProcessor.h" +#include "GroupReference.h" +#include "ItemDefines.h" +#include "Position.h" + +#include +#include + +/* +NpcBot System by Trickerer (onlysuffering@gmail.com) +*/ + +class TeleportHomeEvent; +class TeleportFinishEvent; +class AwaitStateRemovalEvent; + +enum CombatRating : uint8; +enum EnchantmentSlot : uint16; +enum GossipOptionIcon : uint8; +enum MeleeHitOutcome : uint8; + +struct CleanDamage; +struct CalcDamageInfo; +struct ItemTemplate; +struct PlayerClassLevelInfo; +struct SpellNonMeleeDamage; + +class Aura; +class Battleground; +class DamageInfo; +class GameObject; +class Group; +class Item; +class Spell; +class SpellCastTargets; +class Unit; +class Vehicle; +class WanderNode; + +class bot_ai : public CreatureAI +{ + public: + virtual ~bot_ai(); + + bool canUpdate; + + void InitializeAI() override; + //void Reset() override { } + + void JustDied(Unit*) override; + void KilledUnit(Unit* u) override; + void AttackStart(Unit* u) override; + void JustEnteredCombat(Unit* u) override; + void MoveInLineOfSight(Unit* u) override; + void DamageDealt(Unit* victim, uint32& damage, DamageEffectType damageType) override; + //void DamageTaken(Unit* /*attacker*/, uint32& /*damage*/, DamageEffectType /*damageType*/, SpellInfo const* /*spellInfo*/) override { } + void ReceiveEmote(Player* player, uint32 emote) override; + void EnterEvadeMode(EvadeReason/* why*/ = EVADE_REASON_OTHER) override { } + //void LeavingWorld() override { } + void OnSpellStart(SpellInfo const* spellInfo) override { OnBotSpellStart(spellInfo); } + void OnDeath(Unit* attacker = nullptr); + //bool CanRespawn() override { return IAmFree(); } + + virtual void OnBotSummon(Creature* /*summon*/) {} + virtual void OnBotDespawn(Creature* /*summon*/) {} + + virtual void UnsummonAll(bool /*savePets*/ = true) {} + void UnsummonCreature(Creature* creature, bool save); + void UnsummonPet(bool save); + template + void UnsummonCreatures(C const& container, bool save) + { + C c2 = container; // copy; original container might get modified from within the loop + for (auto c : c2) + UnsummonCreature(c, save); + } + + virtual void OnBotDamageTaken(Unit* /*attacker*/, uint32 /*damage*/, CleanDamage const* /*cleanDamage*/, DamageEffectType /*damagetype*/, SpellInfo const* /*spellInfo*/) {} + virtual void OnBotDamageDealt(Unit* /*victim*/, uint32 /*damage*/, CleanDamage const* /*cleanDamage*/, DamageEffectType /*damagetype*/, SpellInfo const* /*spellInfo*/) {} + virtual void OnBotDispelDealt(Unit* /*dispelled*/, uint8 /*num*/) {} + + bool OnGossipHello(Player* player) override; + bool OnGossipSelect(Player* player, uint32 menuId, uint32 gossipListId) override; + bool OnGossipSelectCode(Player* player, uint32 menuId, uint32 gossipListId, char const* code) override; + + virtual void OnBotEnterVehicle(Vehicle const* /*vehicle*/); + virtual void OnBotExitVehicle(Vehicle const* /*vehicle*/); + virtual void AfterBotOwnerEnterVehicle(); + virtual void OnBotOwnerEnterVehicle(Vehicle const* /*vehicle*/); + virtual void OnBotOwnerExitVehicle(Vehicle const* /*vehicle*/); + + Unit* SpawnVehicle(uint32 creEntry, uint32 vehEntry); + void ChooseVehicleForEncounter(uint32 &creEntry, uint32 &vehEntry) const; + + static Position GetAbsoluteTransportPosition(WorldObject const* object); + + static const std::string& LocalizedNpcText(Player const* forPlayer, uint32 textId); + + bool OnGossipHello(Player* player, uint32 option); + bool OnGossipSelect(Player* player, Creature* creature, uint32 sender, uint32 action); + bool OnGossipSelectCode(Player* player, Creature* creature, uint32 sender, uint32 action, char const* code); + + Creature* GetBotsPet() const { return botPet; } + + void Evade(); + void GetNextEvadeMovePoint(Position& pos, bool& use_path) const; + + EventProcessor* GetEvents() { return &Events; } + ObjectGuid::LowType GetBotOwnerGuid() const { return _ownerGuid; } + Player* GetBotOwner() const { return master; } + bool SetBotOwner(Player* newowner); + void CheckOwnerExpiry(); + uint8 GetBotClass() const { return _botclass; } + uint32 GetLastDiff() const { return lastdiff; } + virtual void UpdateDeadAI(uint32 diff); + void ReturnHome() { _atHome = false; } + void CommonTimers(uint32 diff); + void ResetBotAI(uint8 resetType); + void KillEvents(bool force); + void BotMovement(BotMovementType type, Position const* pos, Unit* target = nullptr, bool generatePath = true, float speed = 0.0f) const; + bool CanBotMoveVehicle() const; + void MoveToSendPosition(uint32 point_id); + void MoveToSendPosition(Position const& mpos); + void MoveToLastSendPosition() { MoveToSendPosition(sendlastpos); } + void MarkSendPosition(uint32 point_id); + void SetBotCommandState(uint32 st, bool force = false, Position* newpos = nullptr, float* speed = nullptr); + void RemoveBotCommandState(uint32 st); + bool HasBotCommandState(uint32 st) const { return (_botCommandState & st); } + void SetBotAwaitState(uint8 state); + inline void RemoveBotAwaitState(uint8 state) { _botAwaitState &= ~state; } + inline bool HasBotAwaitState(uint8 state) const { return !!(_botAwaitState & state); } + void EventRemoveBotAwaitState(uint8 state); + void AbortAwaitStateRemoval(); + uint32 GetBotCommandState() const { return _botCommandState; } + bool IsInBotParty(Unit const* unit) const; + bool IsInBotParty(ObjectGuid guid) const; + bool CanBotAttack(Unit const* target, int8 byspell = 0, bool secondary = false) const; + bool CanBotAttackOnVehicle() const; + void ApplyBotDamageMultiplierMelee(uint32& damage, CalcDamageInfo& damageinfo) const; + void ApplyBotDamageMultiplierMelee(int32& damage, SpellNonMeleeDamage& damageinfo, SpellInfo const* spellInfo, WeaponAttackType attackType, bool iscrit) const; + void ApplyBotDamageMultiplierSpell(int32& damage, SpellNonMeleeDamage& damageinfo, SpellInfo const* spellInfo, WeaponAttackType attackType, bool iscrit) const; + void ApplyBotDamageMultiplierHeal(Unit const* victim, float& heal, SpellInfo const* spellInfo, DamageEffectType damagetype, uint32 stack) const; + void ApplyBotCritMultiplierAll(Unit const* victim, float& crit_chance, SpellInfo const* spellInfo, SpellSchoolMask schoolMask, WeaponAttackType attackType) const; + void ApplyBotSpellCostMods(SpellInfo const* spellInfo, int32& cost) const; + void ApplyBotSpellCastTimeMods(SpellInfo const* spellInfo, int32& casttime) const; + void ApplyBotSpellCooldownMods(SpellInfo const* spellInfo, uint32& cooldown) const; + void ApplyBotSpellCategoryCooldownMods(SpellInfo const* spellInfo, uint32& cooldown) const; + void ApplyBotSpellGlobalCooldownMods(SpellInfo const* spellInfo, float& cooldown) const; + void ApplyBotSpellRadiusMods(SpellInfo const* spellInfo, float& radius) const; + void ApplyBotSpellRangeMods(SpellInfo const* spellInfo, float& maxrange) const; + void ApplyBotSpellMaxTargetsMods(SpellInfo const* spellInfo, uint32& targets) const; + void ApplyBotSpellChanceOfSuccessMods(SpellInfo const* spellInfo, float& chance) const; + void ApplyBotEffectMods(SpellInfo const* spellInfo, uint8 effIndex, float& value) const; + void ApplyBotThreatMods(SpellInfo const* spellInfo, float& threat) const; + void ApplyBotEffectValueMultiplierMods(SpellInfo const* spellInfo, SpellEffIndex effIndex, float& multiplier) const; + virtual uint8 GetBotStance() const; + uint32 GetBotRoles() const { return _roleMask; } + bool HasRole(uint32 role) const { return _roleMask & role; } + GossipOptionIcon GetRoleIcon(uint32 role) const; + static uint32 GetRoleString(uint32 role); + void ToggleRole(uint32 role, bool force); + static uint32 DefaultRolesForClass(uint8 m_class, uint8 spec); + bool IsTank(Unit const* unit = nullptr) const; + bool IsOffTank(Unit const* unit = nullptr) const; + + uint32 GetLastZoneId() const { return _lastZoneId; } + bool IsInHeroicOrRaid() const; + + bool IAmFree() const; + + //wandering bots + bool IsWanderer() const { return _wanderer; } + void SetWanderer(); + WanderNode const* GetNextTravelNode(Position const* from, bool random) const; + WanderNode const* GetNextBGTravelNode() const; + void OnWanderNodeReached(); + void OnBotEnterBattleground(); + + Group* GetGroup() { return _group.getTarget(); } + Group const* GetGroup() const { return const_cast(_group.getTarget()); } + void SetGroup(Group* group, int8 subgroup); + uint8 GetSubGroup() const { return _group.getSubGroup(); } + void SetSubGroup(uint8 subgroup) { _group.setSubGroup(subgroup); } + void SetGroupUpdateFlag(uint32 flag) { _groupUpdateMask |= flag; } + uint32 GetGroupUpdateFlag() const { return _groupUpdateMask; } + uint64 GetAuraUpdateMaskForRaid() const { return _auraRaidUpdateMask; } + void SetAuraUpdateMaskForRaid(uint8 slot) { _auraRaidUpdateMask |= (uint64(1) << slot); } + void ResetAuraUpdateMaskForRaid() { _auraRaidUpdateMask = 0; } + void SendUpdateToOutOfRangeBotGroupMembers(); + void SetBattlegroundOrBattlefieldRaid(Group* group, int8 subgroup); + void RemoveFromBattlegroundOrBattlefieldRaid(); + Group* GetOriginalGroup() const { return _originalGroup.getTarget(); } + void SetOriginalGroup(Group* group, int8 subgroup); + uint8 GetOriginalSubGroup() const { return _originalGroup.getSubGroup(); } + void SetOriginalSubGroup(uint8 subgroup) { _originalGroup.setSubGroup(subgroup); } + + Battleground* GetBG() const { return _bg; } + void SetBG(Battleground* bg) { _bg = bg; } + + static bool CCed(Unit const* target, bool root = false); + + void TeleportHomeStart(bool reset); + void TeleportHome(bool reset); + bool FinishTeleport(bool reset); + + bool IsDuringTeleport() const { return teleFinishEvent || teleHomeEvent || _duringTeleport; } + void SetTeleportFinishEvent(TeleportFinishEvent* tfevent) { ASSERT(!teleFinishEvent); teleFinishEvent = tfevent; } + void AbortTeleport(); + void SetIsDuringTeleport(bool value) { _duringTeleport = value; } + + uint8 GetPlayerClass() const; + uint8 GetPlayerRace() const; + + bool IsTempBot() const; + bool CanAppearInWorld() const; + + void SetShouldUpdateStats() { shouldUpdateStats = true; } + void UpdateHealth() { doHealth = true; } + void UpdateMana() { doMana = true; } + + //float GetHitRating() const { return hit; } + int32 GetHaste() const { return haste; } + float GetBotParryChance() const { return parry; } + float GetBotDodgeChance() const { return dodge; } + float GetBotBlockChance() const { return block; } + float GetBotCritChance() const { return crit; } + float GetBotMissChance() const { return -hit; } + float GetBotDamageTakenMod(bool magic) const { return magic ? dmg_taken_mag : dmg_taken_phy; } + float GetBotResilience() const { return resilience; } + uint32 GetBotExpertise() const { return expertise; } + uint32 GetBotSpellPenetration() const { return spellpen; } + uint32 GetBotSpellPower() const { return spellpower; } + uint32 GetBotDefense() const { return defense; } + uint32 GetShieldBlockValue() const { return blockvalue; } + int32 GetBotResistanceBonus(SpellSchoolMask mask) const; + int32 GetBotResistanceBonus(uint8 school) const { return (school > SPELL_SCHOOL_NORMAL && school < MAX_SPELL_SCHOOL) ? resistbonus[school-1] : 0; } + bool CanBlock() const; + bool CanParry() const; + bool CanDodge() const; + bool CanCrit() const; + bool CanMiss() const; + bool CanSheath() const; + bool CanSit() const; + virtual bool CanEat() const; + bool CanDrink() const; + bool CanRegenInCombat() const; + bool CanMount() const; + bool CanUseAmmo() const; + bool RespectEquipsAttackTime() const; + bool CanChangeEquip(uint8 slot) const; + bool CanDisplayNonWeaponEquipmentChanges() const; + bool IsValidTransmog(uint8 slot, ItemTemplate const* source) const; + virtual bool CanSeeEveryone() const { return false; } + virtual float GetBotArmorPenetrationCoef() const { return armor_pen; } + virtual uint32 GetAIMiscValue(uint32 /*data*/) const { return 0; } + virtual void SetAIMiscValue(uint32 /*data*/, uint32 /*value*/) {} + uint8 GetBotComboPoints() const; + float GetBotAmmoDPS() const; + + MeleeHitOutcome BotRollCustomMeleeOutcomeAgainst(Unit const* victim, WeaponAttackType attType) const; + + float GetTotalBotStat(BotStatMods stat) const { return _getTotalBotStat(stat); } + + Item* GetEquips(uint8 slot) const { return _equips[slot]; } + Item* GetEquipsByGuid(ObjectGuid itemGuid) const; + uint32 GetEquipDisplayId(uint8 slot) const; + bool UnEquipAll(ObjectGuid receiver); + bool HasRealEquipment() const; + float GetAverageItemLevel() const; + std::pair GetBotGearScores() const; + + void CastBotItemCombatSpell(DamageInfo const& damageInfo); + void CastBotItemCombatSpell(DamageInfo const& damageInfo, Item* item, ItemTemplate const* proto); + void OnBotSpellStart(SpellInfo const* spellInfo); + void OnBotSpellInterrupted(SpellSchoolMask schoolMask, uint32 unTimeMs); + void OnBotSpellGo(Spell const* spell, bool ok = true); + void OnBotOwnerSpellGo(Spell const* spell, bool ok = true); + void OnBotChannelFinish(Spell const* spell); + void OnOwnerVehicleDamagedBy(Unit* attacker); + virtual void OnClassSpellStart(SpellInfo const* /*spellInfo*/) {} + virtual void OnClassSpellGo(SpellInfo const* /*spell*/) {} + virtual void OnClassChannelFinish(Spell const* /*spell*/) {} + + void SpawnKillReward(Player* looter) const; + void FillKillReward(GameObject* go) const; + + uint32 GetReviveTimer() const { return _reviveTimer; } + void SetReviveTimer(uint32 newtime) { _reviveTimer = newtime; } + void UpdateReviveTimer(uint32 diff); + uint32 GetSelfRezSpell() const { return _selfrez_spell_id; } + + uint32 GetEngageTimer() const { return _engageTimer; } + void ResetEngageTimer(uint32 delay); + + uint8 GetHealHpPctThreshold() const { return _healHpPctThreshold; } + void SetHealHpPctThreshold(uint8 threshold) { _healHpPctThreshold = threshold; } + + bool HasSpell(uint32 basespell) const; + uint32 GetBaseSpell(std::string_view spell_name, LocaleConstant locale) const; + uint32 GetSpellCooldown(uint32 basespell) const; + bool IsSpellReady(uint32 basespell, uint32 diff, bool checkGCD = true) const; + void SetSpellCooldown(uint32 basespell, uint32 msCooldown); + void SetSpellCategoryCooldown(SpellInfo const* spellInfo, uint32 msCooldown); + void ReleaseSpellCooldown(uint32 basespell); + + virtual void SpendRunes(SpellInfo const* /*spellInfo*/, bool /*didHit*/) {} + + void ReInitFaction() { InitFaction(); } + void ReinitOwner() { InitOwner(); } + void SetSpec(uint8 spec, bool activate = true); + uint8 GetSpec() const; + static uint8 SelectSpecForClass(uint8 m_class); + static uint32 TextForSpec(uint8 spec); + static bool IsValidSpecForClass(uint8 m_class, uint8 spec); + + static bool IsMeleeClass(uint8 m_class); + static bool IsTankingClass(uint8 m_class); + static bool IsBlockingClass(uint8 m_class); + static bool IsCastingClass(uint8 m_class); + static bool IsHealingClass(uint8 m_class); + static bool IsHumanoidClass(uint8 m_class); + static bool IsHeroExClass(uint8 m_class); + + AoeSpotsVec const& GetAoeSpots() const; + static void CalculateAoeSpots(Unit const* unit, AoeSpotsVec& spots); + void CalculateAoeSafeSpots(Unit* target, float maxdist, AoeSafeSpotsVec& safespots) const; + + //Pet stuff + static uint32 GetPetOriginalEntry(uint32 entry); + static bool IsPetMelee(uint32 entry); + virtual uint8 GetPetPositionNumber(Creature const* /*summon*/) const { return 0; } + + Unit* HelpFindStunTarget(float dist = 20) const { return FindStunTarget(dist); } + Unit* HelpFindCastingTarget(float maxdist = 10, float mindist = 0, uint32 spellId = 0, uint8 minHpPct = 0) const { return FindCastingTarget(maxdist, mindist, spellId, minHpPct); } + Unit* HelpFindAOETarget(float dist, WorldObject const* src) const { return FindAOETarget(dist, src); } + void HelpGetNearbyTargetsList(std::list &targets, float maxdist, uint8 CCoption, WorldObject const* source = nullptr) const { GetNearbyTargetsList(targets, maxdist, CCoption, source); } + + bool IsPointedTarget(Unit const* target, uint8 targetFlags) const; + bool IsPointedHealTarget(Unit const* target) const; + bool IsPointedTankingTarget(Unit const* target) const; + bool IsPointedOffTankingTarget(Unit const* target) const; + bool IsPointedDPSTarget(Unit const* target) const; + bool IsPointedRangedDPSTarget(Unit const* target) const; + bool IsPointedNoDPSTarget(Unit const* target) const; + bool IsPointedAnyAttackTarget(Unit const* target) const; + + static bool IsDamagingSpell(SpellInfo const* spellInfo); + + bool IsImmunedToMySpellEffect(Unit const* unit, SpellInfo const* spellInfo, SpellEffIndex index) const; + + bool IsContestedPvP() const; + void SetContestedPvP(); + void ResetContestedPvP(); + void UpdateContestedPvP(); + + static bool IsFlagCarrier(Unit const* unit, BattlegroundTypeId bgTypeId = BATTLEGROUND_TYPE_NONE); + + protected: + explicit bot_ai(Creature* creature); + + virtual void ReduceCD(uint32 /*diff*/) {} + bool GlobalUpdate(uint32 diff); + + virtual bool HealTarget(Unit* /*target*/, uint32 /*diff*/) { return false; } + virtual bool BuffTarget(Unit* /*target*/, uint32 /*diff*/) { return false; } + + void BuffAndHealGroup(uint32 diff); + void ResurrectGroup(uint32 REZZ); + void CureGroup(uint32 cureSpell, uint32 diff); + void SetStats(bool force); + void DefaultInit(); + void InitUnitFlags(); // call only in constructor + + void OnOwnerDamagedBy(Unit* attacker); + + static uint32 InitSpell(Unit const* caster, uint32 spell); + void InitSpellMap(uint32 basespell, bool forceadd = false, bool forwardRank = true); + uint32 GetSpell(uint32 basespell) const; + void ResetSpellCooldown(uint32 basespell) { SetSpellCooldown(basespell, 0); } + void RemoveSpell(uint32 basespell); + //void RemoveAllSpells(); + void EnableAllSpells(bool save); + void SpellTimers(uint32 diff); + static uint32 RaceSpellForClass(uint8 myrace, uint8 myclass); + + virtual bool CanUseManually(uint32 /*basespell*/) const { return false; } + virtual bool HasAbilitiesSpecifics() const { return false; } + virtual void FillAbilitiesSpecifics(Player const* /*player*/, std::list &/*specList*/) {} + + virtual std::vector const* GetDamagingSpellsList() const { return nullptr; } + virtual std::vector const* GetCCSpellsList() const { return nullptr; } + virtual std::vector const* GetHealingSpellsList() const { return nullptr; } + virtual std::vector const* GetSupportSpellsList() const { return nullptr; } + + uint32 GetDPSTaken(Unit const* u) const; + int32 GetHPSTaken(Unit const* u) const; + int32 GetHPS(Unit const* u) const; + int32 GetHPPCTPS(Unit const* u) const; + uint8 GetExpectedHPPCT(Unit const* u, uint32 mseconds) const; + + void RefreshAura(uint32 spellId, int8 count = 1, Unit* target = nullptr) const; + bool CheckAttackTarget(); + void MoveBehind(Unit const* target) const; + + void OnStartAttack(Unit const* u); + bool StartAttack(Unit const* u, bool force = false); + + virtual void BreakCC(uint32 diff); + void CheckRacials(uint32 diff); + + void DrinkPotion(bool mana); + bool IsPotionReady() const; + uint32 GetPotion(bool mana) const; + + //everything cast-related + bool doCast(Unit* victim, uint32 spellId, bool triggered = false); + bool doCast(Unit* victim, uint32 spellId, TriggerCastFlags flags); + SpellCastResult CheckBotCast(Unit const* victim, uint32 spellId) const; + virtual bool removeShapeshiftForm() { return true; } + + bool CanRemoveReflectSpells(Unit const* target, uint32 spellId) const; + + bool IsMelee() const; + bool IsRanged() const; + + bool IsShootingWand(Unit const* u = nullptr) const; + + bool IsChanneling(Unit const* u = nullptr) const; + bool IsCasting(Unit const* u = nullptr) const; + bool JumpingFlyingOrFalling() const; + bool JumpingOrFalling() const; + bool Jumping() const; + bool IsIndoors() const; + bool IsOutdoors() const; + bool IsInContactWithWater() const; + + float CalcSpellMaxRange(uint32 spellId, bool enemy = true) const; + + static bool IsPeriodicDynObjAOEDamage(SpellInfo const* spellInfo); + bool IsWithinAoERadius(Position const& pos) const; + + float InitAttackRange(float origRange, bool ranged) const; + void CalculateAttackPos(Unit* target, Position &pos, bool& force) const; + void GetInPosition(bool force, Unit* newtarget, Position* pos = nullptr); + bool AdjustTankingPosition(Unit const* mytarget) const; + virtual float GetSpellAttackRange(bool longRange) const { return longRange ? 23.f : 15.f; } + virtual void CheckAttackState(); + void OnSpellHit(Unit* caster, SpellInfo const* spell); + void OnSpellHitTarget(Unit* /*target*/, SpellInfo const* spell); + + //Searchers + WorldObject* GetNearbyRezTarget(float dist = 30) const; + Unit* FindImmunityShieldDispelTarget(float dist = 30) const; + Unit* FindHostileDispelTarget(float dist = 30, bool stealable = false) const; + Unit* FindAffectedTarget(uint32 spellId, ObjectGuid caster = ObjectGuid::Empty, float dist = DEFAULT_VISIBILITY_DISTANCE, uint8 hostile = 0) const; + Unit* FindPolyTarget(float dist = 30) const; + Unit* FindFearTarget(float dist = 30) const; + Unit* FindStunTarget(float dist = 20) const; + Unit* FindUndeadCCTarget(float dist, uint32 spellId, bool unattacked = true) const; + Unit* FindRootTarget(float dist, uint32 spellId) const; + Unit* FindCastingTarget(float maxdist = 10, float mindist = 0, uint32 spellId = 0, uint8 minHpPct = 0) const; + Unit* FindAOETarget(float dist, WorldObject const* src = nullptr) const; + Unit* FindSplashTarget(float dist = 5, Unit* To = nullptr, float splashdist = 4) const; + Unit* FindSplashTarget(float dist, Unit* To, float splashdist, uint8 minTargets) const; + Unit* FindTranquilTarget(float mindist = 5, float maxdist = 35) const; + Unit* FindDistantTauntTarget(float maxdist = 30, bool ally = false) const; + Unit* FindDrainTarget(float maxdist = 30) const; + void GetNearbyTargetsList(std::list &targets, float maxdist, uint8 CCoption, WorldObject const* source = nullptr) const; + void GetNearbyTargetsInConeList(std::list &targets, float maxdist = 10) const; + void GetNearbyFriendlyTargetsList(std::list &targets, float maxdist = 30) const; + + //Bot specific player-like mods hooks + //todo remove &damage ApplyClassDamageMultiplierMelee (uint&, CalcDamageInfo&) + virtual void ApplyClassDamageMultiplierMelee(uint32& /*damage*/, CalcDamageInfo& /*damageinfo*/) const {} + virtual void ApplyClassDamageMultiplierMeleeSpell(int32& /*damage*/, SpellNonMeleeDamage& /*damageinfo*/, SpellInfo const* /*spellInfo*/, WeaponAttackType /*attackType*/, bool /*crit*/) const {} + virtual void ApplyClassDamageMultiplierSpell(int32& /*damage*/, SpellNonMeleeDamage& /*damageinfo*/, SpellInfo const* /*spellInfo*/, WeaponAttackType /*attackType*/, bool /*crit*/) const {} + virtual void ApplyClassDamageMultiplierHeal(Unit const* /*victim*/, float& /*heal*/, SpellInfo const* /*spellInfo*/, DamageEffectType /*damagetype*/, uint32 /*stack*/) const {} + virtual void ApplyClassSpellCritMultiplierAll(Unit const* /*victim*/, float& /*crit_chance*/, SpellInfo const* /*spellInfo*/, SpellSchoolMask /*schoolMask*/, WeaponAttackType /*attackType*/) const {} + virtual void ApplyClassSpellCostMods(SpellInfo const* /*spellInfo*/, int32& /*cost*/) const {} + virtual void ApplyClassSpellCastTimeMods(SpellInfo const* /*spellInfo*/, int32& /*casttime*/) const {} + virtual void ApplyClassSpellCooldownMods(SpellInfo const* /*spellInfo*/, uint32& /*cooldown*/) const {} + virtual void ApplyClassSpellCategoryCooldownMods(SpellInfo const* /*spellInfo*/, uint32& /*cooldown*/) const {} + virtual void ApplyClassSpellGlobalCooldownMods(SpellInfo const* /*spellInfo*/, float& /*cooldown*/) const {} + virtual void ApplyClassSpellRadiusMods(SpellInfo const* /*spellInfo*/, float& /*radius*/) const {} + virtual void ApplyClassSpellRangeMods(SpellInfo const* /*spellInfo*/, float& /*maxrange*/) const {} + virtual void ApplyClassSpellMaxTargetsMods(SpellInfo const* /*spellInfo*/, uint32& /*targets*/) const {} + virtual void ApplyClassSpellChanceOfSuccessMods(SpellInfo const* /*spellInfo*/, float& /*chance*/) const {} + virtual void ApplyClassEffectMods(SpellInfo const* /*spellInfo*/, uint8 /*effIndex*/, float& /*value*/) const {} + virtual void ApplyClassThreatMods(SpellInfo const* /*spellInfo*/, float& /*threat*/) const {} + virtual void ApplyClassEffectValueMultiplierMods(SpellInfo const* /*spellInfo*/, SpellEffIndex /*effIndex*/, float& /*multiplier*/) const {} + + virtual void InitPowers() {} + virtual void InitSpells() = 0; + virtual void ApplyClassPassives() const = 0; + virtual void InitHeals() {} + + void Regenerate(); + void RegenerateEnergy(); + bool Feasting() const; + uint32 GetRation(bool drink) const; + + bool Wait(); + uint16 Rand() const; + void GenerateRand() const; + + static uint32 GetLostHP(Unit const* unit); + static uint8 GetHealthPCT(Unit const* u); + static uint8 GetManaPCT(Unit const* u); + + virtual MeleeHitOutcome GetNextAttackMeleeOutCome() const; + + //event helpers + void BotJumpInPlaceInFrontOf(Position const* pos, float speedXY, float maxHeight); + void DismountBot(); + + void BotSay(const std::string &text, Player const* target = nullptr) const; + void BotWhisper(const std::string &text, Player const* target = nullptr) const; + void BotYell(const std::string &text, Player const* target = nullptr) const; + void BotSay(std::string&& text, Player const* target = nullptr) const; + void BotWhisper(std::string&& text, Player const* target = nullptr) const; + void BotYell(std::string&& text, Player const* target = nullptr) const; + + void ReportSpellCast(uint32 spellId, const std::string& followedByString, Player const* target) const; + + void ApplyItemEnchantment(Item* item, EnchantmentSlot eslot, uint8 slot); + void RemoveItemClassEnchantment(uint8 slot); + + bool HasAuraTypeWithValueAtLeast(AuraType auratype, int32 minvalue, Unit const* unit = nullptr) const; + + void DoSkytalonVehicleStrats(uint32 diff); + void DoRubyDrakeVehicleStrats(uint32 diff); + void DoEmeraldDrakeVehicleStrats(uint32 diff); + void DoAmberDrakeVehicleStrats(uint32 diff); + void DoArgentMountVehicleStrats(uint32 diff); + void DoDemolisherVehicleStrats(uint32 diff); + void DoSiegeEngineVehicleStrats(uint32 diff); + void DoChopperVehicleStrats(uint32 diff); + void DoGenericVehicleStrats(uint32 diff); + void DoVehicleStrats(BotVehicleStrats strat, uint32 diff); + void DoVehicleActions(uint32 diff); + bool CheckVehicleAttackTarget(BotVehicleStrats /*strat*/); + bool HasVehicleRoleOverride(uint32 role) const; + float GetVehicleAttackDistanceOverride() const; + uint8 LivingVehiclesCount(uint32 entry = 0) const; + + bool ProcessImmediateNonAttackTarget(); + + static bool IsUsableItem(Item const* item); + uint32 GetItemSpellCooldown(uint32 spellid) const; + void CheckUsableItems(uint32 diff); + + uint32 GetLastWMOArea() const { return _lastWMOAreaId; } + + Player* master; + Player* _prevRRobin; + Unit* opponent; + Unit* disttarget; + Creature* botPet; + EventProcessor Events; + ObjectGuid aftercastTargetGuid; + uint32 GC_Timer; + + uint8 _botclass; + uint8 _spec, _newspec; + int8 _primaryIconTank, _primaryIconDamage; + + BotVehicleStrats curVehStrat; + uint8 vehcomboPoints; + bool shouldEnterVehicle; + + private: + void FindMaster(); + uint32 CalculateOwnershipCheckTime(); + + void _OnHealthUpdate() const; + void _OnManaUpdate() const; + void _OnManaRegenUpdate() const; + + void _UpdateWMOArea(); + void _OnZoneUpdate(uint32 zoneId, uint32 areaId); + void _OnAreaUpdate(uint32 areaId); + + void RemoveItemBonuses(uint8 slot); + void RemoveItemEnchantments(Item const* item); + void RemoveItemEnchantment(Item const* item, EnchantmentSlot eslot); + void RemoveItemClassEnchantments(); + void ApplyItemBonuses(uint8 slot); + void ApplyItemEnchantments(Item* item, uint8 slot); + void ApplyItemEquipSpells(Item* item, bool apply); + void ApplyItemEquipEnchantmentSpells(Item* item); + void ApplyItemSetBonuses(Item* item, bool apply); + void ApplyItemsSpells(); + + bool IsPotionSpell(uint32 spellId) const; + void StartPotionTimer(); + + void BotJump(Position const* pos, bool count = true); + bool UpdateImpossibleChase(Unit const* target); + void ResetChaseTimer(Position const* pos); + void ResetChase(Position const* pos); + + void ApplyRacials(); + void InitRoles(); + void InitSpec(); + void InitEquips(); + void InitOwner(); + void InitFaction(); + void InitRace(); + + bool _canCureTarget(Unit const* target, uint32 cureSpell) const; + void _getBotDispellableAuraList(Unit const* target, uint32 dispelMask, std::list &dispelList) const; + void _calculatePos(Unit const* followUnit, Position& pos, float* speed = nullptr) const; + uint32 _selectMountSpell() const; + void _updateMountedState(); + void _updateStandState() const; + void _updateRations(); + void _updateEquips(uint8 slot, Item* item); + + uint32 _getLootQualityMask() const; + uint32 _getLootQualityThreshold() const; + bool _canLootItemForPlayer(Player* player, Creature* creature, uint8 slot) const; + bool _canLootCreatureForPlayer(Player* player, Creature* creature, uint32 lootQualityMask, uint32 lootThreshold) const; + bool _canLootCreature(Creature* creature) const; + void _autoLootCreatureGold(Creature* creature) const; + void _autoLootCreatureItems(Player* receiver, Creature* creature, uint32 lootQualityMask, uint32 lootThreshold) const; + void _autoLootCreature(Creature* creature); + + bool _canUseOffHand() const; + bool _canUseRanged() const; + bool _canUseRelic() const; + bool _canEquip(ItemTemplate const* newProto, uint8 slot, bool ignoreItemLevel, Item const* newItem = nullptr) const; + void _removeEquipment(uint8 slot); + bool _unequip(uint8 slot, ObjectGuid receiver); + bool _equip(uint8 slot, Item* newItem, ObjectGuid receiver); + bool _resetEquipment(uint8 slot, ObjectGuid receiver); + + void _castBotItemUseSpell(Item const* item, SpellCastTargets const& targets/*, uint8 cast_count = 0, uint32 glyphIndex = 0*/); + + std::tuple _getTargets(bool byspell, bool ranged, bool &reset) const; + Unit* _getVehicleTarget(BotVehicleStrats strat) const; + void _listAuras(Player const* player, Unit const* unit) const; + bool _checkImmunities(Unit const* target, SpellInfo const* spellInfo) const; + static float _getAttackDistance(float distance) { return distance*0.72f; } + void _extendAttackRange(float& dist) const; + bool _canSwitchToTarget(Unit const* from, Unit const* newTarget, int8 byspell) const; + + //for moved + void GetHomePosition(uint16& mapid, Position* pos) const; + + //utilities + void _AddItemTemplateLink(Player const* forPlayer, ItemTemplate const* item, std::ostringstream &str) const; + void _AddItemLink(Player const* forPlayer, Item const* item, std::ostringstream &str, bool addIcon = true) const; + void _AddQuestLink(Player const* forPlayer, Quest const* quest, std::ostringstream &str) const; + void _AddWeaponSkillLink(Player const* forPlayer, SpellInfo const* spellInfo, std::ostringstream &str, uint32 skillid) const; + void _AddSpellLink(Player const* forPlayer, SpellInfo const* spellInfo, std::ostringstream &str, bool color = true) const; + void _AddProfessionLink(Player const* forPlayer, SpellInfo const* spellInfo, std::ostringstream &str, uint32 skillId) const; + void _LocalizeItem(Player const* forPlayer, std::string &itemName, uint32 entry) const; + void _LocalizeItem(Player const* forPlayer, std::string &itemName, std::string &suffix, Item const* item) const; + void _LocalizeQuest(Player const* forPlayer, std::string &questTitle, uint32 entry) const; + void _LocalizeCreature(Player const* forPlayer, std::string &creatureName, uint32 entry) const; + void _LocalizeGameObject(Player const* forPlayer, std::string &gameobjectName, uint32 entry) const; + void _LocalizeSpell(Player const* forPlayer, std::string &spellName, uint32 entry) const; + + float _getBotStat(uint8 slot, BotStatMods stat) const; + float _getTotalBotStat(BotStatMods stat) const; + float _getRatingMultiplier(CombatRating cr) const; + + float _getStatScore(uint8 stat) const; + float _getItemGearStatScore(ItemTemplate const* iproto, uint8 forslot, Item const* item) const; + + void _saveStats(); + + PlayerClassLevelInfo* _classinfo; + SpellInfo const* m_botSpellInfo; + Position homepos, movepos, attackpos, sendlastpos; + Position sendpos[MAX_SEND_POINTS]; + AoeSpotsVec _aoeSpots; + + uint32 _botCommandState; + uint8 _botAwaitState; + + //stats + float hit, parry, dodge, block, crit, dmg_taken_phy, dmg_taken_mag, armor_pen, resilience; + uint32 expertise, spellpower, spellpen, defense, blockvalue; + int32 haste, resistbonus[MAX_SPELL_SCHOOL - 1]; + + //timers + uint32 _reviveTimer, _powersTimer, _chaseTimer, _engageTimer, _potionTimer; + uint32 lastdiff, checkAurasTimer, checkMasterTimer, roleTimer, ordersTimer, regenTimer, _updateTimerMedium, _updateTimerEx1, _updateTimerEx2; + uint32 _checkOwershipTimer; + uint32 _moveBehindTimer; + uint32 _wmoAreaUpdateTimer; + uint32 waitTimer; + uint32 itemsAutouseTimer; + uint32 evadeDelayTimer; + uint32 indoorsTimer; + uint32 outdoorsTimer; + uint32 _contestedPvPTimer; + uint32 _groupUpdateTimer; + //save timers + uint32 _saveDisabledSpellsTimer; + + uint32 _lastZoneId, _lastAreaId, _lastWMOAreaId; + uint32 _selfrez_spell_id; + + uint8 _unreachableCount, _jumpCount, _evadeCount; + uint8 _healHpPctThreshold; + uint32 _roleMask; + uint32 _usableItemSlotsMask; + ObjectGuid::LowType _ownerGuid; + ObjectGuid _lastTargetGuid; + bool doHealth, doMana, shouldUpdateStats; + bool feast_health, feast_mana; + bool spawned; + bool firstspawn; + bool _evadeMode; + bool _atHome; + bool _duringTeleport; + bool _canAppearInWorld; + + //wandering bots + bool _wanderer; + uint8 _baseLevel; + WanderNode const* _travel_node_last; + WanderNode const* _travel_node_cur; + + uint32 _groupUpdateMask; + uint64 _auraRaidUpdateMask; + GroupBotReference _group; + GroupBotReference _originalGroup; + Battleground* _bg; + + float _energyFraction; + + //counters (this session) + uint16 _deathsCount; + uint16 _killsCount; + uint16 _pvpKillsCount; + uint16 _playerKillsCount; + + //save flags + bool _saveDisabledSpells; + + TeleportHomeEvent* teleHomeEvent; + TeleportFinishEvent* teleFinishEvent; + AwaitStateRemovalEvent* awaitStateRemEvent; + + struct BotSpell + { + BotSpell() : spellId(0), cooldown(0), enabled(true) {} + BotSpell(BotSpell const&) = delete; + BotSpell(BotSpell&&) = delete; + BotSpell& operator=(BotSpell const&) = delete; + BotSpell& operator=(BotSpell&&) = delete; + uint32 spellId; + uint32 cooldown; + bool enabled; + }; + + typedef int32 ItemStatBonus[MAX_BOT_ITEM_MOD]; + ItemStatBonus _stats[BOT_INVENTORY_SIZE]; + Item* _equips[BOT_INVENTORY_SIZE]; + + public: + typedef std::unordered_map BotSpellMap; + BotSpellMap const& GetSpellMap() const { return _spells; } + + private: + BotSpellMap _spells; + + public: + //much simplier than SmartAI I guess... + struct BotOrder + { + friend class bot_ai; + + union + { + struct + { + uint64 targetGuid; + uint32 baseSpell; + } spellCastParams; + + struct + { + uint64 targetGuid; + } pullParams; + + } params; + + explicit BotOrder(BotOrderTypes order_type, uint32 timeout_sec = 10) : _type(order_type), _timeout(time(0) + timeout_sec) + { + memset((char*)(¶ms), 0, sizeof(params)); + } + BotOrder(BotOrder&&) noexcept = default; + + BotOrder(BotOrder const&) = delete; + BotOrder& operator=(BotOrder const&) = delete; + BotOrder& operator=(BotOrder&&) = delete; + + private: + BotOrderTypes _type; + time_t _timeout; + }; + + bool HasOrders() const { return !_orders.empty(); } + bool IsLastOrder(BotOrderTypes order_type, uint32 param1 = 0, ObjectGuid guidparam1 = ObjectGuid::Empty) const; + std::size_t GetOrdersCount() const { return _orders.size(); } + bool AddOrder(BotOrder&& order); + void CancelOrder(BotOrder const& order); + void CompleteOrder(BotOrder const& order); + void CancelAllOrders(); + + private: + void _ProcessOrders(); + + typedef std::queue OrdersQueue; + OrdersQueue _orders; +}; + +#endif diff --git a/src/server/game/AI/NpcBots/bot_archmage_ai.cpp b/src/server/game/AI/NpcBots/bot_archmage_ai.cpp new file mode 100644 index 000000000..95e3fcfe4 --- /dev/null +++ b/src/server/game/AI/NpcBots/bot_archmage_ai.cpp @@ -0,0 +1,397 @@ +#include "bot_ai.h" +#include "botspell.h" +#include "bottraits.h" +#include "MotionMaster.h" +#include "Player.h" +#include "ScriptMgr.h" +#include "Spell.h" +#include "SpellAuras.h" +#include "TemporarySummon.h" +#include "World.h" +/* +Archmage NpcBot (by Trickerer onlysuffering@gmail.com) +Description: +Archmage (Warcraft III tribute) +Abilities: +1) Fireball: main attack, single target, no mana cost +2) Blizzard: typical blizzard +3) Summon Water Elemental: summons a water elemental to attack archmage's enemies +Complete - 75% +TODO: mass tele +*/ + +enum ArchmageBaseSpells +{ + MAIN_ATTACK_1 = SPELL_FIREBALL, + BLIZZARD_1 = SPELL_BLIZZARD, + SUMMON_WATER_ELEMENTAL_1= SPELL_SUMMON_WATER_ELEMENTAL +}; +enum ArchmagePassives +{ + BRILLIANCE_AURA = SPELL_BRILLIANCE_AURA +}; +enum ArchmageSpecial +{ + MH_ATTACK_ANIM = SPELL_ATTACK_MELEE_1H, + + SUMMON_ELEM_COST = 125 * 5, + + ARCHMAGE_MOUNTID = 2402 +}; + +static const uint32 Archmage_spells_damage_arr[] = +{ MAIN_ATTACK_1, BLIZZARD_1 }; + +static const uint32 Archmage_spells_support_arr[] = +{ SUMMON_WATER_ELEMENTAL_1 }; + +static const std::vector Archmage_spells_damage(FROM_ARRAY(Archmage_spells_damage_arr)); +static const std::vector Archmage_spells_support(FROM_ARRAY(Archmage_spells_support_arr)); + +class archmage_bot : public CreatureScript +{ +public: + archmage_bot() : CreatureScript("archmage_bot") { } + + CreatureAI* GetAI(Creature* creature) const override + { + return new archmage_botAI(creature); + } + + struct archmage_botAI : public bot_ai + { + archmage_botAI(Creature* creature) : bot_ai(creature) + { + _botclass = BOT_CLASS_ARCHMAGE; + + InitUnitFlags(); + + //archmage immunities + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_POSSESS, true); + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_CHARM, true); + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_SILENCE, true); + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_SHAPESHIFT, true); + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_TRANSFORM, true); + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_BLOCK_SPELL_FAMILY, true); + me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_POLYMORPH, true); + me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_INTERRUPT, true); + me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_SILENCE, true); + } + + bool doCast(Unit* victim, uint32 spellId) + { + if (CheckBotCast(victim, spellId) != SPELL_CAST_OK) + return false; + return bot_ai::doCast(victim, spellId); + } + + void StartAttack(Unit* u, bool force = false) + { + if (!bot_ai::StartAttack(u, force)) + return; + GetInPosition(force, u); + } + + void JustEnteredCombat(Unit* u) override { bot_ai::JustEnteredCombat(u); } + void AttackStart(Unit*) override { } + void KilledUnit(Unit* u) override { bot_ai::KilledUnit(u); } + void EnterEvadeMode(EvadeReason why = EVADE_REASON_OTHER) override { bot_ai::EnterEvadeMode(why); } + void MoveInLineOfSight(Unit* u) override { bot_ai::MoveInLineOfSight(u); } + void JustDied(Unit* u) override { UnsummonAll(false); bot_ai::JustDied(u); } + void DoNonCombatActions(uint32 /*diff*/) { } + + void CheckAura(uint32 diff) + { + if (checkAuraTimer > diff || GC_Timer > diff || IsCasting()) + return; + + checkAuraTimer = 10000; + + if (!IAmFree() && !me->HasAura(BRILLIANCE_AURA, me->GetGUID())) + RefreshAura(BRILLIANCE_AURA); + } + + void UpdateAI(uint32 diff) override + { + if (!me->IsMounted() && !me->GetVehicle()) + me->Mount(ARCHMAGE_MOUNTID); + + if (!GlobalUpdate(diff)) + return; + + DoVehicleActions(diff); + if (!CanBotAttackOnVehicle()) + return; + + CheckAura(diff); + + if (IsPotionReady()) + { + if (me->GetPower(POWER_MANA) < SUMMON_ELEM_COST) + DrinkPotion(true); + else if (GetHealthPCT(me) < 50) + DrinkPotion(false); + } + + //pet is killed or unreachable + if (IsSpellReady(SUMMON_WATER_ELEMENTAL_1, diff, false) && me->GetPower(POWER_MANA) >= SUMMON_ELEM_COST && !IsCasting() && + (IAmFree() || master->IsInCombat()/* || !master->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_RESTING)*/) && + (!botPet || me->GetDistance2d(botPet) > sWorld->GetMaxVisibleDistanceOnContinents())) + { + me->CastSpell(me, GetSpell(SUMMON_WATER_ELEMENTAL_1), false); + return; + } + + if (ProcessImmediateNonAttackTarget()) + return; + + if (!CheckAttackTarget()) + return; + + if (IsCasting()) + return; + + CheckUsableItems(diff); + + Attack(diff); + } + + void Attack(uint32 diff) + { + Unit* mytar = opponent ? opponent : disttarget ? disttarget : nullptr; + if (!mytar) + return; + + StartAttack(mytar, IsMelee()); + + CheckAttackState(); + if (!me->IsAlive() || !mytar->IsAlive()) + return; + + MoveBehind(mytar); + + if (!HasRole(BOT_ROLE_DPS)) + return; + + if (GC_Timer > diff) + return; + + //Blizzard + if (IsSpellReady(BLIZZARD_1, diff) && !JumpingOrFalling() && Rand() < 50) + { + if (Unit* blizztarget = FindAOETarget(CalcSpellMaxRange(BLIZZARD_1))) + { + if (doCast(blizztarget, GetSpell(BLIZZARD_1))) + return; + } + + SetSpellCooldown(BLIZZARD_1, 1000); //fail + } + + if (IsSpellReady(MAIN_ATTACK_1, diff) && CanAffectVictimAny(mytar, SPELL_SCHOOL_FIRE, SPELL_SCHOOL_ARCANE)) + { + if (doCast(mytar, GetSpell(MAIN_ATTACK_1))) + return; + } + } + + void ApplyClassDamageMultiplierSpell(int32& damage, SpellNonMeleeDamage& /*damageinfo*/, SpellInfo const* spellInfo, WeaponAttackType /*attackType*/, bool iscrit) const override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //uint8 lvl = me->GetLevel(); + float fdamage = float(damage); + + //apply bonus damage mods + float pctbonus = 1.0f; + if (iscrit) + pctbonus *= 1.333f; + + if (baseId == MAIN_ATTACK_1 || baseId == BLIZZARD_1) + fdamage += me->SpellBaseDamageBonusDone(SPELL_SCHOOL_MASK_MAGIC) * (spellInfo->_effects[0].BonusMultiplier - 1.f) * me->CalculateDefaultCoefficient(spellInfo, SPELL_DIRECT_DAMAGE) * me->CalculateSpellpowerCoefficientLevelPenalty(spellInfo); + + damage = int32(fdamage * pctbonus); + } + + void OnClassSpellGo(SpellInfo const* spellInfo) override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + + if (baseId == MAIN_ATTACK_1 || baseId == BLIZZARD_1) + GC_Timer = uint32(me->GetAttackTime(BASE_ATTACK) * me->m_modAttackSpeedPct[BASE_ATTACK]); + + if (baseId == MAIN_ATTACK_1) + me->CastSpell(me, MH_ATTACK_ANIM, true); + + if (baseId == SUMMON_WATER_ELEMENTAL_1) + SummonBotPet(); + } + + void SpellHitTarget(WorldObject* wtarget, SpellInfo const* spell) override + { + Unit* target = wtarget->ToUnit(); + if (!target) + return; + + OnSpellHitTarget(target, spell); + } + + void SpellHit(WorldObject* wcaster, SpellInfo const* spell) override + { + Unit* caster = wcaster->ToUnit(); + if (!caster) + return; + + OnSpellHit(caster, spell); + } + + void DamageDealt(Unit* victim, uint32& damage, DamageEffectType damageType) override + { + bot_ai::DamageDealt(victim, damage, damageType); + } + + void DamageTaken(Unit* u, uint32& /*damage*/, DamageEffectType /*damageType*/, SpellInfo const* /*spellInfo*/) override + { + if (!u) + return; + if (!u->IsInCombat() && !me->IsInCombat()) + return; + OnOwnerDamagedBy(u); + } + + void OwnerAttackedBy(Unit* u) override + { + if (!u) + return; + OnOwnerDamagedBy(u); + } + + void SummonBotPet() + { + if (botPet) + UnsummonAll(false); + + uint32 entry = BOT_PET_AWATER_ELEMENTAL; + + Position pos; + + //water elemetal 1 minute duration + Creature* myPet = me->SummonCreature(entry, *me, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 5s); + me->GetNearPoint(myPet, pos.m_positionX, pos.m_positionY, pos.m_positionZ, 2, me->GetOrientation()); + myPet->GetMotionMaster()->MovePoint(me->GetMapId(), pos); + myPet->SetCreator(master); + myPet->SetOwnerGUID(me->GetGUID()); + myPet->SetFaction(master->GetFaction()); + myPet->SetControlledByPlayer(!IAmFree()); + myPet->SetPvP(me->IsPvP()); + myPet->SetByteValue(UNIT_FIELD_BYTES_2, 1, master->GetByteValue(UNIT_FIELD_BYTES_2, 1)); + myPet->SetUInt32Value(UNIT_CREATED_BY_SPELL, SUMMON_WATER_ELEMENTAL_1); + + botPet = myPet; + } + + void UnsummonAll(bool savePets = true) override + { + UnsummonPet(savePets); + } + + void SummonedCreatureDies(Creature* /*summon*/, Unit* /*killer*/) override + { + } + + void SummonedCreatureDespawn(Creature* summon) override + { + //TC_LOG_ERROR("entities.unit", "SummonedCreatureDespawn: {}'s {}", me->GetName(), summon->GetName()); + if (summon == botPet) + botPet = nullptr; + } + + uint32 GetAIMiscValue(uint32 data) const override + { + switch (data) + { + case BOTAI_MISC_PET_TYPE: + return BOT_PET_AWATER_ELEMENTAL; + default: + return 0; + } + } + + void CheckAttackState() override + { + } + + void OnBotEnterVehicle(Vehicle const* vehicle) override + { + me->Dismount(); + bot_ai::OnBotEnterVehicle(vehicle); + } + + void Reset() override + { + UnsummonAll(false); + + checkAuraTimer = 0; + + DefaultInit(); + } + + void ReduceCD(uint32 diff) override + { + if (checkAuraTimer > diff) checkAuraTimer -= diff; + } + + void InitPowers() override + { + me->SetPowerType(POWER_MANA); + } + + void InitSpells() override + { + InitSpellMap(MAIN_ATTACK_1, true, false); + InitSpellMap(BLIZZARD_1, true, false); + InitSpellMap(SUMMON_WATER_ELEMENTAL_1, true, false); + } + + void ApplyClassPassives() const override + { + } + + bool CanUseManually(uint32 basespell) const override + { + switch (basespell) + { + case BLIZZARD_1: + case SUMMON_WATER_ELEMENTAL_1: + return true; + default: + return false; + } + } + + std::vector const* GetDamagingSpellsList() const override + { + return &Archmage_spells_damage; + } + //std::vector const* GetCCSpellsList() const override + //{ + // return &Archmage_spells_cc; + //} + //std::vector const* GetHealingSpellsList() const override + //{ + // return &Archmage_spells_heal; + //} + std::vector const* GetSupportSpellsList() const override + { + return &Archmage_spells_support; + } + + private: + + uint32 checkAuraTimer; + }; +}; + +void AddSC_archmage_bot() +{ + new archmage_bot(); +} diff --git a/src/server/game/AI/NpcBots/bot_bm_ai.cpp b/src/server/game/AI/NpcBots/bot_bm_ai.cpp new file mode 100644 index 000000000..9319762c6 --- /dev/null +++ b/src/server/game/AI/NpcBots/bot_bm_ai.cpp @@ -0,0 +1,941 @@ +#include "bot_ai.h" +#include "botmgr.h" +#include "botspell.h" +#include "Log.h" +#include "MotionMaster.h" +#include "ObjectAccessor.h" +#include "Player.h" +#include "ScriptMgr.h" +#include "SpellAuras.h" +#include "TemporarySummon.h" +/* +Blademaster NpcBot (by Trickerer onlysuffering@gmail.com) +DISABLED: movement mechanics incompatibility +Complete - 75% +TODO: BLADESTORM, Convert illusions to bot_pet_ai +*/ + +#define MAX_ILLUSION_POSITIONS 4 +#define MIRROR_IMAGE_DURATION 90000 + +enum BlademasterBaseSpells +{ + WINDWALK_1 = SPELL_NETHERWALK, + MIRROR_IMAGE_1 = SPELL_MIRROR_IMAGE_BM, + CRITICAL_STRIKE_1 = SPELL_CRITICAL_STRIKE +}; +enum BlademasterPassives +{ +//Talents +//other +}; +enum BlademasterSpecial +{ + NPC_MIRROR_IMAGE_BM = 70552, + TRANSPARENCY = SPELL_TRANSPARENCY_50, + BLACK_COLOR = SPELL_VERTEX_COLOR_BLACK, + STUN_FREEZE = SPELL_STUN_FREEZE_ANIM, + + MIRROR_COST = 125 * 5 +}; + +static const uint32 Blademaster_spells_support_arr[] = +{ MIRROR_IMAGE_1, WINDWALK_1 }; + +static const std::vector Blademaster_spells_support(FROM_ARRAY(Blademaster_spells_support_arr)); + +class blademaster_bot : public CreatureScript +{ +public: + blademaster_bot() : CreatureScript("blademaster_bot") { } + + CreatureAI* GetAI(Creature* creature) const override + { + return new blademaster_botAI(creature); + } + + struct blademaster_botAI : public bot_ai + { + private: + //DelayedMeleeDamageEvent - Blademaster + //deals critical damage, resets attack timer and sends fake log + class DelayedMeleeDamageEvent : public BasicEvent + { + public: + DelayedMeleeDamageEvent(Creature* bot, ObjectGuid targetGuid, bool windwalk) : + _bot(bot), _targetGuid(targetGuid), _windwalk(windwalk), _dinfo(nullptr) { } + + void SetDamageInfo(CalcDamageInfo* dinfo) + { + _dinfo = dinfo; + } + + protected: + bool Execute(uint64 /*e_time*/, uint32 /*p_time*/) + { + (dynamic_cast(_bot->GetAI()))->CriticalStrikeFinish(_targetGuid, _dinfo, _windwalk); + + if (_dinfo) + delete _dinfo; + return true; + } + + private: + Creature* _bot; + ObjectGuid _targetGuid; + bool _windwalk; + CalcDamageInfo* _dinfo; + DelayedMeleeDamageEvent(DelayedMeleeDamageEvent const&); + }; + + class EventTerminateEvent : public BasicEvent + { + public: + EventTerminateEvent(Creature* bot) : _bot(bot) { } + + protected: + bool Execute(uint64 /*e_time*/, uint32 /*p_time*/) + { + (dynamic_cast(_bot->GetAI()))->TerminateEvent(); + return true; + } + + private: + Creature* _bot; + EventTerminateEvent(EventTerminateEvent const&); + }; + + class IllusionUnsummonEvent : public BasicEvent + { + public: + IllusionUnsummonEvent(Creature const* bot) : _bot(bot) { } + + protected: + bool Execute(uint64 /*e_time*/, uint32 /*p_time*/) + { + (dynamic_cast(_bot->GetAI()))->UnsummonAll(false); + + return true; + } + + private: + Creature const* _bot; + IllusionUnsummonEvent(IllusionUnsummonEvent const&); + }; + + class DelayedIllusionSummonEvent : public BasicEvent + { + public: + DelayedIllusionSummonEvent(Creature const* bot) : _bot(bot) { } + + protected: + bool Execute(uint64 /*e_time*/, uint32 /*p_time*/) + { + (dynamic_cast(_bot->GetAI()))->MirrorImageFinish(); + + return true; + } + + private: + Creature const* _bot; + DelayedIllusionSummonEvent(DelayedIllusionSummonEvent const&); + }; + + class DisappearEvent : public BasicEvent + { + public: + DisappearEvent(Creature* bot) : _bot(bot) { } + + protected: + bool Execute(uint64 /*e_time*/, uint32 /*p_time*/) + { + (dynamic_cast(_bot->GetAI()))->MirrorImageMid(); + + return true; + } + + private: + Creature* _bot; + DisappearEvent(DisappearEvent const&); + }; + + void _calcIllusionPositions() + { + float x = me->m_positionX; + float y = me->m_positionY; + float z = me->m_positionZ; + float o = me->GetOrientation(); + + //X X + // C + //X X + // + //C - caster (Blademaster) + //X - new positions (1-3 illusions + blademaster) + + float dist = 3.f; //not too far - 3 for x and y seems to be way to go + for (uint8 i = 0; i != MAX_ILLUSION_POSITIONS; ++i) + { + _illusPos[i].m_positionX = x + ((i <= 1) ? +dist : -dist); // +2+2-2-2 + _illusPos[i].m_positionY = y + (!(i & 1) ? +dist : -dist); // +2-2+2-2 + _illusPos[i].m_positionZ = z; + me->UpdateAllowedPositionZ(_illusPos[i].m_positionX, _illusPos[i].m_positionY, _illusPos[i].m_positionZ); + _illusPos[i].SetOrientation(o); + } + } + + public: + blademaster_botAI(Creature* creature) : bot_ai(creature) + { + _botclass = BOT_CLASS_BM; + + InitUnitFlags(); + + //Blademaster cannot be disarmed + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_DISARM, true); + } + + void IllusionsCheck(uint32 diff) + { + if (!me->IsAlive()) + return; + if (Illusions_Check_Timer > diff) + return; + + Illusions_Check_Timer = 250; + + if (_illusionGuids.empty()) + return; + + for (std::list::const_iterator itr = _illusionGuids.begin(); itr != _illusionGuids.end(); ++itr) + { + Creature* ill = ObjectAccessor::GetCreature(*me, *itr); + if (!ill) + { + TC_LOG_ERROR("entities.player", "bm_bot::IllusionsCheck(): unit {} is not found in world!", (*itr).ToString()); + continue; + } + + ill->SetCanModifyStats(false); + float hpct = ill->GetHealthPct(); + float mpct = (float(ill->GetPower(POWER_MANA)) * 100.f) / float(ill->GetMaxPower(POWER_MANA)); + + ill->SetMaxHealth(me->GetMaxHealth()); + ill->SetHealth(uint32(0.5f + float(ill->GetMaxHealth()) * hpct / 100.f)); + ill->SetMaxPower(POWER_MANA, me->GetMaxPower(POWER_MANA)); + ill->SetPower(POWER_MANA, uint32(0.5f + float(ill->GetMaxPower(POWER_MANA)) * mpct / 100.f)); + ill->SetFloatValue(UNIT_FIELD_MINDAMAGE, me->GetFloatValue(UNIT_FIELD_MINDAMAGE)); + ill->SetFloatValue(UNIT_FIELD_MAXDAMAGE, me->GetFloatValue(UNIT_FIELD_MAXDAMAGE)); + ill->m_modAttackSpeedPct[BASE_ATTACK] = me->m_modAttackSpeedPct[BASE_ATTACK]; + } + } + + bool doCast(Unit* victim, uint32 spellId) + { + //custom + if (_dmdevent) + return false; + if (IsTempBot()) //Illusion etc. + return false; + + if (CheckBotCast(victim, spellId) != SPELL_CAST_OK) + return false; + + return bot_ai::doCast(victim, spellId); + } + + void UpdateAI(uint32 diff) override + { + IllusionsCheck(diff); + + if (!GlobalUpdate(diff)) + return; + + DoVehicleActions(diff); + if (!CanBotAttackOnVehicle()) + return; + + if (IsPotionReady() && !IsTempBot()) + { + if (me->GetPower(POWER_MANA) < MIRROR_COST) + DrinkPotion(true); + else if (GetHealthPCT(me) < 50) + DrinkPotion(false); + } + + //CheckRacials(diff); + + CheckWindWalk(diff); + CheckMirrorImage(diff); + + if (!me->IsInCombat()) + DoNonCombatActions(diff); + + if (IsCasting()) + return; + + if (ProcessImmediateNonAttackTarget()) + return; + + if (!CheckAttackTarget()) + return; + + CheckUsableItems(diff); + + Attack(diff); + } + + void StartAttack(Unit* u, bool force = false) + { + if (!bot_ai::StartAttack(u, force)) + return; + + GetInPosition(force, u); + } + + void JustEnteredCombat(Unit* u) override { bot_ai::JustEnteredCombat(u); } + void AttackStart(Unit*) override { } + void KilledUnit(Unit* u) override { bot_ai::KilledUnit(u); } + void EnterEvadeMode(EvadeReason why = EVADE_REASON_OTHER) override { bot_ai::EnterEvadeMode(why); } + void MoveInLineOfSight(Unit* u) override { bot_ai::MoveInLineOfSight(u); } + void DoNonCombatActions(uint32 /*diff*/) { } + + bool CanEat() const override { return Windwalk_Timer <= GetLastDiff() && !illusion_Fade; } + bool CanSeeEveryone() const override { return Windwalk_Timer > GetLastDiff(); } + + void BreakCC(uint32 diff) override + { + if (me->HasAuraWithMechanic((1<IsAlive() || !mytar->IsAlive()) + return; + + MoveBehind(mytar); + } + + void DoBMMeleeAttackIfReady() + { + //Copied from UnitAI::DoMeleeAttackIfReady() with modifications + //cannot attack while casting or jumping + if (me->HasUnitState(UNIT_STATE_CASTING) || _dmdevent) + return; + + Unit* victim = me->GetVictim(); + //Make sure our attack is ready and we aren't currently casting before checking distance + if (me->isAttackReady() && me->IsWithinMeleeRange(victim)) + { + if (!CCed(me, true) && !JumpingFlyingOrFalling()) + { + //Windwalk strike + if (Windwalk_Timer > GetLastDiff()) + { + CriticalStrike(victim, true); + return; + } + //Critical Strike: 15% to deal x2,x3, etc... damage + else if (criticalStikeMult >= 2 && !CCed(me, true) && roll_chance_f(15.f)) + { + CriticalStrike(victim); + return; + } + } + + DoMeleeAttackIfReady(); + return; + } + } + + void CheckAttackState() override + { + if (me->GetVictim()) + { + if (HasRole(BOT_ROLE_DPS)) + DoBMMeleeAttackIfReady(); + } + } + + void CheckWindWalk(uint32 diff) + { + if (!IsSpellReady(WINDWALK_1, diff) || Windwalk_Timer > GetLastDiff() || illusion_Fade || IsCasting() || + Rand() > (10 + 20 * (me->IsInCombat() || master->IsInCombat()))) + return; + + if (!IAmFree() && master->isMoving()) + { + if (me->GetDistance(master) > 30 && + doCast(me, GetSpell(WINDWALK_1))) + return; + + return; + } + + if (!IsTank(me)) + { + //unit to strike + Unit* u = IsMelee() ? me->GetVictim() : nullptr; + + if ((u && u->isMoving() && me->GetDistance(u) > 18 && + (u->GetVictim() != me || u->getAttackers().size() > uint8(u->IsControlledByPlayer() ? 0 : 1))) || + me->getAttackers().size() > 2) + { + if (doCast(me, GetSpell(WINDWALK_1))) + return; + } + } + } + + void CheckMirrorImage(uint32 diff) + { + //only for controlled bot + //if (IAmFree()) + // return; + if (!IsSpellReady(MIRROR_IMAGE_1, diff) || !me->IsInCombat() || !illusionsCount || illusion_Fade || + !HasRole(BOT_ROLE_DPS) || IsCasting() || Rand() > 20) + return; + + uint8 pct = GetHealthPCT(me); + uint8 size = uint8(me->getAttackers().size()); + if (!size) + return; + + if (pct > 25 && (size > 3 || pct < (80 + size * 5))) + if (doCast(me, GetSpell(MIRROR_IMAGE_1))) + return; + } + + void MirrorImageStart() + { + if (!illusionsCount) + return; + + ASSERT(!illusion_Fade); + illusion_Fade = true; + + //OKAY + + //destroy existing illusions if any + UnsummonAll(false); + //mirror image renders BM invulnerable for a short period of time, + //removing all but passive auras + Unit::AuraMap const auras = me->GetOwnedAuras(); //copy + for (Unit::AuraMap::const_iterator iter = auras.begin(); iter != auras.end(); ++iter) + { + Aura* aura = iter->second; + if (aura->GetSpellInfo()->Attributes & SPELL_ATTR0_PASSIVE) + continue; + if (aura->GetId() == SPELL_BURNING_BLADE_BLADEMASTER) + continue; + AuraApplication* aurApp = aura->GetApplicationOfTarget(me->GetGUID()); + if (!aurApp) + continue; + me->RemoveAura(aurApp, AURA_REMOVE_BY_DEFAULT); + } + + me->BotStopMovement(); + me->AttackStop(); + me->HandleEmoteCommand(EMOTE_ONESHOT_NONE); + me->AddAura(BLACK_COLOR, me);//color + me->AddAura(STUN_FREEZE, me);//stop/immunity + + //prepare to disappear + DisappearEvent* devent = new DisappearEvent(me); + Events.AddEvent(devent, Events.CalculateTime(std::chrono::milliseconds(300))); //immediatelly (almost) + } + + void MirrorImageMid() + { + if (!me->IsInWorld() || + !me->IsAlive()/* || CCed(me)*/) //this is just ensurance + { + me->RemoveAura(BLACK_COLOR); + me->RemoveAura(STUN_FREEZE); + illusion_Fade = false; + return; + } + //disappear + me->SetPhaseMask(0, true); + + //INVISIBLE! + //EVENT + DelayedIllusionSummonEvent* disevent = new DelayedIllusionSummonEvent(me); + Events.AddEvent(disevent, Events.CalculateTime(std::chrono::milliseconds(1250))); //1000 ms disappear time + 250 ms buffer + } + + void MirrorImageFinish() + { + illusion_Fade = false; + me->RemoveAura(BLACK_COLOR); + me->RemoveAura(STUN_FREEZE); + if (!me->IsInWorld() || + !me->IsAlive()/* || CCed(me)*/) //this is just ensurance + return; + + _calcIllusionPositions(); + + std::set usedposs; + + for (uint8 i = 0; i != illusionsCount; ++i) + { + Creature* illusion = me->SummonCreature(NPC_MIRROR_IMAGE_BM, *me, TEMPSUMMON_MANUAL_DESPAWN); + if (!illusion) + continue; + + if (!IAmFree()) + ASSERT(master->GetBotMgr()->AddBot(illusion)); + + illusion->SetCreator(master); //TempSummon* Map::SummonCreature() + illusion->SetOwnerGUID(me->GetGUID()); + + //copy visuals + //illusion->SetEntry(me->GetEntry()); + illusion->UpdateEntry(me->GetEntry()); + illusion->SetFaction(me->GetFaction()); + illusion->SetLevel(me->GetLevel()); + illusion->SetDisplayId(me->GetDisplayId()); + illusion->SetNativeDisplayId(me->GetDisplayId()); + illusion->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 0, me->GetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 0)); + + //copy stats + illusion->SetCanModifyStats(false); + illusion->SetMaxHealth(me->GetMaxHealth()); + illusion->SetHealth(me->GetHealth()); + illusion->SetMaxPower(POWER_MANA, me->GetMaxPower(POWER_MANA)); + illusion->SetPower(POWER_MANA, me->GetPower(POWER_MANA)); + illusion->SetFloatValue(UNIT_FIELD_MINDAMAGE, me->GetFloatValue(UNIT_FIELD_MINDAMAGE)); + illusion->SetFloatValue(UNIT_FIELD_MAXDAMAGE, me->GetFloatValue(UNIT_FIELD_MAXDAMAGE)); + illusion->m_modAttackSpeedPct[BASE_ATTACK] = me->m_modAttackSpeedPct[BASE_ATTACK]; + + illusion->BotStopMovement(); + while (true) + { + //move illusion to a random corner + uint8 j = urand(0, MAX_ILLUSION_POSITIONS - 1); + if (usedposs.find(j) == usedposs.end()) + { + illusion->GetMotionMaster()->MovePoint(me->GetMapId(), _illusPos[j]); + //illusion->Relocate(_illusPos[j]); + usedposs.insert(j); + break; + } + } + + illusion->GetBotAI()->SetBotCommandState(BOT_COMMAND_COMBATRESET); + + _illusionGuids.push_back(illusion->GetGUID()); + } + + SetBotCommandState(BOT_COMMAND_COMBATRESET); + + for (uint8 i = 0; i != MAX_ILLUSION_POSITIONS; ++i) + { + if (usedposs.find(i) == usedposs.end()) + { + //me->BotStopMovement(); + me->GetMotionMaster()->MovePoint(me->GetMapId(), _illusPos[i]); + //me->Relocate(_illusPos[i]); + //usedposs.insert(i); + break; + } + } + + uint8 counter = 0; + uint8 r = urand(0, uint8(_illusionGuids.size() - 1)); + uint32 phaseMask = IAmFree() ? PHASEMASK_NORMAL : master->GetPhaseMask(); + + for (std::list::const_iterator itr = _illusionGuids.begin(); itr != _illusionGuids.end(); ++itr) + { + if (Creature* illusion = ObjectAccessor::GetCreature(*me, *itr)) + illusion->SetPhaseMask(phaseMask, true); + + if (counter == r) + me->SetPhaseMask(phaseMask, true); + else + ++counter; + } + + me->GetCombatManager().EndAllPvECombat(); + + if (me->GetPhaseMask() != phaseMask) + me->SetPhaseMask(phaseMask, true); + + //me->setAttackTimer(BASE_ATTACK, 3000); + //waitTimer += 800; + SetSpellCooldown(MIRROR_IMAGE_1, 8000); + + //EVENT + IllusionUnsummonEvent* unsevent = new IllusionUnsummonEvent(me); + Events.AddEvent(unsevent, Events.CalculateTime(std::chrono::milliseconds(MIRROR_IMAGE_DURATION))); + } + + void CriticalStrike(Unit* target, bool windwalk = false) + { + //Okay critical strike must have jump and strike animation, doing delayed damage + _dmdevent = new DelayedMeleeDamageEvent(me, target->GetGUID(), windwalk); + + //hack temp attack damage calc + float mindam = me->GetFloatValue(UNIT_FIELD_MINDAMAGE); + float maxdam = me->GetFloatValue(UNIT_FIELD_MAXDAMAGE); + + if (windwalk) + { + me->SetFloatValue(UNIT_FIELD_MINDAMAGE, mindam * 1.5f); + me->SetFloatValue(UNIT_FIELD_MAXDAMAGE, maxdam * 1.5f); + me->RemoveAura(GetSpell(WINDWALK_1)); + me->RemoveAura(TRANSPARENCY); + } + else + { + me->SetFloatValue(UNIT_FIELD_MINDAMAGE, mindam * criticalStikeMult); + me->SetFloatValue(UNIT_FIELD_MAXDAMAGE, maxdam * criticalStikeMult); + } + + CalcDamageInfo* dinfo = new CalcDamageInfo(); + me->CalculateMeleeDamage(target, dinfo, BASE_ATTACK); + + me->SetFloatValue(UNIT_FIELD_MINDAMAGE, mindam); + me->SetFloatValue(UNIT_FIELD_MAXDAMAGE, maxdam); + + _dmdevent->SetDamageInfo(dinfo); + Events.AddEvent(_dmdevent, Events.CalculateTime(std::chrono::milliseconds(250))); + + BotJumpInPlaceInFrontOf(target, 0.25f, 4.1f); //jump - DO NOT CHANGE + me->CastSpell(target, SPELL_COMBAT_SPECIAL_2H_ATTACK, true); //strike anim + me->resetAttackTimer(BASE_ATTACK); + } + + void CriticalStrikeFinish(ObjectGuid targetGuid, CalcDamageInfo* calcdinfo, bool /*windwalk*/) + { + EventTerminateEvent* etevent = new EventTerminateEvent(me); + Events.AddEvent(etevent, Events.CalculateTime(std::chrono::milliseconds(750))); + + if (!me->IsInWorld() || !me->IsAlive() || CCed(me)) + { + Windwalk_Timer = 0; + return; + } + + Unit* target = ObjectAccessor::GetUnit(*me, targetGuid); + if (!target || !target->IsAlive()) + { + me->PlayDistanceSound(SOUND_MISS_WHOOSH_2H); + Windwalk_Timer = 0; + return; + } + + if (target->IsImmunedToDamage(SPELL_SCHOOL_MASK_NORMAL)) + { + //target became immune + me->SendSpellMiss(target, CRITICAL_STRIKE_1, SPELL_MISS_IMMUNE); + target->PlayDistanceSound(SOUND_ABSORB_GET_HIT); + Windwalk_Timer = 0; + return; + } + else if (!CanSeeEveryone() && !me->CanSeeOrDetect(target, false, false)) + { + //target disappeared + me->SendSpellMiss(target, CRITICAL_STRIKE_1, SPELL_MISS_MISS/*SPELL_MISS_EVADE*/); + me->PlayDistanceSound(SOUND_MISS_WHOOSH_2H); + Windwalk_Timer = 0; + return; + } + + target->PlayDistanceSound(SOUND_AXE_2H_IMPACT_FLESH_CRIT); + + DamageInfo dinfo(*calcdinfo, 0); + + me->SendSpellNonMeleeDamageLog(target, CRITICAL_STRIKE_1, + dinfo.GetDamage() + dinfo.GetAbsorb() + dinfo.GetResist() + dinfo.GetBlock(), + SPELL_SCHOOL_MASK_NORMAL, dinfo.GetAbsorb(), dinfo.GetResist(), false, dinfo.GetBlock(), true, false); + CleanDamage cl(0, 0, BASE_ATTACK, MELEE_HIT_CRIT); + Unit::DealDamage(me, target, dinfo.GetDamage(), &cl); + Unit::ProcSkillsAndAuras((Unit*)me, calcdinfo->Target, calcdinfo->ProcAttacker, calcdinfo->ProcVictim, 0, 0, calcdinfo->HitInfo, nullptr, &dinfo, nullptr); + me->AtTargetAttacked(target, false); + + me->resetAttackTimer(BASE_ATTACK); + Windwalk_Timer = 0; + } + + void TerminateEvent() + { _dmdevent = nullptr; } + + MeleeHitOutcome GetNextAttackMeleeOutCome() const override + { + return _dmdevent ? MELEE_HIT_NORMAL : bot_ai::GetNextAttackMeleeOutCome(); + } + + float GetBotArmorPenetrationCoef() const override + { + return 80.0f + bot_ai::GetBotArmorPenetrationCoef(); + } + + void SpellHit(WorldObject* wcaster, SpellInfo const* spell) override + { + Unit* caster = wcaster->ToUnit(); + if (!caster) + return; + + uint32 spellId = spell->Id; + + if (spellId == GetSpell(WINDWALK_1)) + { + Windwalk_Timer = 30000; //TODO: + me->RemoveMovementImpairingAuras(true); + me->PlayDistanceSound(SOUND_FREEZE_IMPACT_WINDWALK, !IAmFree() ? master : nullptr); + + uint32 dur = 30000; + if (Aura* aura = me->GetAura(spellId)) + { + aura->SetDuration(dur); + aura->SetMaxDuration(dur); + } + if (Aura* aura = me->GetAura(TRANSPARENCY)) + { + aura->SetDuration(dur); + aura->SetMaxDuration(dur); + } + + if (GetHealthPCT(me) < 25 || !HasRole(BOT_ROLE_DPS)) + me->AttackStop(); + + //SpellEffectSanctuary + me->GetCombatManager().SuppressPvPCombat(); + Unit::AttackerSet const& attackers = me->getAttackers(); + for (Unit::AttackerSet::const_iterator itr = attackers.begin(); itr != attackers.end();) + { + if (!(*itr)->CanSeeOrDetect(me)) + (*(itr++))->AttackStop(); + else + ++itr; + } + + me->m_lastSanctuaryTime = getMSTime(); + } + + if (spellId == GetSpell(MIRROR_IMAGE_1)) + { + MirrorImageStart(); + } + + OnSpellHit(caster, spell); + } + + void SpellHitTarget(WorldObject* wtarget, SpellInfo const* spell) override + { + Unit* target = wtarget->ToUnit(); + if (!target) + return; + + OnSpellHitTarget(target, spell); + } + + void DamageDealt(Unit* victim, uint32& damage, DamageEffectType damageType) override + { + //Illusions deal no damage + if (IsTempBot()) + { + //manually add threat as if damage was done + if (victim->GetTypeId() == TYPEID_UNIT) + victim->GetThreatManager().AddThreat(me, float(damage + damage)); + + damage = 0; + } + + bot_ai::DamageDealt(victim, damage, damageType); + } + + void DamageTaken(Unit* u, uint32& damage, DamageEffectType /*damageType*/, SpellInfo const* /*spellInfo*/) override + { + //illusions take twice as much damage + if (IsTempBot()) + { + damage *= 2; + //return; + } + if (illusion_Fade) + { + damage = 0; + return; + } + if (!u) + return; + if (!u->IsInCombat() && !me->IsInCombat()) + return; + OnOwnerDamagedBy(u); + } + + void OwnerAttackedBy(Unit* u) override + { + if (!u) + return; + OnOwnerDamagedBy(u); + } + + void JustDied(Unit* u) override + { + TerminateEvent(); + + if (IsTempBot()) + if (me->GetCreatorGUID().IsCreature()) + if (Unit* bot = ObjectAccessor::GetUnit(*me, me->GetCreatorGUID())) + if (bot->IsNPCBot()) + bot->ToCreature()->OnBotDespawn(me); + + bot_ai::JustDied(u); + + if (!IsTempBot()) + UnsummonAll(false); + } + + void OnBotDespawn(Creature* summon) override + { + if (!summon) + return; + + for (std::list::iterator itr = _illusionGuids.begin(); itr != _illusionGuids.end(); ++itr) + { + if (*itr == summon->GetGUID()) + { + _illusionGuids.erase(itr); + return; + } + } + } + + void UnsummonAll(bool /*savePets*/ = true) override + { + while (!_illusionGuids.empty()) + { + std::list::iterator itr = _illusionGuids.begin(); + if (Creature* illusion = ObjectAccessor::GetCreature(*me, *itr)) + illusion->AI()->JustDied(nullptr); + else + _illusionGuids.erase(itr); + } + } + + uint32 GetAIMiscValue(uint32 data) const override + { + switch (data) + { + case CRITICAL_STRIKE_1: + return criticalStikeMult; + case MIRROR_IMAGE_1: + return illusionsCount; + default: + return 0; + } + } + + void Reset() override + { + UnsummonAll(false); + + _dmdevent = nullptr; + Windwalk_Timer = 0; + criticalStikeMult = 1; + illusionsCount = 0; + illusion_Fade = false; + + DefaultInit(); + } + + void ReduceCD(uint32 diff) override + { + if (Windwalk_Timer > diff) Windwalk_Timer -= diff; + if (Illusions_Check_Timer > diff) Illusions_Check_Timer -= diff; + } + + void InitPowers() override + { + me->SetPowerType(POWER_MANA); + me->SetMaxPower(POWER_MANA, 75); + } + + void InitSpells() override + { + uint8 lvl = me->GetLevel(); + + /*Special*/lvl >= 10 ? InitSpellMap(WINDWALK_1) : RemoveSpell(WINDWALK_1); + /*Special*/lvl >= 20 ? InitSpellMap(MIRROR_IMAGE_1) : RemoveSpell(MIRROR_IMAGE_1); + + criticalStikeMult = + lvl < 10 ? 1 : + lvl < 30 ? 2 : + lvl < 50 ? 3 : + lvl < 82 ? 4 : 5; + + illusionsCount = + lvl < 20 ? 0 : + lvl < 40 ? 1 : + lvl < 70 ? 2 : 3; + } + + void ApplyClassPassives() const override + { + RefreshAura(SPELL_BURNING_BLADE_BLADEMASTER); + } + + bool CanUseManually(uint32 basespell) const override + { + switch (basespell) + { + case WINDWALK_1: + case MIRROR_IMAGE_1: + return true; + default: + break; + } + + return false; + } + + //std::vector const* GetDamagingSpellsList() const override + //{ + // return &Blademaster_spells_damage; + //} + //std::vector const* GetCCSpellsList() const override + //{ + // return &Blademaster_spells_cc; + //} + //std::vector const* GetHealingSpellsList() const override + //{ + // return &Blademaster_spells_heal; + //} + std::vector const* GetSupportSpellsList() const override + { + return &Blademaster_spells_support; + } + + private: + DelayedMeleeDamageEvent* _dmdevent; + std::list _illusionGuids; + Position _illusPos[MAX_ILLUSION_POSITIONS]; + + uint32 Windwalk_Timer; + uint32 Illusions_Check_Timer; + uint8 criticalStikeMult; + uint8 illusionsCount; + bool illusion_Fade; + }; +}; + +void AddSC_blademaster_bot() +{ + new blademaster_bot(); +} diff --git a/src/server/game/AI/NpcBots/bot_crypt_lord_ai.cpp b/src/server/game/AI/NpcBots/bot_crypt_lord_ai.cpp new file mode 100644 index 000000000..ba3a2f3ba --- /dev/null +++ b/src/server/game/AI/NpcBots/bot_crypt_lord_ai.cpp @@ -0,0 +1,842 @@ +#include "bot_ai.h" +#include "botdatamgr.h" +#include "botspell.h" +#include "bottext.h" +#include "CellImpl.h" +#include "Containers.h" +#include "GridNotifiers.h" +#include "GridNotifiersImpl.h" +#include "MotionMaster.h" +#include "ObjectAccessor.h" +#include "Player.h" +#include "ScriptMgr.h" +#include "Spell.h" +#include "SpellAuras.h" +#include "SpellMgr.h" +#include "TemporarySummon.h" +#include "WorldPacket.h" +/* +Crypt Lord NpcBot (by Trickerer onlysuffering@gmail.com) +Description: +Ancient behemoth, once one of the kings of Azjol-Nerub, now an undead monster within ranks of Lich King's mightiest warriors +Specifics: +Very high armor, increased resistances, partially immune to control effects, immune to poison-based effects, mail/plate armor, +deals melee/spellshadow damage, spell power bonus: 200% strength +Abilities: +1) Impale. Crypt Lord slams the ground with his massive claws, shooting spikes out in a frontal cone, +dealing damage and hurling enemy units into the air in their wake, stunning them. Unlocked at level 20. +2) Spiked Carapace. Crypt Lord's chitinous armor increases damage resistance and returns 15% to 50% damage to enemy melee attackers. +3) Carrion Beetles. Crypt Lord progenerates a Carrion Beetle from a fresh corpse of an enemy to attack his enemies. +Beetles are permanent but do not regenerate health and only 6 can be controlled at a time. +Higher levels allow Crypt Lord to summon more powerful beetles. Unlocked at level 10. +4) Locust Swarm. Crypt Lord releases a swarm of 20-40 (depends on Crypt Lord's level) angry locusts +that bite and tear at nearby enemy units, reducing their ability to move or attack. As they chew the enemy flesh, +they convert it into a substance that restores hit points to the Crypt Lord when they return. Unlocked at level 40. +Complete - 100% +TODO: +*/ + +#ifdef _MSC_VER +# pragma warning(push, 4) +#endif + +static constexpr float IMPALE_DAMAGE_TIME_MS_FULL = 400.0f; +static constexpr float IMPALE_DAMAGE_DIST_MAX = 40.0f; +static constexpr float IMPALE_DAMAGE_DELAY_MS_PER_YD = IMPALE_DAMAGE_TIME_MS_FULL / IMPALE_DAMAGE_DIST_MAX; + +enum CryptLordBaseSpells +{ +//28786 +//54022 + IMPALE_1 = SPELL_IMPALE, + CARRION_BEETLES_1 = SPELL_CARRION_BEETLES, + LOCUST_SWARM_1 = SPELL_LOCUST_SWARM +}; +enum CryptLordPassives +{ +}; +enum CryptLordSpecial +{ + MH_ATTACK_ANIM = SPELL_ATTACK_MELEE_1H, + + SPIKED_CARAPACE_DAMAGE = SPELL_SPIKED_CARAPACE_DAMAGE, + IMPALE_DAMAGE = SPELL_IMPALE_DAMAGE, + IMPALE_VISUAL = SPELL_IMPALE_VISUAL, + STUN_VISUAL = 18970, // "Self Stun - (Visual only)" + + IMPALE_COST = 100 * 5, + CARRION_BEETLES_COST = 30 * 5, + LOCUST_SWARM_COST = 150 * 5, + + MAX_MINIONS = 6, + + SPELL_BLOODY_EXPLOSION = 36599, + + MODEL_BLOODY_BONES = 25538, + + IMPALE_MIN_TARGETS = 3, + + LOCUST_SWARM_MIN_LEVEL = 40, + + MAX_LOCUSTS_BASE = 20, + MAX_LOCUSTS_70 = 30, + MAX_LOCUSTS_MAXLEVEL = 40 +}; + +static const uint32 CryptLord_spells_damage_arr[] = +{ IMPALE_1, LOCUST_SWARM_1 }; + +static const uint32 CryptLord_spells_cc_arr[] = +{ IMPALE_1, LOCUST_SWARM_1 }; + +static const uint32 CryptLord_spells_support_arr[] = +{ CARRION_BEETLES_1 }; + +static const std::vector CryptLord_spells_damage(FROM_ARRAY(CryptLord_spells_damage_arr)); +static const std::vector CryptLord_spells_cc(FROM_ARRAY(CryptLord_spells_cc_arr)); +static const std::vector CryptLord_spells_support(FROM_ARRAY(CryptLord_spells_support_arr)); + +class crypt_lord_bot : public CreatureScript +{ +public: + crypt_lord_bot() : CreatureScript("crypt_lord_bot") { } + + CreatureAI* GetAI(Creature* creature) const override + { + return new crypt_lord_botAI(creature); + } +/* + bool OnGossipHello(Player* player, Creature* creature) override + { + return creature->GetBotAI()->OnGossipHello(player, 0); + } + + bool OnGossipSelect(Player* player, Creature* creature, uint32 sender, uint32 action) override + { + if (bot_ai* ai = creature->GetBotAI()) + return ai->OnGossipSelect(player, creature, sender, action); + return true; + } + + bool OnGossipSelectCode(Player* player, Creature* creature, uint32 sender, uint32 action, char const* code) override + { + if (bot_ai* ai = creature->GetBotAI()) + return ai->OnGossipSelectCode(player, creature, sender, action, code); + return true; + } +*/ + struct crypt_lord_botAI : public bot_ai + { + crypt_lord_botAI(Creature* creature) : bot_ai(creature) + { + _botclass = BOT_CLASS_CRYPT_LORD; + + InitUnitFlags(); + + //crypt_lord immunities + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_POSSESS, true); + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_CHARM, true); + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_SHAPESHIFT, true); + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_TRANSFORM, true); + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_DISARM, true); + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_DISARM_OFFHAND, true); + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_FEAR, true); + me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_DISARM, true); + me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_POLYMORPH, true); + me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_FEAR, true); + me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_HORROR, true); + me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_TURN, true); + me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_SLEEP, true); + + _locusts.resize(MAX_LOCUSTS_MAXLEVEL, ObjectGuid::Empty); + } + + bool doCast(Unit* victim, uint32 spellId) + { + if (CheckBotCast(victim, spellId) != SPELL_CAST_OK) + return false; + if (spellId == GetSpell(IMPALE_1)) + { + me->AttackStop(); + me->BotStopMovement(); + me->SetInFront(victim); + } + else if (spellId == GetSpell(LOCUST_SWARM_1)) + { + me->AttackStop(); + me->BotStopMovement(); + } + return bot_ai::doCast(victim, spellId); + } + + void StartAttack(Unit* u, bool force = false) + { + if (!bot_ai::StartAttack(u, force)) + return; + GetInPosition(force, u); + } + + void JustEnteredCombat(Unit* u) override { bot_ai::JustEnteredCombat(u); } + void KilledUnit(Unit* u) override { bot_ai::KilledUnit(u); } + void EnterEvadeMode(EvadeReason why = EVADE_REASON_OTHER) override { bot_ai::EnterEvadeMode(why); } + void MoveInLineOfSight(Unit* u) override { bot_ai::MoveInLineOfSight(u); } + void JustDied(Unit* u) override { UnsummonLocusts(true); UnsummonAll(false); bot_ai::JustDied(u); } + + void DoNonCombatActions(uint32 diff) + { + CheckCarrionBeetles(diff); + } + + void CheckCarrionBeetles(uint32 diff) + { + if (!IsSpellReady(CARRION_BEETLES_1, diff) || _carrionBeetlesCheckTimer > diff || _minions.size() >= MAX_MINIONS || + me->GetPower(POWER_MANA) < CARRION_BEETLES_COST || Rand() > 50) + return; + + _carrionBeetlesCheckTimer = urand(2000, 3000); + + auto corpse_pred = [this, mindist = 30.f](Creature const* c) mutable { + if (_isUsableCorpse(c) && c->GetDistance(me) < mindist) + { + mindist = c->GetDistance(me); + return true; + } + return false; + }; + Creature* creature = nullptr; + Trinity::CreatureSearcher searcher(me, creature, corpse_pred); + Cell::VisitAllObjects(me, searcher, 30.f); + + if (creature) + { + me->SetInFront(creature); + if (doCast(creature, GetSpell(CARRION_BEETLES_1))) + return; + } + } + + void CheckLocustSwarm(uint32 diff) + { + if (!IsSpellReady(LOCUST_SWARM_1, diff) || _locustSwarmCheckTimer > diff || me->GetPower(POWER_MANA) < LOCUST_SWARM_COST || + GetHealthPCT(me) > 40 || Rand() > 35) + return; + + _locustSwarmCheckTimer = urand(1500, 3500); + + std::list targets; + GetNearbyTargetsList(targets, 50.0f, 0); + + if (targets.empty()) // impossible + return; + + if (targets.size() > 1u || targets.front()->IsControlledByPlayer()) + { + if (doCast(me, GetSpell(LOCUST_SWARM_1))) + return; + } + } + + void UpdateAI(uint32 diff) override + { + if (!GlobalUpdate(diff)) + return; + + DoVehicleActions(diff); + if (!CanBotAttackOnVehicle()) + return; + + if (!me->IsInCombat()) + DoNonCombatActions(diff); + + if (IsPotionReady()) + { + if (me->GetPower(POWER_MANA) < 550) + DrinkPotion(true); + else if (GetHealthPCT(me) < 50) + DrinkPotion(false); + } + + if (IsCasting()) + return; + + //if (IsSpellReady(INFERNO_1, diff) && !botPet && me->IsInCombat() && + // me->GetPower(POWER_MANA) >= INFERNAL_COST && Rand() < 60) + //{ + // Unit* target = FindAOETarget(CalcSpellMaxRange(INFERNO_1)); + + // if (target) + // _infernoPos = target->GetPosition(); + // else + // me->GetNearPoint(me, _infernoPos.m_positionX, _infernoPos.m_positionY, _infernoPos.m_positionZ, 5.f, 0.f); + + // me->CastSpell(_infernoPos, GetSpell(INFERNO_1), false); + // return; + //} + + if (ProcessImmediateNonAttackTarget()) + return; + + if (!CheckAttackTarget()) + return; + + CheckLocustSwarm(diff); + + CheckUsableItems(diff); + + Attack(diff); + } + + void Attack(uint32 diff) + { + Unit* mytar = opponent ? opponent : disttarget ? disttarget : nullptr; + if (!mytar) + return; + + StartAttack(mytar, IsMelee()); + + MoveBehind(mytar); + + if (!HasRole(BOT_ROLE_DPS)) + return; + + if (IsSpellReady(IMPALE_1, diff) && _impaleCheckTimer <= diff && me->GetPower(POWER_MANA) >= IMPALE_COST && + me->isAttackReady() && Rand() < 75) + { + _impaleCheckTimer = urand(800, 1600); + + SpellInfo const* impaleSpellInfo = sSpellMgr->AssertSpellInfo(GetSpell(IMPALE_1)); + impaleSpellInfo = impaleSpellInfo->TryGetSpellInfoOverride(me); + if (me->GetExactDist(mytar) < IMPALE_DAMAGE_DIST_MAX && + (mytar->IsNonMeleeSpellCast(false) || GetManaPCT(me) > 90 || mytar->GetHealth() < me->GetMaxHealth() / 4 || + (mytar->IsControlledByPlayer() && mytar->GetHealth() > me->GetHealth())) && + !(mytar->IsImmunedToSpellEffect(impaleSpellInfo, impaleSpellInfo->GetEffect(EFFECT_1), me) && + mytar->IsImmunedToSpellEffect(impaleSpellInfo, impaleSpellInfo->GetEffect(EFFECT_2), me))) + { + Spell const* tarSpell = mytar->GetCurrentSpell(CURRENT_GENERIC_SPELL); + tarSpell = tarSpell ? tarSpell : mytar->GetCurrentSpell(CURRENT_CHANNELED_SPELL); + if (!tarSpell || tarSpell->GetTimer() > 500) + { + if (doCast(mytar, GetSpell(IMPALE_1))) + return; + } + } + + static const std::array my_angles = { 0.0f, float(M_PI / 2), float(M_PI / 2) * 2.0f, float(M_PI / 2) * 3.0f }; + + std::list impale_targets; + GetNearbyTargetsList(impale_targets, IMPALE_DAMAGE_DIST_MAX, 0); + + std::array direction_targets{}; + for (Unit* u : impale_targets) + { + float angle = me->GetRelativeAngle(u); + for (size_t i = 0; i < std::size(my_angles); ++i) + { + float rborder = Position::NormalizeOrientation(my_angles[i] - float(M_PI) * 0.25f); + float lborder = Position::NormalizeOrientation(my_angles[i] + float(M_PI) * 0.25f); + if ((angle > rborder && angle < lborder) || u->IsWithinMeleeRange(me)) + { + direction_targets[i].push_back(u); + break; + } + } + } + + std::add_pointer_t> chosen_targets = nullptr; + size_t max_count = IMPALE_MIN_TARGETS - 1; + for (decltype(direction_targets)::value_type const& tlist : direction_targets) + { + if (tlist.size() > max_count) + { + chosen_targets = &tlist; + max_count = tlist.size(); + } + } + + if (chosen_targets) + { + Unit* target = Trinity::Containers::SelectRandomContainerElement(*chosen_targets); + if (target && doCast(target, GetSpell(IMPALE_1))) + return; + } + } + + CheckAttackState(); + //if (!me->IsAlive() || !mytar->IsAlive()) + // return; + } + + void ApplyClassDamageMultiplierMeleeSpell(int32& damage, SpellNonMeleeDamage& /*damageinfo*/, SpellInfo const* spellInfo, WeaponAttackType /*attackType*/, bool /*iscrit*/) const override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + uint8 lvl = me->GetLevel(); + float fdamage = float(damage); + + // apply bonus damage mods + float pctbonus = 1.0f; + + //Impale Damage scaling from AP + if (lvl >= 40 && baseId == IMPALE_DAMAGE) + { + float ap_factor = lvl >= 60 ? 0.125f : 0.0625f; + float total_ap = me->GetTotalAttackPowerValue(BASE_ATTACK); + fdamage += total_ap * ap_factor; + if (lvl >= 70) + pctbonus *= 1.1f; + if (lvl >= DEFAULT_MAX_LEVEL) + pctbonus *= 1.2f; + } + + damage = int32(fdamage * pctbonus); + } + + //void ApplyClassSpellRadiusMods(SpellInfo const* spellInfo, float& radius) const override + //{ + // uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + // float flatbonus = 0.0f; + // float pctbonus = 0.0f; + + // //100% mods + // if (baseId == IMPALE_DAMAGE) + // radius = 0.0f; + + // radius = radius * (1.0f + pctbonus) + flatbonus; + //} + + void OnClassSpellStart(SpellInfo const* spellInfo) override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + switch (baseId) + { + case IMPALE_1: + case CARRION_BEETLES_1: + me->CastSpell(me, MH_ATTACK_ANIM, true); + break; + case LOCUST_SWARM_1: + _handleLocustSwarm(); + break; + default: + break; + } + } + + void OnClassSpellGo(SpellInfo const* /*spellInfo*/) override + { + } + + void OnClassChannelFinish(Spell const* spell) override + { + SpellInfo const* spellInfo = spell->GetSpellInfo(); + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + + if (baseId == IMPALE_1 || baseId == CARRION_BEETLES_1 || baseId == LOCUST_SWARM_1) + { + if (baseId == CARRION_BEETLES_1) + { + ObjectGuid target_guid = spell->m_targets.GetUnitTargetGUID(); + if (Unit* spellTarget = ObjectAccessor::GetUnit(*me, target_guid)) + { + ASSERT(!IsInBotParty(spellTarget)); + spellTarget->CastSpell(spellTarget, SPELL_BLOODY_EXPLOSION, true); + spellTarget->SetDisplayId(MODEL_BLOODY_BONES); + + for (int32 i = 0; i < spellInfo->GetEffect(EFFECT_0).BasePoints; ++i) + SummonBotPet(spellTarget); + } + } + + me->CastSpell(me, SPELL_BRIEF_STUN, true); + } + } + + void SpellHitTarget(WorldObject* wtarget, SpellInfo const* spell) override + { + Unit* target = wtarget->ToUnit(); + if (!target) + return; + + uint32 baseId = spell->GetFirstRankSpell()->Id; + + if (baseId == IMPALE_1) + { + if (me->IsValidAttackTarget(target)) + { + uint32 base_delay = uint32(std::max(spell->GetDuration(), 0)); + uint32 ms_delay = base_delay + uint32(IMPALE_DAMAGE_DELAY_MS_PER_YD * me->GetExactDist2d(target)); + //BotWhisper("Impale hit " + target->GetName() + " (" + std::to_string(target->GetGUID().GetCounter()) + "), delay: " + std::to_string(ms_delay)); + //We use WO events container since bot ai events get cleaned at death, see JustDied() + me->m_Events.AddEventAtOffset([this, guid = target->GetGUID()]() { + if (Unit* u = ObjectAccessor::GetUnit(*me, guid)) + { + if (u->GetDistance(me) < IMPALE_DAMAGE_DIST_MAX + 5.0f) + me->CastSpell(u, IMPALE_DAMAGE, true); + } + }, std::chrono::milliseconds(ms_delay)); + } + } + else if (baseId == IMPALE_DAMAGE) + { + //BotWhisper("Impale damage hit " + target->GetName() + " (" + std::to_string(target->GetGUID().GetCounter()) + ')'); + me->CastSpell(target, IMPALE_VISUAL, true); + if (Aura* stun = target->AddAura(STUN_VISUAL, target)) + { + int32 dur = std::max(spell->GetMaxDuration(), 6000); + stun->SetDuration(dur); + stun->SetMaxDuration(dur); + } + } + + OnSpellHitTarget(target, spell); + } + + void SpellHit(WorldObject* wcaster, SpellInfo const* spell) override + { + Unit* caster = wcaster->ToUnit(); + if (!caster) + return; + + OnSpellHit(caster, spell); + } + + void DamageTaken(Unit* u, uint32& damage, DamageEffectType damageType, SpellInfo const* spellInfo) override + { + if (u && damage && (damageType == DIRECT_DAMAGE || damageType == SPELL_DIRECT_DAMAGE) && + (!spellInfo || spellInfo->DmgClass == SPELL_DAMAGE_CLASS_MELEE) && me->IsValidAttackTarget(u)) + { + SpellInfo const* damageSpellInfo = sSpellMgr->AssertSpellInfo(SPIKED_CARAPACE_DAMAGE); + if (u->IsImmunedToDamage(damageSpellInfo)) + { + me->SendSpellDamageImmune(u, SPIKED_CARAPACE_DAMAGE); + } + else + { + uint32 damage_returned_pct = _getSpikesDamageReflectPct(); + uint32 damage_returned = CalculatePct(damage, damage_returned_pct); + if (damage_returned) + { + WorldPacket data(SMSG_SPELLDAMAGESHIELD, 8 + 8 + 4 + 4 + 4 + 4 + 4); + data << uint64(me->GetGUID()); + data << uint64(u->GetGUID()); + data << uint32(damageSpellInfo->Id); + data << uint32(damage_returned); + data << uint32(std::max(int32(damage_returned) - int32(u->GetHealth()), 0)); + data << uint32(damageSpellInfo->SchoolMask); + me->SendMessageToSet(&data, true); + Unit::DealDamage(me, u, damage_returned, nullptr, SPELL_DIRECT_DAMAGE, damageSpellInfo->GetSchoolMask(), damageSpellInfo, true); + } + } + } + + if (!u) + return; + if (!u->IsInCombat() && !me->IsInCombat()) + return; + OnOwnerDamagedBy(u); + } + + void OwnerAttackedBy(Unit* u) override + { + if (!u) + return; + OnOwnerDamagedBy(u); + } + + uint8 GetPetPositionNumber(Creature const* summon) const override + { + uint8 i = 0; + for (Summons::const_iterator citr = _minions.begin(); citr != _minions.end(); ++citr) + { + if ((*citr)->GetGUID() == summon->GetGUID()) + return i; + ++i; + } + return 0; + } + + void SummonBotPet(Unit const* from) + { + if (_minions.size() >= MAX_MINIONS) + { + Unit* u = nullptr; + //try 1: by minimal level + uint8 minlevel = me->GetLevel(); + for (Summons::const_iterator itr = _minions.begin(); itr != _minions.end(); ++itr) + { + if ((*itr)->GetLevel() < minlevel) + { + minlevel = (*itr)->GetLevel(); + u = *itr; + } + } + //try 2: last resort + if (!u) + u = *(_minions.begin()); + + u->ToTempSummon()->UnSummon(); + } + + uint32 entry = _getCarrionBeetleEntry(); + Position pos = from->GetPosition(); + + Creature* myPet = me->SummonCreature(entry, pos, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 2s); + myPet->SetCreator(master); + myPet->SetOwnerGUID(me->GetGUID()); + myPet->SetFaction(master->GetFaction()); + myPet->SetControlledByPlayer(!IAmFree()); + myPet->SetPvP(me->IsPvP()); + myPet->SetByteValue(UNIT_FIELD_BYTES_2, 1, master->GetByteValue(UNIT_FIELD_BYTES_2, 1)); + + myPet->SetUInt32Value(UNIT_CREATED_BY_SPELL, CARRION_BEETLES_1); + + //myPet->SetMeleeDamageSchool(SPELL_SCHOOL_FIRE); + if (Aura* stun = myPet->AddAura(SUMMONING_DISORIENTATION, myPet)) + { + stun->SetDuration(1500); + stun->SetMaxDuration(1500); + } + + myPet->GetAI()->SetData(BOTPETAI_MISC_MAXLEVEL, me->GetLevel()); + _minions.insert(myPet); + } + + void SummonLocust(uint32 offset) + { + ObjectGuid old_locust_guid = _locusts[offset]; + if (!old_locust_guid.IsEmpty()) + { + if (Creature* old_locust = ObjectAccessor::GetCreature(*me, old_locust_guid)) + old_locust->KillSelf(); + } + + uint32 entry = BOT_PET_LOCUST_SWARM; + Position pos = me->GetNearPosition(frand(5.0f, 15.0f), frand(float(M_PI * 0.75), float(M_PI * 1.25))); + + Creature* locust = me->SummonCreature(entry, *me, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 1s); + locust->SetCreator(master); + locust->SetOwnerGUID(me->GetGUID()); + locust->SetFaction(master->GetFaction()); + locust->SetControlledByPlayer(!IAmFree()); + locust->SetPvP(me->IsPvP()); + locust->SetByteValue(UNIT_FIELD_BYTES_2, 1, master->GetByteValue(UNIT_FIELD_BYTES_2, 1)); + + locust->SetUInt32Value(UNIT_CREATED_BY_SPELL, LOCUST_SWARM_1); + + locust->GetMotionMaster()->MovePoint(1, pos, false); + + locust->GetAI()->SetData(BOTPETAI_MISC_CAPACITY, CalculatePct(me->GetMaxHealth(), uint32(2))); + locust->GetAI()->SetData(BOTPETAI_MISC_MAX_ATTACKERS, CalculatePct(me->GetMaxHealth(), (_getMaxLocusts() + 2) / 3)); + _locusts[offset] = locust->GetGUID(); + } + + void UnsummonAll(bool savePets = true) override + { + UnsummonCreatures(_minions, savePets); + UnsummonLocusts(false); + } + + void UnsummonLocusts(bool kill) + { + for (ObjectGuid locust_guid : _locusts) + { + if (!locust_guid.IsEmpty()) + { + if (Creature* locust = ObjectAccessor::GetCreature(*me, locust_guid)) + { + if (kill) + locust->KillSelf(false); + else + locust->ToTempSummon()->UnSummon(); + } + } + } + } + + void SummonedCreatureDies(Creature* /*summon*/, Unit* /*killer*/) override + { + } + + void SummonedCreatureDespawn(Creature* summon) override + { + if (_minions.find(summon) != _minions.end()) + _minions.erase(summon); + else + { + Swarm::iterator it = std::find(std::begin(_locusts), std::end(_locusts), summon->GetGUID()); + if (it != std::end(_locusts)) + *it = ObjectGuid::Empty; + } + } + + uint32 GetAIMiscValue(uint32 data) const override + { + switch (data) + { + case BOTAI_MISC_PET_TYPE: + return _getCarrionBeetleEntry(); + default: + return 0; + } + } + + void Reset() override + { + _impaleCheckTimer = 0; + _carrionBeetlesCheckTimer = 0; + _locustSwarmCheckTimer = 0; + + UnsummonAll(false); + + DefaultInit(); + } + + void ReduceCD(uint32 diff) override + { + if (_impaleCheckTimer > diff) _impaleCheckTimer -= diff; + if (_carrionBeetlesCheckTimer > diff) _carrionBeetlesCheckTimer -= diff; + if (_locustSwarmCheckTimer > diff) _locustSwarmCheckTimer -= diff; + } + + void InitPowers() override + { + me->SetPowerType(POWER_MANA); + me->SetMaxPower(POWER_MANA, BASE_MANA_1_CRYPT_LORD); + } + + void InitSpells() override + { + InitSpellMap(IMPALE_1); + InitSpellMap(CARRION_BEETLES_1); + InitSpellMap(LOCUST_SWARM_1); + } + + void ApplyClassPassives() const override + { + RefreshAura(SPELL_NULLIFY_POISON); + } + + bool CanUseManually(uint32 basespell) const override + { + switch (basespell) + { + case IMPALE_1: + //case CARRION_BEETLES_1: + case LOCUST_SWARM_1: + return true; + default: + return false; + } + } + + bool HasAbilitiesSpecifics() const override { return true; } + void FillAbilitiesSpecifics(Player const* player, std::list &specList) override + { + specList.push_back(LocalizedNpcText(player, BOT_TEXT_REFLECT) + ": " + std::to_string(_getSpikesDamageReflectPct()) + '%'); + if (me->GetLevel() >= LOCUST_SWARM_MIN_LEVEL) + specList.push_back(LocalizedNpcText(player, BOT_TEXT_LOCUSTS) + ": " + std::to_string(_getMaxLocusts())); + } + + std::vector const* GetDamagingSpellsList() const override + { + return &CryptLord_spells_damage; + } + std::vector const* GetCCSpellsList() const override + { + return &CryptLord_spells_cc; + } + //std::vector const* GetHealingSpellsList() const override + //{ + // return &CryptLord_spells_heal; + //} + std::vector const* GetSupportSpellsList() const override + { + return &CryptLord_spells_support; + } + + private: + void _handleLocustSwarm() + { + static const uint32 LOCUSTS_RELEASE_TIME = 4000u; + uint32 max_locusts = _getMaxLocusts(); + for (uint32 i = 0; i < max_locusts; ++i) + { + uint32 offset = LOCUSTS_RELEASE_TIME / max_locusts * (i + 1); + Events.AddEventAtOffset([this, num = i]() { + SummonLocust(num); + }, std::chrono::milliseconds(offset)); + } + } + + uint32 _getMaxLocusts() const + { + uint8 lvl = me->GetLevel(); + uint32 max_locusts; + if (lvl >= DEFAULT_MAX_LEVEL + BotDataMgr::GetLevelBonusForBotRank(me->GetCreatureTemplate()->rank)) + max_locusts = MAX_LOCUSTS_MAXLEVEL; + else if (lvl >= 70) + max_locusts = MAX_LOCUSTS_70; + else + max_locusts = MAX_LOCUSTS_BASE; + + return max_locusts; + } + + uint32 _getSpikesDamageReflectPct() const + { + uint8 lvl = me->GetLevel(); + uint32 damage_returned_pct; + if (lvl >= 70) + damage_returned_pct = 50; + else if (lvl >= 60) + damage_returned_pct = 35; + else if (lvl >= 40) + damage_returned_pct = 25; + else if (lvl >= 20) + damage_returned_pct = 20; + else + damage_returned_pct = 15; + return damage_returned_pct; + } + + uint32 _getCarrionBeetleEntry() const + { + uint32 entry; + uint8 lvl = me->GetLevel(); + if (lvl >= 60) + entry = BOT_PET_CARRION_BEETLE3; + else if (lvl >= 30) + entry = BOT_PET_CARRION_BEETLE2; + else + entry = BOT_PET_CARRION_BEETLE1; + return entry; + } + + bool _isUsableCorpse(Creature const* c) const + { + static const uint32 ViableCreatureTypesMask = + (1 << (CREATURE_TYPE_BEAST-1)) | (1 << (CREATURE_TYPE_DRAGONKIN-1)) | (1 << (CREATURE_TYPE_HUMANOID-1)); + + return c->getDeathState() == DeathState::CORPSE && c->GetDisplayId() == c->GetNativeDisplayId() && + !c->IsVehicle() && !c->isWorldBoss() && !c->IsDungeonBoss() && + ((1 << (c->GetCreatureType()-1)) & ViableCreatureTypesMask) && + !c->IsControlledByPlayer() && !c->IsNPCBot(); + } + + uint32 _impaleCheckTimer; + uint32 _carrionBeetlesCheckTimer; + uint32 _locustSwarmCheckTimer; + + typedef std::set Summons; + Summons _minions; + typedef std::vector Swarm; + Swarm _locusts; + }; +}; + +void AddSC_crypt_lord_bot() +{ + new crypt_lord_bot(); +} + +#ifdef _MSC_VER +# pragma warning(pop) +#endif diff --git a/src/server/game/AI/NpcBots/bot_dark_ranger_ai.cpp b/src/server/game/AI/NpcBots/bot_dark_ranger_ai.cpp new file mode 100644 index 000000000..6026b50d4 --- /dev/null +++ b/src/server/game/AI/NpcBots/bot_dark_ranger_ai.cpp @@ -0,0 +1,603 @@ +#include "bot_ai.h" +#include "bot_GridNotifiers.h" +#include "botspell.h" +#include "Containers.h" +#include "ScriptMgr.h" +#include "SpellAuraEffects.h" +#include "TemporarySummon.h" +/* +Dark Ranger NpcBot (by Trickerer onlysuffering@gmail.com) +Description: +A former ranger of Quel'thalas forcibly raised from the dead (Warcraft III tribute) +Specifics: +Spell damage taken reduced by 35%, partially immune to control effects, leather/cloth armor, +deals physical/spellshadow damage, spell power bonus: 50% intellect. Main attribute: Agility +Abilities: +1) Silence. Silences an enemy and up to 4 nearby targets for 8 seconds, 15 seconds cooldown +2) Black Arrow. Fires a cursed arrow dealing 150% weapon damage and additional spellshadow damage over time. +If affected target dies from Dark Ranger\'s damage, a Dark Minion will spawn from the corpse +(maximum 5 Minions, 80 seconds duration, only works on humanoids, beasts and dragonkin), +skeleton level depends on level of the killed unit +Deals five times more damage if target is under 20% health +3) Drain Life. Drains health from an enemy every second for 5 seconds (6 ticks), +healing Dark Ranger for 200% of the drained amount +4) Charm NIY +5ex) Auto Shot. A hunter auto shot ability since dark ranger is purely ranged and only uses bows. +Complete - 75% +TODO: Charm +*/ + +enum DarkRangerBaseSpells +{ + AUTO_SHOT_1 = 75, + BLACK_ARROW_1 = SPELL_BLACK_ARROW, + DRAIN_LIFE_1 = SPELL_DRAIN_LIFE, + SILENCE_1 = SPELL_SILENCE +}; +enum DarkRangerPassives +{ +}; +enum DarkRangerSpecial +{ + DRAINLIFE_COST = 75 * 5, + MAX_MINIONS = 5, + + SPELL_SPAWN_ANIM = 25035, + SPELL_BLOODY_EXPLOSION = 36599, + SPELL_THREAT_MOD = 31745, //Salvation + + MODEL_BLOODY_BONES = 25538 +}; + +static const uint32 Darkranger_spells_damage_arr[] = +{ BLACK_ARROW_1, DRAIN_LIFE_1 }; + +static const uint32 Darkranger_spells_cc_arr[] = +{ SILENCE_1 }; + +static const std::vector Darkranger_spells_damage(FROM_ARRAY(Darkranger_spells_damage_arr)); +static const std::vector Darkranger_spells_cc(FROM_ARRAY(Darkranger_spells_cc_arr)); + +class dark_ranger_bot : public CreatureScript +{ +public: + dark_ranger_bot() : CreatureScript("dark_ranger_bot") { } + + CreatureAI* GetAI(Creature* creature) const override + { + return new dark_ranger_botAI(creature); + } + + struct dark_ranger_botAI : public bot_ai + { + dark_ranger_botAI(Creature* creature) : bot_ai(creature) + { + _botclass = BOT_CLASS_DARK_RANGER; + + InitUnitFlags(); + + //dark ranger immunities + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_POSSESS, true); + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_CHARM, true); + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_SHAPESHIFT, true); + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_TRANSFORM, true); + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_DISARM, true); + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_DISARM_OFFHAND, true); + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_DISARM_RANGED, true); + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_FEAR, true); + me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_DISARM, true); + me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_POLYMORPH, true); + me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_FEAR, true); + me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_HORROR, true); + me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_TURN, true); + me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_SLEEP, true); + } + + bool doCast(Unit* victim, uint32 spellId) + { + if (CheckBotCast(victim, spellId) != SPELL_CAST_OK) + return false; + + return bot_ai::doCast(victim, spellId); + } + + void StartAttack(Unit* u, bool force = false) + { + if (!bot_ai::StartAttack(u, force)) + return; + GetInPosition(force, u); + } + + void JustEnteredCombat(Unit* u) override { bot_ai::JustEnteredCombat(u); } + void EnterEvadeMode(EvadeReason why = EVADE_REASON_OTHER) override { bot_ai::EnterEvadeMode(why); } + void MoveInLineOfSight(Unit* u) override { bot_ai::MoveInLineOfSight(u); } + void JustDied(Unit* u) override { UnsummonAll(false); bot_ai::JustDied(u); } + void DoNonCombatActions(uint32 /*diff*/) { } + + void KilledUnit(Unit* u) override + { + bot_ai::KilledUnit(u); + + if (u->GetGUID() == _blackArrowKillGUID) + { + //black arrow affection -> spawn skeleton (launch) + SummonBotPet(u); + } + } + + void Counter(uint32 diff) + { + if (Rand() > 55) + return; + + if (IsSpellReady(SILENCE_1, diff)) + { + Unit* target = FindCastingTarget(CalcSpellMaxRange(SILENCE_1), 0, SILENCE_1); + if (target && doCast(target, GetSpell(SILENCE_1))) + return; + } + } + + void UpdateAI(uint32 diff) override + { + if (!GlobalUpdate(diff)) + return; + + DoVehicleActions(diff); + if (!CanBotAttackOnVehicle()) + return; + + CheckDrainLife(diff); + + if (IsPotionReady()) + { + if (me->GetPower(POWER_MANA) < DRAINLIFE_COST) + DrinkPotion(true); + else if (GetHealthPCT(me) < 30) + DrinkPotion(false); + } + + if (ProcessImmediateNonAttackTarget()) + return; + + if (!CheckAttackTarget()) + { + me->InterruptSpell(CURRENT_AUTOREPEAT_SPELL); + + if (!IAmFree() && me->IsStandState() && !me->isMoving() && !master->isMoving() && !me->IsMounted() && + !me->IsInCombat() && !master->IsInCombat() && Rand() < 10 && me->GetDistance(master) < 15 && + !me->HasStealthAura() && !me->HasInvisibilityAura() && !me->HasAuraType(SPELL_AURA_PERIODIC_DAMAGE) && + _minions.empty()) + { + me->CastSpell(me, RACIAL_SHADOWMELD, true); + } + + return; + } + + if (IsCasting()) + return; + + CheckUsableItems(diff); + + DoRangedAttack(diff); + } + + void DoRangedAttack(uint32 diff) + { + Unit* mytar = opponent ? opponent : disttarget ? disttarget : nullptr; + if (!mytar) + return; + + StartAttack(mytar, IsMelee()); + + CheckAttackState(); + if (!me->IsAlive() || !mytar->IsAlive()) + return; + + Counter(diff); + + CheckBlackArrow(diff); + + MoveBehind(mytar); + + float dist = me->GetDistance(mytar); + float maxRangeLong = 30.f; + + bool inpostion = !mytar->HasAuraType(SPELL_AURA_MOD_CONFUSE) || dist > maxRangeLong - 15.f; + + //Auto Shot + if (Spell const* shot = me->GetCurrentSpell(CURRENT_AUTOREPEAT_SPELL)) + { + if (shot->GetSpellInfo()->Id == AUTO_SHOT_1 && (shot->m_targets.GetUnitTarget() != mytar || !inpostion)) + me->InterruptSpell(CURRENT_AUTOREPEAT_SPELL); + } + else if (HasRole(BOT_ROLE_DPS)/* && dist > 5*/ && dist < maxRangeLong) + { + if (doCast(mytar, AUTO_SHOT_1)) + {} + } + + //RANGED SECTION + if (dist > maxRangeLong) + return; + + //Black Arrow + if (IsSpellReady(BLACK_ARROW_1, diff) && HasRole(BOT_ROLE_DPS) && + (Rand() < 20 || !mytar->GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_WARLOCK, 0x0, 0x4, 0x0, me->GetGUID()))) + { + if (doCast(mytar, GetSpell(BLACK_ARROW_1))) + return; + } + } + + void CheckDrainLife(uint32 diff) + { + if (!IsSpellReady(DRAIN_LIFE_1, diff) || !HasRole(BOT_ROLE_DPS) || GetHealthPCT(me) > 70 || Rand() > 80 || + IsCasting()) + return; + + Unit* target = me->GetVictim(); + if (!target) + target = FindAffectedTarget(GetSpell(BLACK_ARROW_1), me->GetGUID(), 30); + + if (target && doCast(target, GetSpell(DRAIN_LIFE_1))) + return; + } + + void CheckBlackArrow(uint32 diff) + { + if (!IsSpellReady(BLACK_ARROW_1, diff) || !HasRole(BOT_ROLE_DPS) || Rand() > 30) + return; + + std::list targets; + GetNearbyTargetsList(targets, 50, 0); + targets.remove_if(BOTAI_PRED::AuraedTargetExcludeByCaster(BLACK_ARROW_1, me->GetGUID())); + if (Unit* target = !targets.empty() ? Trinity::Containers::SelectRandomContainerElement(targets) : nullptr) + { + if (doCast(target, GetSpell(BLACK_ARROW_1))) + return; + } + } + + void ApplyClassSpellCritMultiplierAll(Unit const* /*victim*/, float& /*crit_chance*/, SpellInfo const* /*spellInfo*/, SpellSchoolMask /*schoolMask*/, WeaponAttackType /*attackType*/) const override + { + } + + void ApplyClassDamageMultiplierMeleeSpell(int32& damage, SpellNonMeleeDamage& damageinfo, SpellInfo const* spellInfo, WeaponAttackType /*attackType*/, bool /*iscrit*/) const override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + float flat_mod = 0.f; + + //2) apply bonus damage mods + float pctbonus = 1.0f; + //Black Arrow on targets < 20% hp (only direct damage) + if (baseId == BLACK_ARROW_1 && damageinfo.target && damageinfo.target->HasAuraState(AURA_STATE_HEALTHLESS_20_PERCENT)) + pctbonus *= 5.f; + + damage = int32(damage * pctbonus + flat_mod); + } + + void ApplyClassDamageMultiplierSpell(int32& damage, SpellNonMeleeDamage& /*damageinfo*/, SpellInfo const* /*spellInfo*/, WeaponAttackType /*attackType*/, bool /*iscrit*/) const override + { + //uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //uint8 lvl = me->GetLevel(); + float fdamage = float(damage); + float flat_mod = 0.f; + + //2) apply bonus damage mods + float pctbonus = 1.0f; + //if (iscrit) + //{ + // //!!!spell damage is not yet critical and will be multiplied by 1.5 + // //so we should put here bonus damage mult /1.5 + // //Lava Flows (part 1): 24% additional crit damage bonus for Lava Burst + // if (lvl >= 50 && spellId == GetSpell(LAVA_BURST_1)) + // pctbonus *= 1.16f; + //} + ////Trap Mastery part 2: 30% bonus damage for Immolation Trap, Explosive Trap and Black Arrow + //if (lvl >= 15 && (baseId == IMMOLATION_TRAP_AURA_1 || baseId == EXPLOSIVE_TRAP_AURA_1 || baseId == BLACK_ARROW_1)) + // pctbonus *= 1.3f; + //Black Arrow on targets < 20% hp (only direct damage) + //if (baseId == BLACK_ARROW_1 && damageinfo.target && damageinfo.target->HasAuraState(AURA_STATE_HEALTHLESS_20_PERCENT)) + // pctbonus *= 5.f; + + damage = int32(fdamage * pctbonus + flat_mod); + } + + void ApplyClassEffectMods(SpellInfo const* spellInfo, uint8 effIndex, float& value) const override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //uint8 lvl = me->GetLevel(); + float pctbonus = 1.0f; + + //Drain Life scaling from health pool: 2% + if (baseId == DRAIN_LIFE_1 && effIndex == EFFECT_0) + value += float(me->GetMaxHealth()) * 0.02f; + + value = value * pctbonus; + } + + void OnClassSpellGo(SpellInfo const* /*spellInfo*/) override + { + //uint32 spellId = spellInfo->Id; + //uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + + //Rapid Killing: use up buff manually + //if (baseId == AIMED_SHOT_1 || baseId == ARCANE_SHOT_1 || baseId == CHIMERA_SHOT_1) + //{ + // if (AuraEffect const* rapi = me->GetAuraEffect(RAPID_KILLING_BUFF, 0)) + // if (rapi->IsAffectingSpell(spellInfo)) + // me->RemoveAura(RAPID_KILLING_BUFF); + //} + } + + void SpellHitTarget(WorldObject* wtarget, SpellInfo const* spell) override + { + Unit* target = wtarget->ToUnit(); + if (!target) + return; + + if (target == me) + return; + + //uint32 baseId = spell->GetFirstRankSpell()->Id; + //uint8 lvl = me->GetLevel(); + + //if (baseId == HUNTERS_MARK_1) + //{ + // //DarkRanger's Mark helper + // if (AuraEffect* mark = target->GetAuraEffect(spell->Id, 1, me->GetGUID())) + // { + // //Glyph of DarkRanger's Mark: +20% effect + // //Improved DarkRanger's Mark: +30% effect + // if (lvl >= 15) + // mark->ChangeAmount(mark->GetAmount() + mark->GetAmount() / 2); + // else if (lvl >= 10) + // mark->ChangeAmount(mark->GetAmount() * 13 / 10); + // } + //} + + OnSpellHitTarget(target, spell); + } + + void SpellHit(WorldObject* wcaster, SpellInfo const* spell) override + { + Unit* caster = wcaster->ToUnit(); + if (!caster) + return; + + OnSpellHit(caster, spell); + } + + void OnBotDamageDealt(Unit* victim, uint32 damage, CleanDamage const* /*cleanDamage*/, DamageEffectType /*damagetype*/, SpellInfo const* spellInfo) override + { + //black arrow affection -> spawn skeleton (mark) + if (damage && me->IsAlive() && victim->GetTypeId() == TYPEID_UNIT && damage >= victim->GetHealth() && + (victim->GetCreatureType() == CREATURE_TYPE_BEAST || + victim->GetCreatureType() == CREATURE_TYPE_DRAGONKIN || + victim->GetCreatureType() == CREATURE_TYPE_HUMANOID) && + ((spellInfo && spellInfo->GetFirstRankSpell()->Id == BLACK_ARROW_1) || + victim->GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_WARLOCK, 0x0, 0x4, 0x0, me->GetGUID()))) + _blackArrowKillGUID = victim->GetGUID(); + } + + void DamageDealt(Unit* victim, uint32& damage, DamageEffectType damageType) override + { + bot_ai::DamageDealt(victim, damage, damageType); + } + + void DamageTaken(Unit* u, uint32& /*damage*/, DamageEffectType /*damageType*/, SpellInfo const* /*spellInfo*/) override + { + if (!u) + return; + if (!u->IsInCombat() && !me->IsInCombat()) + return; + OnOwnerDamagedBy(u); + } + + void OwnerAttackedBy(Unit* u) override + { + if (!u) + return; + OnOwnerDamagedBy(u); + } + + uint8 GetPetPositionNumber(Creature const* summon) const override + { + uint8 i = 0; + for (Summons::const_iterator citr = _minions.begin(); citr != _minions.end(); ++citr) + { + if ((*citr)->GetGUID() == summon->GetGUID()) + return i; + ++i; + } + return 0; + } + + void SummonBotPet(Unit* from) + { + if (_minions.size() >= MAX_MINIONS) + { + //TC_LOG_ERROR("entities.player", "bot_dranger_ai::SummonBotPet(): max minions"); + Unit* u = nullptr; + //try 1: by minimal level + uint8 minlevel = me->GetLevel(); + for (Summons::const_iterator itr = _minions.begin(); itr != _minions.end(); ++itr) + { + if ((*itr)->GetLevel() < minlevel) + { + minlevel = (*itr)->GetLevel(); + u = *itr; + } + } + //try 2: by minimal duration + if (!u) + { + uint32 minduration = 0; + for (Summons::const_iterator itr = _minions.begin(); itr != _minions.end(); ++itr) + { + if ((*itr)->GetAI()->GetData(BOTPETAI_MISC_DURATION) > minduration) + { + minduration = (*itr)->GetAI()->GetData(BOTPETAI_MISC_DURATION); + u = *itr; + } + } + } + + if (!u) + return; + } + + //addition: change unit's modelid + if (!from->IsPet() && !from->IsVehicle() && + !from->ToCreature()->isWorldBoss() && + !from->ToCreature()->IsDungeonBoss()) + { + from->CastSpell(from, SPELL_BLOODY_EXPLOSION, true); + from->SetDisplayId(MODEL_BLOODY_BONES); + } + + uint32 rank = from->ToCreature()->GetCreatureTemplate()->rank; + uint32 entry = (rank == CREATURE_ELITE_NORMAL) ? BOT_PET_DARK_MINION : BOT_PET_DARK_MINION_ELITE; + + uint8 maxLevel = std::min(from->GetLevel(), me->GetLevel()); + + Position pos = from->GetPosition(); + + Creature* myPet = me->SummonCreature(entry, pos, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 1s); + myPet->SetCreator(master); + myPet->SetOwnerGUID(me->GetGUID()); + myPet->SetFaction(master->GetFaction()); + myPet->SetControlledByPlayer(!IAmFree()); + myPet->SetPvP(me->IsPvP()); + myPet->SetByteValue(UNIT_FIELD_BYTES_2, 1, master->GetByteValue(UNIT_FIELD_BYTES_2, 1)); + myPet->SetUInt32Value(UNIT_CREATED_BY_SPELL, BLACK_ARROW_1); + + //dark minion immunities + myPet->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_SHAPESHIFT, true); + myPet->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_TRANSFORM, true); + myPet->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_FEAR, true); + myPet->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_POLYMORPH, true); + myPet->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_FEAR, true); + myPet->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_HORROR, true); + myPet->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_SLEEP, true); + //heal + myPet->ApplySpellImmune(0, IMMUNITY_EFFECT, SPELL_EFFECT_HEAL, true); + myPet->ApplySpellImmune(0, IMMUNITY_EFFECT, SPELL_EFFECT_HEAL_PCT, true); + myPet->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_PERIODIC_HEAL, true); + + //myPet->CastSpell(myPet, SPELL_BLOODY_EXPLOSION, true); + myPet->CastSpell(myPet, SPELL_SPAWN_ANIM, true); + if (Aura* stun = myPet->AddAura(SUMMONING_DISORIENTATION, myPet)) + { + stun->SetDuration(1500); + stun->SetMaxDuration(1500); + } + + myPet->GetAI()->SetData(BOTPETAI_MISC_MAXLEVEL, maxLevel); + _minions.insert(myPet); + } + + void UnsummonAll(bool savePets = true) override + { + UnsummonCreatures(_minions, savePets); + } + + void SummonedCreatureDies(Creature* /*summon*/, Unit* /*killer*/) override + { + //TC_LOG_ERROR("entities.unit", "SummonedCreatureDies: {}'s {}", me->GetName(), summon->GetName()); + //if (summon == botPet) + // botPet = nullptr; + } + + void SummonedCreatureDespawn(Creature* summon) override + { + //all darkranger bot pets despawn at death or manually (gossip, teleport, etc.) + //TC_LOG_ERROR("entities.unit", "SummonedCreatureDespawn: {}'s {}", me->GetName(), summon->GetName()); + if (_minions.find(summon) != _minions.end()) + _minions.erase(summon); + } + + float GetSpellAttackRange(bool longRange) const override + { + return longRange ? CalcSpellMaxRange(BLACK_ARROW_1) - 5.f : 15.f; + } + + uint32 GetAIMiscValue(uint32 data) const override + { + switch (data) + { + case BOTAI_MISC_PET_TYPE: + return BOT_PET_DARK_MINION; + default: + return 0; + } + } + + void Reset() override + { + UnsummonAll(false); + + //for (uint8 i = 0; i != MAX_SPELL_SCHOOL; ++i) + // me->m_threatModifier[1] = 0.0f; + + _blackArrowKillGUID = ObjectGuid::Empty; + + DefaultInit(); + + //threat mod + if (Aura* threat = me->AddAura(SPELL_THREAT_MOD, me)) + threat->GetEffect(0)->ChangeAmount(-100); + } + + void ReduceCD(uint32 /*diff*/) override + { + //if (trapTimer > diff) trapTimer -= diff; + } + + void InitPowers() override + { + me->SetPowerType(POWER_MANA); + } + + void InitSpells() override + { + //uint8 lvl = me->GetLevel(); + InitSpellMap(AUTO_SHOT_1); + InitSpellMap(BLACK_ARROW_1); + InitSpellMap(DRAIN_LIFE_1); + InitSpellMap(SILENCE_1); + } + + void ApplyClassPassives() const override + { + } + + std::vector const* GetDamagingSpellsList() const override + { + return &Darkranger_spells_damage; + } + std::vector const* GetCCSpellsList() const override + { + return &Darkranger_spells_cc; + } + //std::vector const* GetHealingSpellsList() const override + //{ + // return &Darkranger_spells_heal; + //} + //std::vector const* GetSupportSpellsList() const override + //{ + // return &Darkranger_spells_support; + //} + private: + ObjectGuid _blackArrowKillGUID; + typedef std::set Summons; + Summons _minions; + }; +}; + +void AddSC_dark_ranger_bot() +{ + new dark_ranger_bot(); +} diff --git a/src/server/game/AI/NpcBots/bot_death_knight_ai.cpp b/src/server/game/AI/NpcBots/bot_death_knight_ai.cpp new file mode 100644 index 000000000..63e06f64e --- /dev/null +++ b/src/server/game/AI/NpcBots/bot_death_knight_ai.cpp @@ -0,0 +1,2118 @@ +#include "bot_ai.h" +#include "botmgr.h" +#include "bottext.h" +#include "bottraits.h" +#include "DBCStores.h" +#include "GameEventMgr.h" +#include "Group.h" +#include "Log.h" +#include "Map.h" +#include "MotionMaster.h" +#include "ObjectAccessor.h" +#include "Player.h" +#include "ScriptMgr.h" +#include "Spell.h" +#include "SpellAuraEffects.h" +#include "SpellMgr.h" +#include "TemporarySummon.h" +#include "World.h" +/* +Death Knight NpcBot by Trickerer onlysuffering@gmail.com +Complete - around 85% +Note: Rune system adapted from TC +TODO: pet related +Notes: raise dead / army of the dead not working off the bat, summon garg crash, dancing rune weapon crash, need ai workarounds +*/ + +enum DeathKnightBaseSpells +{ + BLOOD_STRIKE_1 = 45902, + ICY_TOUCH_1 = 45477, + PLAGUE_STRIKE_1 = 45462, + DEATH_STRIKE_1 = 49998, + OBLITERATE_1 = 49020, + RUNE_STRIKE_1 = 56815, + HEART_STRIKE_1 = 55050, + FROST_STRIKE_1 = 49143, + SCOURGE_STRIKE_1 = 55090, + + BLOOD_BOIL_1 = 48721, + DEATH_AND_DECAY_1 = 43265, + HOWLING_BLAST_1 = 49184, + + DEATH_COIL_1 = 47541, + DEATH_GRIP_1 = 49576, + PESTILENCE_1 = 50842, + MIND_FREEZE_1 = 47528, + STRANGULATE_1 = 47476, + CHAINS_OF_ICE_1 = 45524, + ICEBOUND_FORTITUDE_1 = 48792, + DARK_COMMAND_1 = 56222, + ANTI_MAGIC_SHELL_1 = 48707, + ARMY_OF_THE_DEAD_1 = 42650, + + PATH_OF_FROST_1 = 3714, + HORN_OF_WINTER_1 = 57330, + EMPOWER_RUNE_WEAPON_1 = 47568, + BLOOD_TAP_1 = 45529, + + RUNE_TAP_1 = 48982, + LICHBORNE_1 = 49039, + //CE + MARK_OF_BLOOD_1 = 49005, + DEATHCHILL_1 = 49796, + HYSTERIA_1 = 49016, + HUNGERING_COLD_1 = 49203, + ANTI_MAGIC_ZONE_1 = 48707, + VAMPIRIC_BLOOD_1 = 55233, + UNBREAKABLE_ARMOR_1 = 51271, + BONE_SHIELD_1 = 49222, + + BLOOD_PRESENCE_1 = 48266, + FROST_PRESENCE_1 = 48263, + UNHOLY_PRESENCE_1 = 48265//unused +}; +enum DeathKnightPassives +{ +//Talents + BUTCHERY = 49483,//rank 2 + TOUGHNESS = 49789,//rank 5 + BLADED_ARMOR = 49393,//rank 5 + SCENT_OF_BLOOD = 49509,//rank 3 + TWO_HANDED_WEAPON_SPECIALIZATION = 55108,//rank 2 + ICY_TALONS = 50887,//rank 5 + ANNIHILATION = 51473,//rank 3 + NECROSIS1 = 51459, + NECROSIS2 = 51462, + NECROSIS3 = 51463, + NECROSIS4 = 51464, + NECROSIS5 = 51465, + SPELL_DEFLECTION = 49497,//rank 3 + VENDETTA = 55136,//rank 3 + KILLING_MACHINE = 51130,//rank 5 + CHILL_OF_THE_GRAVE = 50115,//rank 2 + ON_A_PALE_HORSE_A = 51970,//rank 2 + ON_A_PALE_HORSE_B = 51986,//rank 2 + BLOOD_CAKED_BLADE1 = 49219, + BLOOD_CAKED_BLADE2 = 49627, + BLOOD_CAKED_BLADE3 = 49628, + FRIGID_DREADPLATE = 51109,//rank 3 + UNHOLY_BLIGHT = 49194, + DIRGE = 51206,//rank 2 + BLOODY_VENGEANCE1 = 48988, + BLOODY_VENGEANCE2 = 49503, + BLOODY_VENGEANCE3 = 49504, + ABOMINATIONS_MIGHT = 53138,//rank 2 + IMPROVED_ICY_TALONS = 55610, + DESECRATION = 55667,//rank 2 + //BLOODWORMS = 49543,//rank 3 + IMPROVED_BLOOD_PRESENCE = 50371,//rank 2 + DESOLATION = 66817,//rank 5 + IMPROVED_UNHOLY_PRESENCE = 50392,//rank 2 + THREAT_OF_THASSARIAN = 66192,//rank 3 + CRYPT_FEVER = 49632,//rank 3 + WILL_OF_THE_NECROPOLIS = 52286,//rank 3 + ACCLIMATION = 50152,//rank 3 + WANDERING_PLAGUE = 49655,//rank 3 + EBON_PLAGUEBRINGER = 51161,//rank 3 + +//Other + //GLYPH_DANCING_RUNE_WEAPON = 63330, + GLYPH_DISEASE = 63334, + GLYPH_CHAINS_OF_ICE = 58620, + GLYPH_UNHOLY_BLIGHT = 63332, + CHAINS_OF_ICE_FROST_RUNE_REFRESH = 62459,//5 runic power gain + + ITEM_DEATH_KNIGHT_T8_MELEE_4P = 64736, + ITEM_DEATH_KNIGHT_T9_MELEE_4P = 67118, + +//Special + FROST_FEVER = 59921, + BLOOD_PLAGUE = 59879, + RUNE_STRIKE_PASSIVE = 56816,//rune strike activation req aura + RUNIC_POWER_MASTERY = 49455//rank 1 +}; +enum DeathKnightSpecial +{ + FROST_FEVER_AURA = 55095, + BLOOD_PLAGUE_AURA = 55078, + CRYPT_FEVER_AURA = 50510,//rank 3 + EBON_PLAGUE_AURA = 51735,//rank 3 + + BLADE_BARRIER_BUFF = 64859,//rank 5 + KILLING_MACHINE_BUFF = 51124, + RIME_BUFF = 59052,//Freezing Fog + ITEM_DEATH_KNIGHT_T10_TANK_4P_BUFF = 70654,//Blood Armor + ITEM_DEATH_KNIGHT_T10_MELEE_4P_BUFF = 70657,//Advantage + + CHILBLAINS_DEBUFF = 50436,//Icy Clutch rank 3 + + BLOOD_PRESENCE_HEAL_EFFECT = 50475, + //UNHOLY_BLIGHT_AURA = 50536, + + DEATH_STRIKE_HEAL = 45470, + DEATH_COIL_HEAL = 47633, + DEATH_COIL_DAMAGE = 47632, + + GLYPH_HEART_STRIKE_DEBUFF = 58617, //50% move slow + GLYPH_RUNE_TAP_HEAL = 59754, + GLYPH_SCOURGE_STRIKE_EFFECT = 69961 +}; + +static const uint32 Deathknight_spells_damage_arr[] = +{ BLOOD_BOIL_1, BLOOD_STRIKE_1, DEATH_AND_DECAY_1, DEATH_COIL_1,DEATH_STRIKE_1, +FROST_STRIKE_1, HEART_STRIKE_1, HOWLING_BLAST_1, HUNGERING_COLD_1, ICY_TOUCH_1, +OBLITERATE_1, PESTILENCE_1, PLAGUE_STRIKE_1, RUNE_STRIKE_1, SCOURGE_STRIKE_1 }; + +static const uint32 Deathknight_spells_cc_arr[] = +{ DEATH_GRIP_1, CHAINS_OF_ICE_1, MIND_FREEZE_1, STRANGULATE_1 }; + +static const uint32 Deathknight_spells_support_arr[] = +{ ANTI_MAGIC_SHELL_1, ANTI_MAGIC_ZONE_1, ARMY_OF_THE_DEAD_1, BLOOD_TAP_1, BONE_SHIELD_1,DARK_COMMAND_1, DEATHCHILL_1, +DEATH_COIL_1, EMPOWER_RUNE_WEAPON_1, HORN_OF_WINTER_1, HUNGERING_COLD_1,HYSTERIA_1, ICEBOUND_FORTITUDE_1, +LICHBORNE_1, MARK_OF_BLOOD_1, PATH_OF_FROST_1, PESTILENCE_1, RUNE_TAP_1,UNBREAKABLE_ARMOR_1, VAMPIRIC_BLOOD_1 }; + +static const std::vector Deathknight_spells_damage(FROM_ARRAY(Deathknight_spells_damage_arr)); +static const std::vector Deathknight_spells_cc(FROM_ARRAY(Deathknight_spells_cc_arr)); +static const std::vector Deathknight_spells_support(FROM_ARRAY(Deathknight_spells_support_arr)); + +const RuneType runeSlotTypes[MAX_RUNES] = +{ + RUNE_BLOOD, + RUNE_BLOOD, + RUNE_UNHOLY, + RUNE_UNHOLY, + RUNE_FROST, + RUNE_FROST +}; +struct BotRuneInfo +{ + uint8 BaseRune; + uint8 CurrentRune; + int32 Cooldown; +}; +class death_knight_bot : public CreatureScript +{ +public: + death_knight_bot() : CreatureScript("death_knight_bot") { } + + CreatureAI* GetAI(Creature* creature) const override + { + return new death_knight_botAI(creature); + } + + struct death_knight_botAI : public bot_ai + { +/* + bool OnGossipHello(Player* player) override + { + return OnGossipHello(player, 0); + } + + bool OnGossipSelect(Player* player, uint32 sender, uint32 action) override + { + return OnGossipSelect(player, me, sender, action); + } + + bool OnGossipSelectCode(Player* player, uint32 sender, uint32 action, char const* code) override + { + return OnGossipSelectCode(player, me, sender, action, code); + } +*/ + death_knight_botAI(Creature* creature) : bot_ai(creature) + { + _botclass = BOT_CLASS_DEATH_KNIGHT; + + InitUnitFlags(); + } + + bool doCast(Unit* victim, uint32 spellId) + { + if (CheckBotCast(victim, spellId) != SPELL_CAST_OK) + return false; + + if (!HaveRunes(spellId)) + return false; + + return bot_ai::doCast(victim, spellId); + } + + void SpendRunes(SpellInfo const* spellInfo, bool didHit) override + { + SpellRuneCostEntry const* src = sSpellRuneCostStore.LookupEntry(spellInfo->RuneCostID); + if (!src || (src->NoRuneCost() && src->NoRunicPowerGain())) + return; + + //Freezing Fog + if (rimeProcTimer > GetLastDiff() && spellInfo->Id == HOWLING_BLAST_1) + { + for (uint8 i = 0; i != RUNE_DEATH; ++i) + runeCost[i] = 0; + } + else + { + for (uint8 i = 0; i != RUNE_DEATH; ++i) + runeCost[i] = src->RuneCost[i]; + } + + runeCost[RUNE_DEATH] = 0; + + //debug 1 + //std::ostringstream str; + //str << "Casted " << spellInfo->SpellName[0] << " cost" + // << " " << runeCost[0] << " " << runeCost[1] << " " << runeCost[2] << " " << runeCost[3]; + + for (uint8 i = 0; i != MAX_RUNES; ++i) + { + uint8 rune = _runes[i].CurrentRune; + if (runeCost[rune] > 0 && _runes[i].Cooldown <= 0) + if (SpendRune(rune, didHit)) + runeCost[rune]--; + } + + runeCost[RUNE_DEATH] = runeCost[RUNE_BLOOD] + runeCost[RUNE_UNHOLY] + runeCost[RUNE_FROST]; + + if (runeCost[RUNE_DEATH] > 0) + { + for (uint8 i = 0; i != MAX_RUNES && runeCost[RUNE_DEATH] > 0; ++i) + { + if (_runes[i].CurrentRune == RUNE_DEATH && _runes[i].Cooldown <= 0) + if (SpendRune(RUNE_DEATH, didHit)) + runeCost[RUNE_DEATH]--; + } + } + + //debug 2 + //uint32 runesLeft[NUM_RUNE_TYPES] = { 0,0,0,0 }; + //for (uint8 i = 0; i != NUM_RUNE_TYPES; ++i) + // for (uint8 j = 0; j != MAX_RUNES; ++j) + // if (_runes[j].CurrentRune == i && _runes[j].Cooldown <= 0) + // runesLeft[i]++; + //str << " left" << " " << runesLeft[0] << " " << runesLeft[1] << " " << runesLeft[2] << " " << runesLeft[3]; + //BotWhisper(str.str().c_str()); + + me->ModifyPower(POWER_RUNIC_POWER, int32(src->RunicPower * runicpowerIncomeMult)); + + //BladeBarrier + if (/*src->RuneCost[RUNE_BLOOD] > 0 && */GetCooledRunesCount(RUNE_BLOOD) > 1) + me->CastSpell(me, BLADE_BARRIER_BUFF, true); + + //Item - Death Knight T10 Melee 4P Bonus + if (me->GetLevel() >= 80 && GetCooledRunesCount() == MAX_RUNES) + me->CastSpell(me, ITEM_DEATH_KNIGHT_T10_MELEE_4P_BUFF, true); + } + + void getpower() + { + runicpower = me->GetPower(POWER_RUNIC_POWER); + if (me->FindCurrentSpellBySpellId(RUNE_STRIKE_1)) + runicpower = std::max(runicpower - 200, 0); + } + + int32 rcost(uint32 spellId) const + { + if (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId)) + return spellInfo->CalcPowerCost(me, spellInfo->GetSchoolMask()); + return 0; + } + + uint8 GetBotStance() const override { return _presence; } + + void StartAttack(Unit* u, bool force = false) + { + if (!bot_ai::StartAttack(u, force)) + return; + GetInPosition(force, u); + } + + void JustEnteredCombat(Unit* u) override { bot_ai::JustEnteredCombat(u); } + void EnterEvadeMode(EvadeReason why = EVADE_REASON_OTHER) override { bot_ai::EnterEvadeMode(why); } + void MoveInLineOfSight(Unit* u) override { bot_ai::MoveInLineOfSight(u); } + void JustDied(Unit* u) override { UnsummonAll(false); bot_ai::JustDied(u); } + void KilledUnit(Unit* u) override { bot_ai::KilledUnit(u); } + + void DoNonCombatActions(uint32 diff) + { + if (GC_Timer > diff || Rand() > 20 || IsCasting() || Feasting()) + return; + + //PATH OF FROST + if (GetSpell(PATH_OF_FROST_1) && HaveRunes(PATH_OF_FROST_1)/* && !me->IsMounted()*/) //works while mounted + { + if ((me->HasUnitMovementFlag(MOVEMENTFLAG_SWIMMING) && !me->HasAuraType(SPELL_AURA_WATER_WALK)) || + (master->HasUnitMovementFlag(MOVEMENTFLAG_SWIMMING) && !master->HasAuraType(SPELL_AURA_WATER_WALK))) + { + if (doCast(me, GetSpell(PATH_OF_FROST_1))) + return; + } + } + } + + void CheckHysteria(uint32 diff) + { + if (!IsSpellReady(HYSTERIA_1, diff) || Rand() > 35) + return; + + Unit* target = nullptr; + + if (master->GetVictim() && master->IsInCombat() && IsMeleeClass(master->GetClass()) && + GetHealthPCT(master) > 60 && me->GetDistance(master) < 30 && + master->getAttackers().empty() && !CCed(master, true) && + !master->GetAuraEffect(SPELL_AURA_MOD_DAMAGE_PERCENT_DONE, SPELLFAMILY_DEATHKNIGHT, 0x20000000, 0x0, 0x0)) + target = master; + + if (IAmFree()) + return; + + if (!target) + { + Group const* gr = master->GetGroup(); + if (gr) + { + for (GroupReference const* itr = gr->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* player = itr->GetSource(); + if (!player || player == master || player->IsBeingTeleported() || + me->GetMap() != player->FindMap()) + continue; + + if (IsMeleeClass(player->GetClass()) && player->GetVictim() && GetHealthPCT(player) > 60 && + me->GetDistance(player) < 30 && player->getAttackers().empty() && !CCed(player, true) && + !player->GetAuraEffect(SPELL_AURA_MOD_DAMAGE_PERCENT_DONE, SPELLFAMILY_DEATHKNIGHT, 0x20000000, 0x0, 0x0)) + { + target = player; + break; + } + + if (!player->HaveBot()) + continue; + + BotMap const* map = player->GetBotMgr()->GetBotMap(); + for (BotMap::const_iterator it = map->begin(); it != map->end(); ++it) + { + Creature* bot = it->second; + if (IsMeleeClass(bot->GetBotClass()) && bot->GetVictim() && + bot->GetBotAI()->HasRole(BOT_ROLE_DPS) && !bot->GetBotAI()->HasRole(BOT_ROLE_RANGED) && + GetHealthPCT(bot) > 60 && me->GetDistance(bot) < 30 && !CCed(bot, true) && + !bot->GetAuraEffect(SPELL_AURA_MOD_DAMAGE_PERCENT_DONE, SPELLFAMILY_DEATHKNIGHT, 0x20000000, 0x0, 0x0)) + { + target = bot; + break; + } + } + + if (target) + break; + } + } + } + + if (!target && me->GetVictim() && HasRole(BOT_ROLE_DPS) && !HasRole(BOT_ROLE_RANGED) && + GetHealthPCT(me) > 60 && me->getAttackers().empty() && !CCed(me, true) && + !me->GetAuraEffect(SPELL_AURA_MOD_DAMAGE_PERCENT_DONE, SPELLFAMILY_DEATHKNIGHT, 0x20000000, 0x0, 0x0)) + target = me; + + if (target && doCast(target, GetSpell(HYSTERIA_1))) + { + if (target->GetTypeId() == TYPEID_PLAYER) + ReportSpellCast(HYSTERIA_1, LocalizedNpcText(target->ToPlayer(), BOT_TEXT__ON_YOU), target->ToPlayer()); + //if (target != master) + //{ + // std::string hystmsg = "Hysteria on " + (target == me ? "myself" : target->GetName()) + "!"; + // BotWhisper(hystmsg.c_str()); + //} + + return; + } + + SetSpellCooldown(HYSTERIA_1, 1000); //fail + } + + void CheckAntiMagic(uint32 diff) + { + if (!me->GetVictim() || !me->GetVictim()->HasUnitState(UNIT_STATE_CASTING) || Rand() > 40) + return; + + Spell const* spell = me->GetVictim()->GetCurrentSpell(CURRENT_GENERIC_SPELL); + if (!spell || spell->GetTimer() >= 1000 || !spell->GetSpellInfo()->HasEffect(SPELL_EFFECT_SCHOOL_DAMAGE)) + return; + + Unit const* u = spell->m_targets.GetUnitTarget(); + if (!u || (IAmFree() ? (u != me) : !(master->GetGroup() && master->GetGroup()->IsMember(spell->m_targets.GetObjectTargetGUID())))) + return; + + if (u == me && IsSpellReady(ANTI_MAGIC_SHELL_1, diff, false) && runicpower >= rcost(ANTI_MAGIC_SHELL_1)) + { + if (doCast(me, GetSpell(ANTI_MAGIC_SHELL_1))) + { + getpower(); + return; + } + } + + if (Rand() > 20) + return; + + u = ObjectAccessor::GetUnit(*me, spell->m_targets.GetObjectTargetGUID()); + if (u && !u->isMoving() && me->GetDistance(u) < 4 && IsSpellReady(ANTI_MAGIC_ZONE_1, diff, false) && + HaveRunes(ANTI_MAGIC_ZONE_1) && + !u->GetAuraEffect(SPELL_AURA_SCHOOL_ABSORB, SPELLFAMILY_DEATHKNIGHT, 0x20000, 0x0, 0x0)) + { + if (doCast(me, GetSpell(ANTI_MAGIC_ZONE_1))) + return; + } + } + + void CheckPresence(uint32 diff) + { + if (presencetimer > diff || IAmFree() || IsCasting() || Rand() > 30) + return; + + uint8 newpresence = IsTank() ? DEATH_KNIGHT_FROST_PRESENCE : DEATH_KNIGHT_BLOOD_PRESENCE; + if (_presence == newpresence) + { + presencetimer = 5000; + return; + } + + if (newpresence == DEATH_KNIGHT_FROST_PRESENCE && HaveRunes(FROST_PRESENCE_1)) + { + if (doCast(me, FROST_PRESENCE_1)) + return; + } + else if (newpresence == DEATH_KNIGHT_BLOOD_PRESENCE && HaveRunes(BLOOD_PRESENCE_1)) + { + if (doCast(me, BLOOD_PRESENCE_1)) + return; + } + + presencetimer = 1000; //fail + } + + void BreakCC(uint32 diff) override + { + if (IsSpellReady(LICHBORNE_1, diff, false) && Rand() < 45 && + me->HasAuraWithMechanic((1< 35) + return; + + if (IsSpellReady(STRANGULATE_1, diff) && me->IsInCombat() && HaveRunes(STRANGULATE_1)) + { + Unit* u = FindCastingTarget(CalcSpellMaxRange(STRANGULATE_1), 0, STRANGULATE_1); + if (u && doCast(u, GetSpell(STRANGULATE_1))) + return; + } + + Unit* target = me->GetVictim(); + if (IsSpellReady(MIND_FREEZE_1, diff, false) && target && me->GetDistance(target) < 5 && + runicpower >= rcost(MIND_FREEZE_1) && target->IsNonMeleeSpellCast(false,false,true)) + { + if (doCast(me->GetVictim(), GetSpell(MIND_FREEZE_1))) + getpower(); + } + } + + void UpdateAI(uint32 diff) override + { + if (me->IsAlive()) + { + if (runicpowertimer <= diff) + { + if (!me->IsInCombat()) + { + if (me->GetPower(POWER_RUNIC_POWER) > uint32(30.f * runicpowerLossMult)) + me->SetPower(POWER_RUNIC_POWER, me->GetPower(POWER_RUNIC_POWER) - uint32(30.f * runicpowerLossMult)); //-3 runic power every 2 sec + else if (me->GetPower(POWER_RUNIC_POWER) > 0) + me->SetPower(POWER_RUNIC_POWER, 0); + } + runicpowertimer = 2000; + } + if (runicpowertimer2 <= diff) + { + if (me->IsInCombat()) + { + if (me->GetPower(POWER_RUNIC_POWER) < me->GetMaxPower(POWER_RUNIC_POWER)) + me->SetPower(POWER_RUNIC_POWER, me->GetPower(POWER_RUNIC_POWER) + int32(20.f * runicpowerIncomeMult)); //+2 runic power every 5 sec + else if (me->GetPower(POWER_RUNIC_POWER) < me->GetMaxPower(POWER_RUNIC_POWER)) + me->SetPower(POWER_RUNIC_POWER, me->GetMaxPower(POWER_RUNIC_POWER)); + } + runicpowertimer2 = 5000; + } + getpower(); + } + + if (!GlobalUpdate(diff)) + return; + + DoVehicleActions(diff); + if (!CanBotAttackOnVehicle()) + return; + + //pet is killed or unreachable + if (GC_Timer <= diff && petSummonTimer <= diff && !me->IsInCombat() && !me->IsMounted() && !me->GetVictim() && !IsCasting() && Rand() < 25 && + (!botPet || me->GetDistance2d(botPet) > sWorld->GetMaxVisibleDistanceOnContinents())) + SummonBotPet(); + + if (IsPotionReady()) + { + if (GetHealthPCT(me) < 40) + DrinkPotion(false); + } + + CheckRacials(diff); + CheckPresence(diff); + + if (!me->IsInCombat()) + DoNonCombatActions(diff); + + if (IsCasting()) + return; + + Counter(diff); + + //HORN OF WINTER + if (IsSpellReady(HORN_OF_WINTER_1, diff) && !IAmFree() && Rand() < 25 && + ((me->IsInCombat() && runicpower <= 900) || + !me->GetAuraEffect(SPELL_AURA_MOD_STAT, SPELLFAMILY_DEATHKNIGHT, 0x0, 0x40000000, 0x0))) + { + if (doCast(me, GetSpell(HORN_OF_WINTER_1))) + return; + } + //BONE SHIELD + if (IsSpellReady(BONE_SHIELD_1, diff) && HaveRunes(BONE_SHIELD_1) && Rand() < 25 + 65 * IsTank()) + { + Aura* bone = me->GetAura(GetSpell(BONE_SHIELD_1)); + if (!bone || bone->GetCharges() < 2 || bone->GetDuration() < 10000) + { + if (doCast(me, GetSpell(BONE_SHIELD_1))) + return; + } + } + + CheckHysteria(diff); + CheckAntiMagic(diff); + + if (ProcessImmediateNonAttackTarget()) + return; + + if (!CheckAttackTarget()) + return; + + CheckUsableItems(diff); + + DoNormalAttack(diff); + } + + void DoNormalAttack(uint32 diff) + { + Unit* mytar = opponent ? opponent : disttarget ? disttarget : nullptr; + if (!mytar) + return; + + StartAttack(mytar, IsMelee()); + + CheckAttackState(); + if (!me->IsAlive() || !mytar->IsAlive()) + return; + + //BLOOD TAP + if (IsSpellReady(BLOOD_TAP_1, diff, false) && Rand() < 65) + { + if (doCast(me, GetSpell(BLOOD_TAP_1))) + getpower(); + } + + //ICEBOUND FORTITUDE + if (IsSpellReady(ICEBOUND_FORTITUDE_1, diff, false) && Rand() < 50 && runicpower >= rcost(ICEBOUND_FORTITUDE_1) && + GetHealthPCT(me) < std::min(60, 35 + uint8(me->getAttackers().size()) * 5) + 20 * IsTank()) + { + if (doCast(me, GetSpell(ICEBOUND_FORTITUDE_1))) + { + if (!IAmFree()) + ReportSpellCast(ICEBOUND_FORTITUDE_1, LocalizedNpcText(master, BOT_TEXT__USED), master); + + getpower(); + } + } + + //SELFHEAL + //VAMPIRIC BLOOD + if (IsSpellReady(VAMPIRIC_BLOOD_1, diff, false) && GetHealthPCT(me) < 50 && (IsTank() || Rand() < 75)) + { + if (HaveRunes(VAMPIRIC_BLOOD_1) || (IsSpellReady(EMPOWER_RUNE_WEAPON_1, diff, false) && doCast(me, GetSpell(EMPOWER_RUNE_WEAPON_1)))) + { + if (doCast(me, GetSpell(VAMPIRIC_BLOOD_1))) + { + if (!IAmFree()) + ReportSpellCast(VAMPIRIC_BLOOD_1, LocalizedNpcText(master, BOT_TEXT__USED), master); + + return; + } + } + } + //RUNE TAP + if (IsSpellReady(RUNE_TAP_1, diff, false) && GetHealthPCT(me) < 65 && Rand() < 50 && HaveRunes(RUNE_TAP_1)) + { + if (doCast(me, GetSpell(RUNE_TAP_1))) + return; + } + //LICHBORNE + DEATH COIL + if ((me->GetCreatureType() == CREATURE_TYPE_UNDEAD || IsSpellReady(LICHBORNE_1, diff, false)) && + IsSpellReady(DEATH_COIL_1, diff) && Rand() < 45 && GetHealthPCT(me) < 80 && runicpower >= rcost(DEATH_COIL_1)) + { + if (me->GetCreatureType() == CREATURE_TYPE_UNDEAD || doCast(me, GetSpell(LICHBORNE_1))) + { + if (doCast(me, GetSpell(DEATH_COIL_1))) + return; + } + } + //END SELFHEAL + + //EMPOWER RUNE WEAPON + if (IsSpellReady(EMPOWER_RUNE_WEAPON_1, diff, false) && Rand() < 50 && + GetCooledRunesCount() >= 5 && GetTotalRunesCooldown() >= (RUNE_BASE_COOLDOWN * 5) / 2) + { + if (doCast(me, GetSpell(EMPOWER_RUNE_WEAPON_1))) + { + //BotWhisper("ERW used!"); + } + } + + float dist = me->GetDistance(mytar); + Unit const* u = mytar->GetVictim(); + + //MARK OF BLOOD + if (IsSpellReady(MARK_OF_BLOOD_1, diff) && u && Rand() < 55 && dist < 30 && HaveRunes(MARK_OF_BLOOD_1) && + IsInBotParty(u) && GetHealthPCT(u) < 75 && u->GetDistance(mytar) < 10 && + mytar->GetHealth() > me->GetMaxHealth() / 4 * (1 + mytar->getAttackers().size()) && + (u == me || IsTank(u) || u->GetTypeId() == TYPEID_PLAYER) && + !mytar->GetDummyAuraEffect(SPELLFAMILY_DEATHKNIGHT, 2285, 0)) + { + if (doCast(mytar, GetSpell(MARK_OF_BLOOD_1))) + return; + } + + //NON-DISEASE SECTION + + //PLACEHOLDER: ARMY OF THE DEAD + + //RANGED SECTION + + //DARK COMMAND + if (IsSpellReady(DARK_COMMAND_1, diff, false) && u && u != me && dist < 30 && + mytar->GetTypeId() == TYPEID_UNIT && !mytar->IsControlledByPlayer() && Rand() < 50 && + !CCed(mytar) && !mytar->HasAuraType(SPELL_AURA_MOD_TAUNT) && + (!IsTank(u) || (IsTank() && GetHealthPCT(me) > 67 && + (GetHealthPCT(u) < 30 || (IsOffTank() && !IsOffTank(u) && IsPointedOffTankingTarget(mytar)) || + (!IsOffTank() && IsOffTank(u) && IsPointedTankingTarget(mytar))))) && + ((!IsTankingClass(u->GetClass()) && GetHealthPCT(u) < 80) || IsTank()) && + IsInBotParty(u)) + { + if (doCast(mytar, GetSpell(DARK_COMMAND_1))) + return; + } + //DARK COMMAND 2 (distant) + if (IsSpellReady(DARK_COMMAND_1, diff, false) && !IAmFree() && u == me && Rand() < 30 && IsTank() && + (IsOffTank() || master->GetBotMgr()->GetNpcBotsCountByRole(BOT_ROLE_TANK_OFF) == 0) && + !(me->GetLevel() >= 40 && mytar->GetTypeId() == TYPEID_UNIT && + (mytar->ToCreature()->IsDungeonBoss() || mytar->ToCreature()->isWorldBoss()))) + { + if (Unit* tUnit = FindDistantTauntTarget()) + { + if (doCast(tUnit, GetSpell(DARK_COMMAND_1))) + return; + } + } + + ////DEATH GRIP - DISABLED + //if (DEATH_GRIP && DeathGrip_cd <= diff && dist < 30 && + // (tank == me && mytar->GetVictim() != me) || + // (mytar->GetVictim() == me && mytar->ToPlayer() && mytar->IsNonMeleeSpellCast(false)) && + // Rand() < 75) + //{ + // if (doCast(mytar, DEATH_GRIP)) + // { + // DeathGrip_cd = 25000; + // return; + // } + + // DeathGrip_cd = 1000; //fail + //} + + //UNBREAKABLE ARMOR + if (IsSpellReady(UNBREAKABLE_ARMOR_1, diff, false) && dist < 10 && HaveRunes(UNBREAKABLE_ARMOR_1) && + (IsTank() || !me->getAttackers().empty() || mytar->GetMaxHealth() > me->GetMaxHealth() || Rand() < 35)) + { + if (doCast(me, GetSpell(UNBREAKABLE_ARMOR_1))) + {} + } + + if (!HasRole(BOT_ROLE_DPS)) + return; + + //CHAINS OF ICE + if (IsSpellReady(CHAINS_OF_ICE_1, diff) && Rand() < 65 && dist < CalcSpellMaxRange(CHAINS_OF_ICE_1) && mytar->isMoving() && + !(mytar->GetTypeId() == TYPEID_UNIT && (mytar->ToCreature()->GetCreatureTemplate()->MechanicImmuneMask & (1<<(MECHANIC_SNARE-1)))) && + HaveRunes(CHAINS_OF_ICE_1) && !CCed(mytar, true) && (!u || (!IsTank(u) && IsInBotParty(u))) && + !mytar->HasAuraWithMechanic(1<= rcost(HUNGERING_COLD_1)) + { + std::list targets; + GetNearbyTargetsList(targets, 9.f, 0); + if (targets.size() >= 3) + { + if (doCast(me, GetSpell(HUNGERING_COLD_1))) + return; + } + } + + //Diseases in general + bool noDiseases = (mytar->GetTypeId() == TYPEID_UNIT && (mytar->ToCreature()->GetCreatureTemplate()->MechanicImmuneMask & (1<<(MECHANIC_INFECTED-1)))); + AuraEffect const* blop = noDiseases ? nullptr : mytar->GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_DEATHKNIGHT, 0x0, 0x2000000, 0x0, me->GetGUID()); + AuraEffect const* frof = noDiseases ? nullptr : mytar->GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_DEATHKNIGHT, 0x0, 0x4000000, 0x0, me->GetGUID()); + + auto [can_do_frost, can_do_shadow, can_do_physical] = CanAffectVictimBools(mytar, SPELL_SCHOOL_FROST, SPELL_SCHOOL_SHADOW, SPELL_SCHOOL_NORMAL); + + //DISEASE SECTION + + //PESTILENCE + if (IsSpellReady(PESTILENCE_1, diff) && can_do_shadow && blop && frof && dist < 5 && HaveRunes(PESTILENCE_1)) + { + if (blop->GetBase()->GetDuration() < 5000 || frof->GetBase()->GetDuration() < 5000) + { + if (doCast(mytar, GetSpell(PESTILENCE_1))) + return; + } + + if (Rand() < 35 + 65 * me->GetMap()->IsDungeon()) + { + std::list targets; + GetNearbyTargetsList(targets, 13.f, 0, mytar); + uint8 count = 0; + for (std::list::const_iterator itr = targets.begin(); itr != targets.end(); ++itr) + { + //check existing blop and frof + if (!(*itr)->GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_DEATHKNIGHT, 0x0, 0x2000000, 0x0, me->GetGUID()) || + !(*itr)->GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_DEATHKNIGHT, 0x0, 0x4000000, 0x0, me->GetGUID())) + if (++count > 1) + break; + } + if (count > 1 && doCast(mytar, GetSpell(PESTILENCE_1))) + return; + } + } + //ICY TOUCH + if (IsSpellReady(ICY_TOUCH_1, diff) && can_do_frost && !noDiseases && (!frof || frof->GetBase()->GetMaxDuration() < 3000) && + dist < CalcSpellMaxRange(ICY_TOUCH_1) && HaveRunes(ICY_TOUCH_1)) + { + if (doCast(mytar, GetSpell(ICY_TOUCH_1))) + return; + } + //HOWLING BLAST + if (IsSpellReady(HOWLING_BLAST_1, diff) && can_do_frost && (rimeProcTimer > diff || Rand() < 70) && + (!u || mytar->IsControlledByPlayer() || rimeProcTimer > diff || + (u && u != me && IsTank(u) && u->getAttackers().size() > 2)) && + dist < CalcSpellMaxRange(HOWLING_BLAST_1) && HaveRunes(HOWLING_BLAST_1)) + { + if (u && u->getAttackers().size() > 4 && + IsSpellReady(DEATHCHILL_1, diff, false) && doCast(me, GetSpell(DEATHCHILL_1))) + {/* BotWhisper("Deathchill used!"); */} + if (doCast(mytar, GetSpell(HOWLING_BLAST_1))) + return; + } + + //END DISEASE SECTION + + //MELEE SECTION + + //FROST STRIKE + if (IsSpellReady(FROST_STRIKE_1, diff) && can_do_frost && Rand() < 90 && dist < 5 && + runicpower >= rcost(FROST_STRIKE_1) && + (runicpower >= 1000 || !GetSpell(OBLITERATE_1) || !HaveRunes(OBLITERATE_1))) + { + if (doCast(mytar, GetSpell(FROST_STRIKE_1))) + return; + } + //BLOOD BOIL + if (IsSpellReady(BLOOD_BOIL_1, diff) && can_do_shadow && IsTank() && Rand() < 25 && HaveRunes(BLOOD_BOIL_1)) + { + std::list targets; + GetNearbyTargetsList(targets, 9.f, 1); + if (targets.size() >= 4) + if (doCast(me, GetSpell(BLOOD_BOIL_1))) + return; + } + //DEATH AND DECAY + if (IsSpellReady(DEATH_AND_DECAY_1, diff) && can_do_shadow && Rand() < (10 + 30 * IsTank()) && dist < 8 && + HaveRunes(DEATH_AND_DECAY_1)) + { + if (Unit* target = FindAOETarget(10)) + { + if (doCast(target, GetSpell(DEATH_AND_DECAY_1))) + return; + } + } + //DEATH COIL + if (IsSpellReady(DEATH_COIL_1, diff) && can_do_shadow && Rand() < 50 && (dist > 6 || !GetSpell(FROST_STRIKE_1)) && + (dist < (IAmFree() ? 30 : 15)) && runicpower > 2 * rcost(DEATH_COIL_1)) + { + if (doCast(mytar, GetSpell(DEATH_COIL_1))) + return; + } + + MoveBehind(mytar); + + if (!can_do_physical || dist > 5) + return; + + //PLAGUE STRIKE + if (IsSpellReady(PLAGUE_STRIKE_1, diff) && !noDiseases && (!blop || blop->GetBase()->GetDuration() < 3000) && + HaveRunes(PLAGUE_STRIKE_1)) + { + if (doCast(mytar, GetSpell(PLAGUE_STRIKE_1))) + return; + } + + //DISEASE SECTION + + //DEATH STRIKE + if (IsSpellReady(DEATH_STRIKE_1, diff) && blop && frof && Rand() < 60 && + GetHealthPCT(me) < (80 - (10*(blop != nullptr) + 10*(frof != nullptr))) && + (!me->GetMap()->IsDungeon() || mytar->IsControlledByPlayer()) && HaveRunes(DEATH_STRIKE_1)) + { + if (doCast(mytar, GetSpell(DEATH_STRIKE_1))) + return; + } + //OBLITERATE + if (IsSpellReady(OBLITERATE_1, diff) && (noDiseases || (blop && frof)) && HaveRunes(OBLITERATE_1)) + { + //DEATHCHILL + if (IsSpellReady(DEATHCHILL_1, diff, false) && doCast(me, GetSpell(DEATHCHILL_1))) + {/* BotWhisper("Deathchill used!"); */} + if (doCast(mytar, GetSpell(OBLITERATE_1))) + return; + } + //HEART STRIKE - splash + if (IsSpellReady(HEART_STRIKE_1, diff) && (noDiseases || (blop && frof)) && (IsTank() || Rand() < 40) && + HaveRunes(HEART_STRIKE_1) && FindSplashTarget()) + { + if (doCast(mytar, GetSpell(HEART_STRIKE_1))) + return; + } + //BLOOD STRIKE + if (IsSpellReady(BLOOD_STRIKE_1, diff) && (noDiseases || (blop && frof)) && HaveRunes(BLOOD_STRIKE_1)) + { + if (doCast(mytar, GetSpell(BLOOD_STRIKE_1))) + return; + } + //SCOURGE STRIKE unused + //if (IsSpellReady(SCOURGE_STRIKE_1, diff) && (noDiseases || (blop && frof)) && HaveRunes(SCOURGE_STRIKE_1)) + //{ + // if (doCast(mytar, GetSpell(SCOURGE_STRIKE_1))) + // return; + //} + + //END DISEASE SECTION + + //RUNE STRIKE tank + if (IsSpellReady(RUNE_STRIKE_1, diff, false) && (IsTank() || runicpower >= 800) && + me->HasAuraState(AURA_STATE_DEFENSE) && !me->GetCurrentSpell(CURRENT_MELEE_SPELL) && + runicpower >= rcost(RUNE_STRIKE_1)) + { + if (doCast(mytar, GetSpell(RUNE_STRIKE_1))) + return; + } + } + + void ApplyClassDamageMultiplierMelee(uint32& /*damage*/, CalcDamageInfo& damageinfo) const override + { + uint8 lvl = me->GetLevel(); + float fdamage = float(damageinfo.Damages[0].Damage); + float pctbonus = 0.0f; + + //Blood Gorged part 1 (white attacks): 10% bonus damage for all attacks + if ((GetSpec() == BOT_SPEC_DK_BLOOD) && lvl >= 64 && me->HasAuraState(AURA_STATE_HEALTH_ABOVE_75_PERCENT)) + pctbonus += 0.1f; + + damageinfo.Damages[0].Damage = uint32(fdamage * (1.0f + pctbonus)); + } + + void ApplyClassSpellCritMultiplierAll(Unit const* /*victim*/, float& crit_chance, SpellInfo const* spellInfo, SpellSchoolMask /*schoolMask*/, WeaponAttackType /*attackType*/) const override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + uint8 lvl = me->GetLevel(); + + //Increased Plague Strike Crit (id 60130): 10% additional critical chance for Plague Strike + if (baseId == PLAGUE_STRIKE_1) + crit_chance += 10.f; + //Scourge / Oblit Crit Up (60134): 5% additional critical chance for Scourge Strike and Obliterate + if (lvl >= 80 && (baseId == SCOURGE_STRIKE_1 || baseId == OBLITERATE_1)) + crit_chance += 5.f; + + //Killing Machine + if (AuraEffect const* mach = me->GetAuraEffect(KILLING_MACHINE_BUFF, 0)) + if (mach->IsAffectingSpell(spellInfo)) + crit_chance += 100.f; + //Deathchill + if (AuraEffect const* chil = me->GetAuraEffect(DEATHCHILL_1, 0)) + if (chil->IsAffectingSpell(spellInfo)) + crit_chance += 100.f; + + //Subversion: 9% additional critical chance for Blood Strike, Scourge Strike, Heart Strike and Obliterate + if (baseId == BLOOD_STRIKE_1 || baseId == HEART_STRIKE_1 || baseId == SCOURGE_STRIKE_1 || baseId == OBLITERATE_1) + crit_chance += 9.f; + //Vicious Strikes (part 1): 6% additional critical chance for Plague Strike and Scourge Strike + if (baseId == PLAGUE_STRIKE_1 || baseId == SCOURGE_STRIKE_1) + crit_chance += 6.f; + //Rime (part 1): 15% additional critical chance for Icy Touch and Obliterate + if ((GetSpec() == BOT_SPEC_DK_FROST) && lvl >= 60 && (baseId == ICY_TOUCH_1 || baseId == OBLITERATE_1)) + crit_chance += 15.f; + //Improved Death Strike (part 2): 6% additional critical chance for Death Strike + if ((GetSpec() == BOT_SPEC_DK_BLOOD) && lvl >= 62 && baseId == DEATH_STRIKE_1) + crit_chance += 6.f; + + //Glyph of Rune Strike: 10% additional critical chance for Rune Strike + if (baseId == RUNE_STRIKE_1) + crit_chance += 10.f; + + //Item - Death Knight T8 Melee 2P Bonus: 8% additional critical chance for Death Coil and Frost Strike + if (lvl >= 80 && + (baseId == DEATH_COIL_1 || baseId == DEATH_COIL_DAMAGE || + baseId == DEATH_COIL_HEAL || baseId == FROST_STRIKE_1)) + crit_chance += 8.f; + } + + void ApplyClassDamageMultiplierMeleeSpell(int32& damage, SpellNonMeleeDamage& damageinfo, SpellInfo const* spellInfo, WeaponAttackType /*attackType*/, bool iscrit) const override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + uint8 lvl = me->GetLevel(); + float fdamage = float(damage); + + //apply bonus damage mods + float pctbonus = 0.0f; + if (iscrit) + { + //!!!Melee spell damage is not yet critical, all reduced by half + + //Might of Mograine: 45% crit damage bonus for Blood Boil, Blood Strike, Death Strike and Heart Strike + if ((GetSpec() == BOT_SPEC_DK_BLOOD) && lvl >= 63 && + (baseId == BLOOD_BOIL_1 || baseId == BLOOD_STRIKE_1 || baseId == DEATH_STRIKE_1 || baseId == HEART_STRIKE_1)) + pctbonus += 0.45f / 2.f; + //Guile of Gorefiend (part 1 melee): 45% crit damage bonus for Blood Strike, Frost Strike, Howling Blast and Obliterate + if ((GetSpec() == BOT_SPEC_DK_FROST) && lvl >= 63 && + (baseId == BLOOD_STRIKE_1 || baseId == FROST_STRIKE_1 || + baseId == HOWLING_BLAST_1 || baseId == OBLITERATE_1)) + pctbonus += 0.45f / 2.f; + //Vicious Strikes (part 2): 30% crit damage bonus for Plague Strike and Scourge Strike + if (baseId == PLAGUE_STRIKE_1 || baseId == SCOURGE_STRIKE_1) + pctbonus += 0.3f / 2.f; + } + + //Increased Blood Strike Damage (52394): 90 bonus damage for Blood Strike and Heart Strike + if (lvl >= 70 && (baseId == BLOOD_STRIKE_1 || baseId == HEART_STRIKE_1)) + fdamage += 90.f; + //Copy of Increased Blood Strike Damage (60825): 90 bonus damage for Blood Strike and Heart Strike + if (lvl >= 80 && (baseId == BLOOD_STRIKE_1 || baseId == HEART_STRIKE_1)) + fdamage += 90.f; + //Increased Scourge Strike Damage (54809): 91 bonus damage for Scourge Strike + if (lvl >= 80 && baseId == SCOURGE_STRIKE_1) + fdamage += 91.f; + + //Outbreak: 30% bonus damage for Plague Strike and 20% for Scourge Strike + if (lvl >= 57) + { + if (baseId == PLAGUE_STRIKE_1) + pctbonus += 0.3f; + else if (baseId == SCOURGE_STRIKE_1) + pctbonus += 0.2f; + } + //Bloody Strikes: 15% bonus damage for Blood Strike, 45% for Heart Strike and 30% for Blood Boil + if ((GetSpec() == BOT_SPEC_DK_BLOOD) && lvl >= 59) + { + if (baseId == BLOOD_STRIKE_1) + pctbonus += 0.15f; + else if (baseId == HEART_STRIKE_1) + pctbonus += 0.45f; + else if (baseId == BLOOD_BOIL_1) + pctbonus += 0.3f; + } + //Merciless Combat (melee): 12% bonus damage for Icy Touch, Howling Blast, Obliterate and Frost Strike on targets with less than 35% hp + if ((GetSpec() == BOT_SPEC_DK_FROST) && lvl >= 60 && + (baseId == ICY_TOUCH_1 || baseId == HOWLING_BLAST_1 || baseId == OBLITERATE_1 || baseId == FROST_STRIKE_1) && + damageinfo.target->HasAuraState(AURA_STATE_HEALTHLESS_35_PERCENT)) + pctbonus += 0.12f; + //Improved Death Strike (part 1): 30% bonus damage for Death Strike + if ((GetSpec() == BOT_SPEC_DK_BLOOD) && lvl >= 62 && baseId == DEATH_STRIKE_1) + pctbonus += 0.3f; + //Blood of the North (part 1): 10% bonus damage for Blood Strike and Frost Strike + if ((GetSpec() == BOT_SPEC_DK_FROST) && lvl >= 62 && (baseId == BLOOD_STRIKE_1 || baseId == FROST_STRIKE_1)) + pctbonus += 0.1f; + //Blood Gorged part 1 (melee): 10% bonus damage for all spells + if ((GetSpec() == BOT_SPEC_DK_BLOOD) && lvl >= 64 && me->HasAuraState(AURA_STATE_HEALTH_ABOVE_75_PERCENT)) + pctbonus += 0.1f; + //Tundra Stalker (melee): 15% damage bonus on targets affected with Frost Fever + if ((GetSpec() == BOT_SPEC_DK_FROST) && + lvl >= 64 && damageinfo.target->GetAuraEffect(SPELL_AURA_MOD_RANGED_HASTE, SPELLFAMILY_DEATHKNIGHT, 0x0, 0x0, 0x2)) + pctbonus += 0.15f; + //Rage of Rivendare (melee): 10% damage bonus on targets affected with Blood Plague + if ((GetSpec() == BOT_SPEC_DK_UNHOLY) && + lvl >= 64 && damageinfo.target->GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_DEATHKNIGHT, 0x0, 0x2000000, 0x0)) + pctbonus += 0.1f; + + //Glyph of Blood Strike: 20% bonus damage for Blood Strike on snared targets + if (baseId == BLOOD_STRIKE_1 && damageinfo.target->HasAuraWithMechanic(1<= 10) + { + //10 to 250 * 0.001 = 10 to 250 / 1000 = 0.01 to 0.25 + pctbonus += float(std::min(runicpower, 250)) * 0.001f; + } + //Glyph of Obliterate: 25% bonus damage for Obliterate + if (baseId == OBLITERATE_1) + pctbonus += 0.25f; + //Glyph of Plague Strike: 20% bonus damage for Plague Strike + if (baseId == PLAGUE_STRIKE_1) + pctbonus += 0.2f; + + //Item - Death Knight T8 Tank 2P Bonus + if (lvl >= 80 && baseId == RUNE_STRIKE_1) + pctbonus += 0.1f; + //Item - Death Knight T8 DPS Relic + if (lvl >= 80 && baseId == FROST_STRIKE_1) + fdamage += 380.f; + //Item - Death Knight T9 Tank 2P Bonus + if (lvl >= 80 && (baseId == BLOOD_STRIKE_1 || baseId == HEART_STRIKE_1)) + pctbonus += 0.05f; + //Item - Death Knight T10 Melee 2P Bonus part 1 + if (lvl >= 80 && baseId == OBLITERATE_1) + pctbonus += 0.1f; + //Item - Death Knight T10 Melee 2P Bonus part 2 + if (lvl >= 80 && baseId == HEART_STRIKE_1) + pctbonus += 0.07f; + + damage = int32(fdamage * (1.0f + pctbonus)); + } + + void ApplyClassDamageMultiplierSpell(int32& damage, SpellNonMeleeDamage& damageinfo, SpellInfo const* spellInfo, WeaponAttackType /*attackType*/, bool iscrit) const override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + uint8 lvl = me->GetLevel(); + float fdamage = float(damage); + + //apply bonus damage mods + float pctbonus = 0.0f; + if (iscrit) + { + //!!!spell damage is not yet critical and will be multiplied by 1.5 + //so we should put here bonus damage mult /1.5 + + //Runic Focus (class passive): 50% crit damage bonus for all spells + pctbonus += 0.5f / 1.5f; + + //Guile of Gorefiend (part 1 spell): 45% crit damage bonus for Blood Strike, Frost Strike, Howling Blast and Obliterate + if ((GetSpec() == BOT_SPEC_DK_FROST) && lvl >= 63 && + (baseId == BLOOD_STRIKE_1 || baseId == FROST_STRIKE_1 || + baseId == HOWLING_BLAST_1 || baseId == OBLITERATE_1)) + pctbonus += 0.45f / 1.5f; + } + + //Improved Icy Touch part 1: 15% bonus damage for Icy Touch + if (baseId == ICY_TOUCH_1) + pctbonus += 0.15f; + //Black Ice: 10% bonus damage for Shadow and Frost spells + if (spellInfo->GetSchoolMask() & (SPELL_SCHOOL_MASK_FROST|SPELL_SCHOOL_MASK_SHADOW)) + pctbonus += 0.1f; + //Morbidity part 1: 15% damage bonus for Death Coil + if (baseId == DEATH_COIL_1 || baseId == DEATH_COIL_DAMAGE) + pctbonus += 0.15f; + //Glacier Rot: 20% bonus damage for Icy Touch, Howling Blast and Frost Strike on diseased targets + if ((GetSpec() == BOT_SPEC_DK_FROST) && + lvl >= 59 && (baseId == ICY_TOUCH_1 || baseId == HOWLING_BLAST_1 || baseId == FROST_STRIKE_1) && + IsDiseased(damageinfo.target)) + pctbonus += 0.2f; + //Impurity: 20% bonus (from attack power) damage for all spells + if ((GetSpec() == BOT_SPEC_DK_UNHOLY) && lvl >= 59) + if (SpellBonusEntry const* bonus = sSpellMgr->GetSpellBonusData(spellInfo->Id)) + if (bonus->ap_bonus > 0.f) + fdamage += bonus->ap_bonus * 0.2f * me->GetTotalAttackPowerValue(BASE_ATTACK); + //Merciless Combat (spell): 12% bonus damage for Icy Touch, Howling Blast, Obliterate and Frost Strike on targets with less than 35% hp + if ((GetSpec() == BOT_SPEC_DK_FROST) && lvl >= 60 && + (baseId == ICY_TOUCH_1 || baseId == HOWLING_BLAST_1 || baseId == OBLITERATE_1 || baseId == FROST_STRIKE_1) && + damageinfo.target->HasAuraState(AURA_STATE_HEALTHLESS_35_PERCENT)) + pctbonus += 0.12f; + //Blood Gorged part 1 (spell): 10% bonus damage for all spells + if ((GetSpec() == BOT_SPEC_DK_BLOOD) && lvl >= 64 && me->HasAuraState(AURA_STATE_HEALTH_ABOVE_75_PERCENT)) + pctbonus += 0.1f; + //Tundra Stalker (spell): 15% damage bonus on targets affected with Frost Fever + if ((GetSpec() == BOT_SPEC_DK_FROST) && + lvl >= 64 && damageinfo.target->GetAuraEffect(SPELL_AURA_MOD_RANGED_HASTE, SPELLFAMILY_DEATHKNIGHT, 0x0, 0x0, 0x2)) + pctbonus += 0.15f; + //Rage of Rivendare (spell): 10% damage bonus on targets affected with Blood Plague + if ((GetSpec() == BOT_SPEC_DK_UNHOLY) && + lvl >= 64 && damageinfo.target->GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_DEATHKNIGHT, 0x0, 0x2000000, 0x0)) + pctbonus += 0.1f; + + //Glyph of Dark Death part 1: 15% damage bonus for Death Coil + if (baseId == DEATH_COIL_1 || baseId == DEATH_COIL_DAMAGE) + pctbonus += 0.15f; + //Glyph of Icy Touch: 20% damage bonus for Frost Fever + if (baseId == FROST_FEVER || baseId == FROST_FEVER_AURA) + pctbonus += 0.2f; + + //Item - Death Knight T8 DPS Relic + if (lvl >= 80 && baseId == DEATH_COIL_DAMAGE) + fdamage += 113.f; + //Increased Icy Touch Damage (id 54800): 111 bonus damage for Icy Touch + if (baseId == ICY_TOUCH_1) + fdamage += 111.f; + //Increased Death Coil Damage (id 54807): 80 bonus damage for Death Coil + if (baseId == DEATH_COIL_1 || baseId == DEATH_COIL_DAMAGE) + fdamage += 80.f; + + damage = int32(fdamage * (1.0f + pctbonus)); + } + + void ApplyClassDamageMultiplierHeal(Unit const* /*victim*/, float& heal, SpellInfo const* spellInfo, DamageEffectType /*damagetype*/, uint32 /*stack*/) const override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + uint8 lvl = me->GetLevel(); + float pctbonus = 0.0f; + float flat_mod = 0.0f; + + //pct mods + //Morbidity part 2: 15% bonus healing for Death Coil + if (baseId == DEATH_COIL_1 || baseId == DEATH_COIL_HEAL) + pctbonus += 0.15f; + //Improved Rune Tap part 1: 100% bonus healing for Rune Tap + if (lvl >= 58 && baseId == RUNE_TAP_1) + pctbonus += 1.f; + //Improved Death Strike (part 3): 50% bonus healing for Death Strike + if ((GetSpec() == BOT_SPEC_DK_BLOOD) && lvl >= 62 && baseId == DEATH_STRIKE_HEAL) + pctbonus += 0.5f; + + //Glyph of Dark Death part 2: 15% bonus healing for Death Coil + if (baseId == DEATH_COIL_1 || baseId == DEATH_COIL_HEAL) + pctbonus += 0.15f; + //Glyph of Rune Tap part 1: 10% bonus healing for Rune Tap + if (baseId == RUNE_TAP_1) + pctbonus += 0.1f; + + //flat mods + //Improved Prayer of Mending: 100 additional heal for Prayer of Mending + //if (baseId == PRAYER_OF_MENDING_HEAL) + // flat_mod += 100; + + heal = heal * (1.0f + pctbonus) + flat_mod; + } + + void ApplyClassSpellCostMods(SpellInfo const* spellInfo, int32& cost) const override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + uint8 lvl = me->GetLevel(); + float fcost = float(cost); + int32 flatbonus = 0; + float pctbonus = 0.0f; + + //percent mods + //Endless Winter part 2 -100% Runic Power cost for Mind Freeze + if (lvl >= 58 && baseId == MIND_FREEZE_1) + pctbonus += 1.f; + + //Glyph of Blood Tap: -100% Health cost for Blood Tap + if (baseId == BLOOD_TAP_1) + pctbonus += 1.f; + + //flat mods + //Glyph of Frost Strike: -8 Runic Power cost for Frost Strike + if (baseId == FROST_STRIKE_1) + flatbonus += 80; + //Glyph of Hungering Cold: -40 Runic Power cost for Hungering Cold + if (lvl >= 60 && baseId == HUNGERING_COLD_1) + flatbonus += 400; + + //cost can be < 0 + cost = int32(fcost * (1.0f - pctbonus)) - flatbonus; + } + + void ApplyClassSpellCooldownMods(SpellInfo const* spellInfo, uint32& cooldown) const override + { + //cooldown is in milliseconds + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + uint8 lvl = me->GetLevel(); + int32 timebonus = 0; + float pctbonus = 0.0f; + + //pct mods + //Aspiration + //if (lvl >= 45 && (baseId == INNER_FOCUS_1 || baseId == POWER_INFUSION_1 || baseId == PAIN_SUPPRESSION_1)) + // pctbonus += 0.2f; + + //flat mods + //Unholy Command: -10 sec cooldown for Dark Command + if (lvl >= 56 && baseId == DARK_COMMAND_1) + timebonus += 10000; + //Improved Rune Tap part 2: -30 sec cooldown for Rune Tap + if (lvl >= 57 && baseId == RUNE_TAP_1) + timebonus += 30000; + + //Glyph of Strangulate: -20 sec cooldown for Strangulate + if (baseId == STRANGULATE_1) + timebonus += 20000; + + //Item - Death Knight T9 Tank 2P Bonus + if (lvl >= 80 && (baseId == UNBREAKABLE_ARMOR_1 || baseId == VAMPIRIC_BLOOD_1 || baseId == BONE_SHIELD_1)) + timebonus += 10000; + //Strangulate Cooldown Reduction: -5 sec cooldown for Strangulate + if (baseId == STRANGULATE_1) + timebonus += 5000; + + //Empower Rune Weapon Cooldown Reduction For Bot: -3 min + if (baseId == EMPOWER_RUNE_WEAPON_1) + timebonus += 180000; + + cooldown = int32(std::max((float(cooldown) * (1.0f - pctbonus)) - timebonus, 0.f)); + } + + void ApplyClassSpellCategoryCooldownMods(SpellInfo const* spellInfo, uint32& cooldown) const override + { + //cooldown is in milliseconds + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + uint8 lvl = me->GetLevel(); + int32 timebonus = 0; + float pctbonus = 0.0f; + + //pct mods + //Aspiration + //if (lvl >= 45 && baseId == PENANCE_1) + // pctbonus += 0.2f; + + //flat mods + //Morbidity part 2 + if (baseId == DEATH_AND_DECAY_1) + timebonus += 15000; + + //Item - Death Knight T9 Tank 2P Bonus + if (lvl >= 80 && baseId == DARK_COMMAND_1) + timebonus += 2000; + + cooldown = int32(std::max((float(cooldown) * (1.0f - pctbonus)) - timebonus, 0.f)); + } + + void ApplyClassSpellGlobalCooldownMods(SpellInfo const* spellInfo, float& cooldown) const override + { + //cooldown is in milliseconds + //uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + //uint8 lvl = me->GetLevel(); + float timebonus = 0.0f; + float pctbonus = 0.0f; + + //Unholy Presence + if (_presence == DEATH_KNIGHT_UNHOLY_PRESENCE && + ((spellInfo->SpellFamilyFlags[0] & 0xFFDFFE7F) || + (spellInfo->SpellFamilyFlags[0] & 0x480B11F7) || + (spellInfo->SpellFamilyFlags[0] & 0x20))) + timebonus += 500.f; + + cooldown = (cooldown * (1.0f - pctbonus)) - timebonus; + } + + void ApplyClassSpellRadiusMods(SpellInfo const* spellInfo, float& radius) const override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + //uint8 lvl = me->GetLevel(); + float flatbonus = 0.0f; + float pctbonus = 0.0f; + + //pct mods + ////Holy Reach + //if (lvl >= 25 && ((spellInfo->SpellFamilyFlags[0] & 0x18400200) || (spellInfo->SpellFamilyFlags[2] & 0x4))) + // pctbonus += 0.2f; + + //flat mods + //Glyph of Corpse Explosion + if (spellInfo->SpellFamilyFlags[1] & 0x20) + flatbonus += 5.f; + //Glyph of Pestilence + if (baseId == PESTILENCE_1) + flatbonus += 5.f; + + radius = radius * (1.0f + pctbonus) + flatbonus; + } + + void ApplyClassSpellRangeMods(SpellInfo const* spellInfo, float& maxrange) const override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + //uint8 lvl = me->GetLevel(); + float flatbonus = 0.0f; + float pctbonus = 0.0f; + + //pct mods + //Holy Reach: +20% range for Holy Spells + //if (lvl >= 25 && (spellInfo->SpellFamilyFlags[0] & 0x100080)) + // pctbonus += 0.2f; + + //flat mods + //Icy Reach: +10 yd range for Icy Touch, Chains of Ice and Howling Blast + if (baseId == ICY_TOUCH_1 || baseId == CHAINS_OF_ICE_1 || baseId == HOWLING_BLAST_1) + flatbonus += 10.f; + + maxrange = maxrange * (1.0f + pctbonus) + flatbonus; + } + + void OnClassSpellGo(SpellInfo const* spellInfo) override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + uint8 lvl = me->GetLevel(); + + //Glyph of Rune Tap part 2 + if (baseId == RUNE_TAP_1) + me->CastSpell(me, GLYPH_RUNE_TAP_HEAL, true); + + //Empower Rune Weapon: rune activation helper + if (baseId == EMPOWER_RUNE_WEAPON_1) + ActivateAllRunes(); + + //Improved Chains of Ice (62142): convert frost rune into death rune + if (lvl >= 80 && baseId == CHAINS_OF_ICE_1) + { + ConvertRune(RUNE_FROST); + } + //Death Rune Mastery: convert Unholy and Frost Runes into Death Runes + if (lvl >= 57 && (baseId == DEATH_STRIKE_1 || baseId == OBLITERATE_1)) + { + ConvertRune(RUNE_UNHOLY); + ConvertRune(RUNE_FROST); + } + //Reaping: Blood Strike and Pestilence convert Blood Rune to Death Rune + if ((GetSpec() == BOT_SPEC_DK_UNHOLY) && lvl >= 60 && (baseId == BLOOD_STRIKE_1 || baseId == PESTILENCE_1)) + { + ConvertRune(RUNE_BLOOD); + //Blood of the North (part 2): same effect + if (lvl >= 62) + ConvertRune(RUNE_BLOOD); + } + //Rime: consume buff + if (baseId == HOWLING_BLAST_1 && rimeProcTimer > GetLastDiff() && me->HasAura(RIME_BUFF)) + me->RemoveAurasDueToSpell(RIME_BUFF); + //Blood Tap + if (baseId == BLOOD_TAP_1) + { + ConvertRune(RUNE_BLOOD); + + //Item - Death Knight T10 Tank 4P Bonus: Blood Armor (12% damage reduce) + if (lvl >= 80) + me->CastSpell(me, ITEM_DEATH_KNIGHT_T10_TANK_4P_BUFF, true); + } + + //stances + if (spellInfo->GetCategory() == 47) + { + presencetimer = 1000; + + if (baseId == BLOOD_PRESENCE_1) + _presence = DEATH_KNIGHT_BLOOD_PRESENCE; + else if (baseId == FROST_PRESENCE_1) + _presence = DEATH_KNIGHT_FROST_PRESENCE; + else if (baseId == UNHOLY_PRESENCE_1) + _presence = DEATH_KNIGHT_UNHOLY_PRESENCE; + } + } + + void SpellHitTarget(WorldObject* wtarget, SpellInfo const* spell) override + { + Unit* target = wtarget->ToUnit(); + if (!target) + return; + + uint32 spellId = spell->Id; + uint32 baseId = spell->GetFirstRankSpell()->Id; + uint8 lvl = me->GetLevel(); + + //consume buffs (not on spell go) + //Killing Machine + //Deathchill + AuraEffect const* mach = me->GetAuraEffect(KILLING_MACHINE_BUFF, 0); + AuraEffect const* chil = me->GetAuraEffect(DEATHCHILL_1, 0); + if (mach && mach->IsAffectingSpell(spell)) + me->RemoveAurasDueToSpell(KILLING_MACHINE_BUFF); + else if (chil && chil->IsAffectingSpell(spell)) + me->RemoveAurasDueToSpell(DEATHCHILL_1); + + //Icy Touch tanking helper (TODO: remove this hack after threat mods implementation) + //emulating passive mod +600% threat generated by Icy Touch while in Frost Presence + if (baseId == ICY_TOUCH_1 && _presence == DEATH_KNIGHT_FROST_PRESENCE && target->CanHaveThreatList()) + { + if (SpellThreatEntry const* threatEntry = sSpellMgr->GetSpellThreatEntry(spellId)) + { + int32 baseThreat = 0; + if (threatEntry->apPctMod != 0x0) + baseThreat += int32(threatEntry->apPctMod * me->GetTotalAttackPowerValue(BASE_ATTACK)); + baseThreat += threatEntry->flatMod; + + if (baseThreat) + target->GetThreatManager().AddThreat(me, baseThreat * 6.f, spell); + } + } + + //Improved Icy Touch part 2: 6% increased effect (flat) + if (baseId == FROST_FEVER_AURA) + { + if (Aura const* feve = target->GetAura(spellId, me->GetGUID())) + { + if (AuraEffect* fev1 = feve->GetEffect(1)) + fev1->ChangeAmount(fev1->GetAmount() - 6); + if (AuraEffect* fev2 = feve->GetEffect(2)) + fev2->ChangeAmount(fev2->GetAmount() - 6); + } + } + //Rime (part 2): Obliterate has 15% chance to reset Howling Blast cooldown + if ((GetSpec() == BOT_SPEC_DK_FROST) && baseId == OBLITERATE_1 && urand(1,100) <= 15) + { + ResetSpellCooldown(HOWLING_BLAST_1); + me->CastSpell(me, RIME_BUFF, true); + } + + //Glyph of Horn of Winter: 1 minute bonus duration (8 for bot) + if (baseId == HORN_OF_WINTER_1) + { + if (Aura* horn = target->GetAura(spellId, me->GetGUID())) + { + uint32 dur = horn->GetDuration() + 480000; + horn->SetDuration(dur); + horn->SetMaxDuration(dur); + } + + //Winter Veil addition + if (sGameEventMgr->IsActiveEvent(GAME_EVENT_WINTER_VEIL)) + target->AddAura(44755, target); //snowflakes + } + //Epidemic: 6 sec bonus duration for DK Diseases + if (baseId == FROST_FEVER_AURA || baseId == BLOOD_PLAGUE_AURA || + baseId == CRYPT_FEVER_AURA || baseId == EBON_PLAGUE_AURA) + { + if (Aura* fever = target->GetAura(spellId, me->GetGUID())) + { + uint32 dur = fever->GetDuration() + 6000; + fever->SetDuration(dur); + fever->SetMaxDuration(dur); + } + } + //Chilblains: proc Icy Clutch + if ((GetSpec() == BOT_SPEC_DK_FROST) && lvl >= 61 && baseId == FROST_FEVER_AURA) + me->CastSpell(target, CHILBLAINS_DEBUFF, true); + //Sudden Doom: 15% ctc Death Coil on Blood Strike or Heart Strike + if ((GetSpec() == BOT_SPEC_DK_BLOOD) && + (baseId == BLOOD_STRIKE_1 || baseId == HEART_STRIKE_1) && GetSpell(DEATH_COIL_1) && urand(1,100) <= 15) + me->CastSpell(target, GetSpell(DEATH_COIL_1), true); + //Glyph of Heart Strike + if (baseId == HEART_STRIKE_1) + me->CastSpell(target, GLYPH_HEART_STRIKE_DEBUFF, true); + //Glyph of Howling Blast + if (lvl >= 60 && baseId == HOWLING_BLAST_1) + me->CastSpell(target, FROST_FEVER_AURA, true); + //Glyph of Scourge Strike + if (baseId == SCOURGE_STRIKE_1) + me->CastSpell(target, GLYPH_SCOURGE_STRIKE_EFFECT, true); + + OnSpellHitTarget(target, spell); + } + + void SpellHit(WorldObject* wcaster, SpellInfo const* spell) override + { + Unit* caster = wcaster->ToUnit(); + if (!caster) + return; + + uint32 baseId = spell->GetFirstRankSpell()->Id; + uint32 spellId = spell->Id; + uint8 lvl = me->GetLevel(); + + //Rime helper (Freezing Fog) + if (baseId == RIME_BUFF) + { + rimeProcTimer = 15000; + } + + //Improved Blood Presence + if ((GetSpec() == BOT_SPEC_DK_BLOOD) && lvl >= 61 && baseId == BLOOD_PRESENCE_1) + { + if (AuraEffect* pres = me->GetAuraEffect(spellId, 1)) + pres->ChangeAmount(pres->GetAmount() + 10); + } + if (baseId == ANTI_MAGIC_SHELL_1) + { + if (Aura* shell = me->GetAura(spellId)) + { + //Glyph of Anti-Magic Shell: 2 sec increased duration + uint32 dur = shell->GetDuration() + 2000; + shell->SetDuration(dur); + shell->SetMaxDuration(dur); + //Magic Suppression part 2 + if (AuraEffect* shab = shell->GetEffect(0)) + shab->ChangeAmount(int32(shab->GetAmount() * 1.25f)); + } + } + if (baseId == VAMPIRIC_BLOOD_1) + { + //Glyph of Vampiric Blood: 5 sec increased duration + if (Aura* bloo = me->GetAura(spellId)) + { + uint32 dur = bloo->GetDuration() + 5000; + bloo->SetDuration(dur); + bloo->SetMaxDuration(dur); + } + } + if (baseId == BONE_SHIELD_1) + { + //Glyph of Bone Shield: 1 bonus charge (1 for bot, 3 for tank) + if (Aura* bone = me->GetAura(spellId)) + bone->SetCharges(bone->GetCharges() + (IsTank() ? 3 : 1)); + } + if (baseId == ICEBOUND_FORTITUDE_1) + { + if (Aura* fort = me->GetAura(spellId)) + { + //Icebound Duration Increase: + 3 sec duration + uint32 dur = fort->GetDuration() + 3000; + + //Guile of Gorefiend (part 2): Icebound Fortitude 6 sec increased duration + if ((GetSpec() == BOT_SPEC_DK_FROST) && lvl >= 63) + dur += 6000; + + fort->SetDuration(dur); + fort->SetMaxDuration(dur); + + if (AuraEffect* eff2 = fort->GetEffect(EFFECT_2)) + { + //calc correct amount + int32 amount = eff2->GetAmount() - int32(0.15f * (std::max(0, GetBotDefense() - lvl*5))); + //Glyph of Icebound Fortitude + amount = std::min(amount, -40); + //Increased Icebound Fortitude Mitigation (54803) + if (lvl >= 70) + amount -= 2; + eff2->ChangeAmount(amount); + } + } + } + if (baseId == UNBREAKABLE_ARMOR_1) + { + if (AuraEffect* armo = me->GetAuraEffect(spellId, 0)) + armo->ChangeAmount(armo->GetAmount() + 5); //25 +20% = 30 + } + + OnSpellHit(caster, spell); + } + + void DamageDealt(Unit* victim, uint32& damage, DamageEffectType damageType) override + { + if (victim == me) + return; + + if (damageType == DIRECT_DAMAGE || damageType == SPELL_DIRECT_DAMAGE) + { + //Blood Presence Heal + if (_presence == DEATH_KNIGHT_FROST_PRESENCE || _presence == DEATH_KNIGHT_UNHOLY_PRESENCE) + { + if (int32 bp0 = int32(damage / 25)) //4% + { + CastSpellExtraArgs args(true); + args.AddSpellBP0(bp0); + me->CastSpell(me, BLOOD_PRESENCE_HEAL_EFFECT, args); + } + } + } + + bot_ai::DamageDealt(victim, damage, damageType); + } + + void DamageTaken(Unit* u, uint32& /*damage*/, DamageEffectType /*damageType*/, SpellInfo const* /*spellInfo*/) override + { + if (!u) + return; + if (!u->IsInCombat() && !me->IsInCombat()) + return; + OnOwnerDamagedBy(u); + } + + void OwnerAttackedBy(Unit* u) override + { + if (!u) + return; + OnOwnerDamagedBy(u); + } + + uint8 GetPetPositionNumber(Creature const* /*summon*/) const override + { + //TODO: garg, aod, drw + return 0; + } + + void SummonBotPet() + { + if (botPet) + UnsummonAll(false); + + uint32 entry = BOT_PET_GHOUL; + + Position pos; + + Creature* myPet = me->SummonCreature(entry, *me, TEMPSUMMON_CORPSE_DESPAWN); + me->GetNearPoint(myPet, pos.m_positionX, pos.m_positionY, pos.m_positionZ, 0.f, float(me->GetOrientation() + M_PI / 2.f)); + myPet->GetMotionMaster()->MovePoint(me->GetMapId(), pos); + myPet->SetCreator(master); + myPet->SetOwnerGUID(me->GetGUID()); + myPet->SetFaction(master->GetFaction()); + myPet->SetControlledByPlayer(!IAmFree()); + myPet->SetPvP(me->IsPvP()); + myPet->SetByteValue(UNIT_FIELD_BYTES_2, 1, master->GetByteValue(UNIT_FIELD_BYTES_2, 1)); + + botPet = myPet; + } + + void UnsummonAll(bool savePets = true) override + { + UnsummonPet(savePets); + } + + void SummonedCreatureDies(Creature* /*summon*/, Unit* /*killer*/) override + { + //TC_LOG_ERROR("entities.unit", "SummonedCreatureDies: {}'s {}", me->GetName(), summon->GetName()); + //if (summon == botPet) + // botPet = nullptr; + } + + void SummonedCreatureDespawn(Creature* summon) override + { + //all hunter bot pets despawn at death or manually (gossip, teleport, etc.) + //TC_LOG_ERROR("entities.unit", "SummonedCreatureDespawn: {}'s {}", me->GetName(), summon->GetName()); + if (summon == botPet) + { + petSummonTimer = 30000; + botPet = nullptr; + } + } + + uint32 GetAIMiscValue(uint32 data) const override + { + switch (data) + { + case BOTAI_MISC_PET_TYPE: + return BOT_PET_GHOUL; + default: + return 0; + } + } + + void Reset() override + { + UnsummonAll(false); + + petSummonTimer = 5000; + + presencetimer = 0; + runicpowertimer = 2000; + runicpowertimer2 = 5000; + + rimeProcTimer = 0; + + _presence = BOT_STANCE_NONE; + + runicpowerIncomeMult = sWorld->getRate(RATE_POWER_RUNICPOWER_INCOME); + runicpowerLossMult = sWorld->getRate(RATE_POWER_RUNICPOWER_LOSS); + + DefaultInit(); + InitRunes(); + } + + void ReduceCD(uint32 diff) override + { + RuneTimers(diff); + + if (presencetimer > diff) presencetimer -= diff; + if (runicpowertimer > diff) runicpowertimer -= diff; + if (runicpowertimer2 > diff) runicpowertimer2 -= diff; + + if (rimeProcTimer > diff) rimeProcTimer -= diff; + + if (petSummonTimer > diff) petSummonTimer -= diff; + } + + void InitPowers() override + { + me->SetPowerType(POWER_RUNIC_POWER); + me->SetMaxPower(POWER_RUNIC_POWER, 1300); + //RefreshAura(RUNIC_POWER_MASTERY); + //if (AuraEffect* mast = me->GetAuraEffect(RUNIC_POWER_MASTERY, 0)) + //{ + // //Runic Power Mastery rank 2 + // mast->ChangeAmount(1300); + //} + + if (botPet && botPet->GetPowerType() != POWER_ENERGY) + botPet->SetByteValue(UNIT_FIELD_BYTES_0, 3, POWER_ENERGY); + } + + void InitSpells() override + { + uint8 lvl = me->GetLevel(); + bool isBloo = GetSpec() == BOT_SPEC_DK_BLOOD; + bool isFros = GetSpec() == BOT_SPEC_DK_FROST; + bool isUnho = GetSpec() == BOT_SPEC_DK_UNHOLY; + + InitSpellMap(ICY_TOUCH_1); + InitSpellMap(PLAGUE_STRIKE_1); + InitSpellMap(BLOOD_STRIKE_1); + InitSpellMap(DEATH_STRIKE_1); + InitSpellMap(OBLITERATE_1); + InitSpellMap(RUNE_STRIKE_1); + InitSpellMap(BLOOD_BOIL_1); + InitSpellMap(DEATH_AND_DECAY_1); + InitSpellMap(DEATH_COIL_1); + InitSpellMap(DEATH_GRIP_1); + InitSpellMap(PESTILENCE_1); + InitSpellMap(MIND_FREEZE_1); + InitSpellMap(STRANGULATE_1); + InitSpellMap(CHAINS_OF_ICE_1); + InitSpellMap(ICEBOUND_FORTITUDE_1); + InitSpellMap(DARK_COMMAND_1); + InitSpellMap(ANTI_MAGIC_SHELL_1); + InitSpellMap(ARMY_OF_THE_DEAD_1); + InitSpellMap(PATH_OF_FROST_1); + InitSpellMap(HORN_OF_WINTER_1); + InitSpellMap(EMPOWER_RUNE_WEAPON_1); + InitSpellMap(BLOOD_TAP_1); + + /*Talent*/lvl >= 57 ? InitSpellMap(RUNE_TAP_1) : RemoveSpell(RUNE_TAP_1); + /*Talent*/lvl >= 59 && isBloo ? InitSpellMap(MARK_OF_BLOOD_1) : RemoveSpell(MARK_OF_BLOOD_1); + /*Talent*/lvl >= 61 && isBloo ? InitSpellMap(HYSTERIA_1) : RemoveSpell(HYSTERIA_1); + /*Talent*/lvl >= 62 && isBloo ? InitSpellMap(VAMPIRIC_BLOOD_1) : RemoveSpell(VAMPIRIC_BLOOD_1); + /*Talent*/lvl >= 63 && isBloo ? InitSpellMap(HEART_STRIKE_1) : RemoveSpell(HEART_STRIKE_1); + + /*Talent*/lvl >= 57 ? InitSpellMap(LICHBORNE_1) : RemoveSpell(LICHBORNE_1); + /*Talent*/lvl >= 59 && isFros ? InitSpellMap(DEATHCHILL_1) : RemoveSpell(DEATHCHILL_1); + /*Talent*/lvl >= 61 && isFros ? InitSpellMap(HUNGERING_COLD_1) : RemoveSpell(HUNGERING_COLD_1); + /*Talent*/lvl >= 62 && isFros ? InitSpellMap(UNBREAKABLE_ARMOR_1) : RemoveSpell(UNBREAKABLE_ARMOR_1); + /*Talent*/lvl >= 63 && isFros ? InitSpellMap(FROST_STRIKE_1) : RemoveSpell(FROST_STRIKE_1); + /*Talent*/lvl >= 65 && isFros ? InitSpellMap(HOWLING_BLAST_1) : RemoveSpell(HOWLING_BLAST_1); + + /*Talent*/lvl >= 61 && isUnho ? InitSpellMap(ANTI_MAGIC_ZONE_1) : RemoveSpell(ANTI_MAGIC_ZONE_1); + /*Talent*/lvl >= 62 && isUnho ? InitSpellMap(BONE_SHIELD_1) : RemoveSpell(BONE_SHIELD_1); + /*Talent*/lvl >= 63 && isUnho ? InitSpellMap(SCOURGE_STRIKE_1) : RemoveSpell(SCOURGE_STRIKE_1); + + InitSpellMap(BLOOD_PRESENCE_1, true); + InitSpellMap(FROST_PRESENCE_1, true); + InitSpellMap(UNHOLY_PRESENCE_1, true); + } + + void ApplyClassPassives() const override + { + uint8 level = master->GetLevel(); + bool isBloo = GetSpec() == BOT_SPEC_DK_BLOOD; + bool isFros = GetSpec() == BOT_SPEC_DK_FROST; + bool isUnho = GetSpec() == BOT_SPEC_DK_UNHOLY; + + RefreshAura(BUTCHERY, level >= 55 ? 1 : 0); + RefreshAura(BLADED_ARMOR, level >= 56 ? 1 : 0); + RefreshAura(SCENT_OF_BLOOD, level >= 56 ? 1 : 0); + RefreshAura(TWO_HANDED_WEAPON_SPECIALIZATION, level >= 56 ? 1 : 0); + RefreshAura(SPELL_DEFLECTION, isBloo && level >= 58 ? 1 : 0); + RefreshAura(VENDETTA, isBloo && level >= 58 ? 1 : 0); + RefreshAura(BLOODY_VENGEANCE3, isBloo && level >= 62 ? 1 : 0); + RefreshAura(BLOODY_VENGEANCE2, isBloo && level >= 61 && level < 62 ? 1 : 0); + RefreshAura(BLOODY_VENGEANCE1, isBloo && level >= 60 && level < 61 ? 1 : 0); + RefreshAura(ABOMINATIONS_MIGHT, !IAmFree() && isBloo && level >= 60 ? 1 : 0); + //RefreshAura(BLOODWORMS, isBloo && level >= 61 ? 1 : 0); + RefreshAura(IMPROVED_BLOOD_PRESENCE, isBloo && level >= 61 ? 1 : 0); + RefreshAura(WILL_OF_THE_NECROPOLIS, isBloo && level >= 63 ? 1 : 0); + + RefreshAura(TOUGHNESS, level >= 55 ? 1 : 0); + RefreshAura(ICY_TALONS, level >= 57 ? 1 : 0); + RefreshAura(ANNIHILATION, level >= 57 ? 1 : 0); + RefreshAura(KILLING_MACHINE, isFros && level >= 58 ? 1 : 0); + RefreshAura(CHILL_OF_THE_GRAVE, isFros && level >= 58 ? 1 : 0); + RefreshAura(FRIGID_DREADPLATE, isFros && level >= 59 ? 1 : 0); + RefreshAura(IMPROVED_ICY_TALONS, !IAmFree() && isFros && level >= 60 ? 1 : 0); + RefreshAura(THREAT_OF_THASSARIAN, isFros && level >= 62 ? 1 : 0); + RefreshAura(ACCLIMATION, isFros && level >= 63 ? 1 : 0); + + RefreshAura(NECROSIS5, isUnho && level >= 62 ? 1 : 0); + RefreshAura(NECROSIS4, isUnho && level >= 60 && level < 61 ? 1 : 0); + RefreshAura(NECROSIS3, isUnho && level >= 59 && level < 60 ? 1 : 0); + RefreshAura(NECROSIS2, isUnho && level >= 58 && level < 59 ? 1 : 0); + RefreshAura(NECROSIS1, isUnho && level >= 57 && level < 58 ? 1 : 0); + RefreshAura(ON_A_PALE_HORSE_A, isUnho && level >= 58 ? 1 : 0); + RefreshAura(ON_A_PALE_HORSE_B, isUnho && level >= 58 ? 1 : 0); + RefreshAura(BLOOD_CAKED_BLADE3, isUnho && level >= 60 ? 1 : 0); + RefreshAura(BLOOD_CAKED_BLADE2, isUnho && level >= 59 && level < 60 ? 1 : 0); + RefreshAura(BLOOD_CAKED_BLADE1, isUnho && level >= 58 && level < 59 ? 1 : 0); + RefreshAura(UNHOLY_BLIGHT, isUnho && level >= 59 ? 1 : 0); + RefreshAura(DIRGE, isUnho && level >= 59 ? 1 : 0); + RefreshAura(DESECRATION, isUnho && level >= 60 ? 1 : 0); + RefreshAura(DESOLATION, isUnho && level >= 61 ? 1 : 0); + RefreshAura(IMPROVED_UNHOLY_PRESENCE, isUnho && level >= 61 ? 1 : 0); + RefreshAura(CRYPT_FEVER, isUnho && level >= 62 ? 1 : 0); + RefreshAura(WANDERING_PLAGUE, isUnho && level >= 63 ? 1 : 0); + RefreshAura(EBON_PLAGUEBRINGER, isUnho && level >= 63 ? 1 : 0); + + //RefreshAura(GLYPH_DANCING_RUNE_WEAPON, level >= 60 ? 1 : 0); + RefreshAura(GLYPH_DISEASE); + RefreshAura(GLYPH_CHAINS_OF_ICE); + RefreshAura(GLYPH_UNHOLY_BLIGHT, level >= 60 ? 1 : 0); + + RefreshAura(CHAINS_OF_ICE_FROST_RUNE_REFRESH); + RefreshAura(ITEM_DEATH_KNIGHT_T8_MELEE_4P, level >= 80 ? 1 : 0); + RefreshAura(ITEM_DEATH_KNIGHT_T9_MELEE_4P, level >= 80 ? 1 : 0); + + RefreshAura(FROST_FEVER); + RefreshAura(BLOOD_PLAGUE); + RefreshAura(RUNE_STRIKE_PASSIVE); + } + + bool CanUseManually(uint32 basespell) const override + { + switch (basespell) + { + case LICHBORNE_1: + case PATH_OF_FROST_1: + case HORN_OF_WINTER_1: + case BONE_SHIELD_1: + case RUNE_TAP_1: + case EMPOWER_RUNE_WEAPON_1: + case VAMPIRIC_BLOOD_1: + case HYSTERIA_1: + return true; + default: + return false; + } + } + + std::vector const* GetDamagingSpellsList() const override + { + return &Deathknight_spells_damage; + } + std::vector const* GetCCSpellsList() const override + { + return &Deathknight_spells_cc; + } + //std::vector const* GetHealingSpellsList() const override + //{ + // return &Deathknight_spells_heal; + //} + std::vector const* GetSupportSpellsList() const override + { + return &Deathknight_spells_support; + } + + private: + BotRuneInfo _runes[MAX_RUNES]; + +/*tmrs*/uint32 presencetimer, runicpowertimer, runicpowertimer2; + uint32 rimeProcTimer; +/*misc*/int32 runicpower; +/*misc*/int32 runeCost[NUM_RUNE_TYPES]; +/*misc*/float runicpowerIncomeMult, runicpowerLossMult; +/*Chck*/uint8 _presence; + //Pet + uint32 petSummonTimer; + + bool HaveRunes(uint32 spellId) + { + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); + return (spellInfo && HaveRunes(spellInfo)); + } + + bool HaveRunes(SpellInfo const* spellInfo) + { + if (spellInfo->PowerType != POWER_RUNE || !spellInfo->RuneCostID) + return true; + + SpellRuneCostEntry const* src = sSpellRuneCostStore.LookupEntry(spellInfo->RuneCostID); + if (!src || src->NoRuneCost()) + return true; + + //Freezing Fog + if (rimeProcTimer > GetLastDiff() && spellInfo->Id == HOWLING_BLAST_1) + return true; + + for (uint8 i = 0; i != RUNE_DEATH; ++i) + runeCost[i] = src->RuneCost[i]; + + runeCost[RUNE_DEATH] = MAX_RUNES; + + for (uint8 i = 0; i != MAX_RUNES; ++i) + { + uint8 rune = _runes[i].CurrentRune; + if (runeCost[rune] > 0 && _runes[i].Cooldown <= 0) + runeCost[rune]--; + } + + for (uint8 i = 0; i != RUNE_DEATH; ++i) + if (runeCost[i] > 0) + runeCost[RUNE_DEATH] += runeCost[i]; + + if (runeCost[RUNE_DEATH] > MAX_RUNES) + return false; + + return true; + } + + bool SpendRune(uint8 runetype, bool didHit) + { + for (uint8 i = 0; i != MAX_RUNES; ++i) + { + if (_runes[i].CurrentRune == runetype && _runes[i].Cooldown <= 0) + { + _runes[i].CurrentRune = _runes[i].BaseRune; + uint32 cooldown = didHit ? RUNE_BASE_COOLDOWN : RUNE_MISS_COOLDOWN; + + //Improved Unholy Presence + if (_presence == DEATH_KNIGHT_UNHOLY_PRESENCE) + cooldown -= 1000; + + _runes[i].Cooldown += cooldown; + return true; + } + } + + return false; + } + + uint8 GetCooledRunesCount(uint8 runetype) const + { + uint8 count = 0; + for (uint8 i = 0; i != MAX_RUNES; ++i) + if (_runes[i].BaseRune == runetype && _runes[i].Cooldown > 0) + ++count; + + return count; + } + + uint8 GetCooledRunesCount() const + { + uint8 count = 0; + for (uint8 i = 0; i != MAX_RUNES; ++i) + if (_runes[i].Cooldown > 0) + ++count; + + return count; + } + + uint32 GetTotalRunesCooldown() const + { + uint32 totalCd = 0; + for (uint8 i = 0; i != MAX_RUNES; ++i) + totalCd += std::max(_runes[i].Cooldown, 0); + + return totalCd; + } + + void ConvertRune(uint8 runetype) + { + for (uint8 i = 0; i != MAX_RUNES; ++i) + { + if (_runes[i].CurrentRune == runetype) + { + _runes[i].CurrentRune = RUNE_DEATH; + return; + } + } + } + + void ActivateAllRunes() + { + for (uint8 i = 0; i != MAX_RUNES; ++i) + _runes[i].Cooldown = std::min(_runes[i].Cooldown, me->IsInCombat() ? -1 : 0); + } + + void InitRunes() + { + for (uint8 i = 0; i != MAX_RUNES; ++i) + { + _runes[i].BaseRune = runeSlotTypes[i]; + _runes[i].CurrentRune = _runes[i].BaseRune; + _runes[i].Cooldown = 0; + } + } + + void RuneTimers(uint32 diff) + { + for (uint8 i = 0; i != MAX_RUNES; ++i) + { + int32 &cd = _runes[i].Cooldown; + if (me->IsInCombat()) + { + //RGP + if (cd != 0) + { + if (cd >= int32(-2500 + diff)) + cd -= diff; + else if (cd != -2500) + cd = -2500; + //ensurance + if (!cd) + --cd; + } + } + else + { + if (cd >= int32(diff)) + cd -= diff; + else if (cd) + cd = 0; + } + } + } + + bool IsDiseased(Unit const* unit) const + { + static const AuraType botDiseaseAuraTypes[] = + { + SPELL_AURA_PERIODIC_DAMAGE, // Frost Fever and Blood Plague + SPELL_AURA_LINKED, // Crypt Fever and Ebon Plague + SPELL_AURA_NONE + }; + + for (AuraType const* itr = botDiseaseAuraTypes; *itr != SPELL_AURA_NONE; ++itr) + { + Unit::AuraEffectList const& disAuras = unit->GetAuraEffectsByType(*itr); + for (Unit::AuraEffectList::const_iterator ditr = disAuras.begin(); ditr != disAuras.end(); ++ditr) + { + // Get auras with disease dispel type by caster + if ((*ditr)->GetSpellInfo()->Dispel == DISPEL_DISEASE) + return true; + } + } + + return false; + } + }; +}; + +void AddSC_death_knight_bot() +{ + new death_knight_bot(); +} diff --git a/src/server/game/AI/NpcBots/bot_dreadlord_ai.cpp b/src/server/game/AI/NpcBots/bot_dreadlord_ai.cpp new file mode 100644 index 000000000..363b6a777 --- /dev/null +++ b/src/server/game/AI/NpcBots/bot_dreadlord_ai.cpp @@ -0,0 +1,547 @@ +#include "bot_ai.h" +#include "botspell.h" +#include "Player.h" +#include "ScriptMgr.h" +#include "SpellAuras.h" +#include "TemporarySummon.h" +/* +Dreadlord NpcBot (by Trickerer onlysuffering@gmail.com) +Description: +Incredibly powerful demon who wields power of darkness and mental domination +Specifics: +High armor, high resistances, partially immune to control effects, damage taken speeds up spells recharge, plate armor, +deals melee/spellshadow damage, bonus damage to CCed units, spell power bonus: 200% strength. +Abilities: +1) Carrion Swarm. Sends a horde of bats combined with chaotic magic to damage enemies in frontal cone, 10 seconds cooldown. +2) Sleep. Puts the enemy target to sleep for 60 seconds (15 seconds on players) and allows next physical attack +on that target to bypass armor, removed by direct damage, 6 seconds cooldown. +3) Vampiric Aura. Increases physical critical damage by 5% and heals party and raid members within 40 yards for a +percentage (100% for Dreadlord and 25% for everyone else) of damage done by physical attacks and Carrion Swarm, no threat. +4) Summon Infernal Servant. Calls an infernal down from the sky dealing damage and stunning enemy units, lasts 180 seconds, 180 seconds cooldown. +Complete - 100% +TODO: +*/ + +enum DreadlordBaseSpells +{ + CARRION_SWARM_1 = SPELL_CARRION_SWARM, + SLEEP_1 = SPELL_SLEEP, + INFERNO_1 = SPELL_INFERNO +}; +enum DreadlordPassives +{ + VAMPIRIC_AURA = SPELL_VAMPIRIC_AURA, +}; +enum DreadlordSpecial +{ + MH_ATTACK_ANIM = SPELL_ATTACK_MELEE_1H, + + CARRION_COST = 110 * 5, + SLEEP_COST = 50 * 5, + INFERNAL_COST = 175 * 5, + + DAMAGE_CD_REDUCTION = 250,//ms + INFERNO_SPAWN_DELAY = 650,//ms + + IMMOLATION = 39007 +}; + +static const uint32 Dreadlord_spells_damage_arr[] = +{ CARRION_SWARM_1, INFERNO_1 }; + +static const uint32 Dreadlord_spells_cc_arr[] = +{ SLEEP_1 }; + +static const uint32 Dreadlord_spells_support_arr[] = +{ INFERNO_1 }; + +static const std::vector Dreadlord_spells_damage(FROM_ARRAY(Dreadlord_spells_damage_arr)); +static const std::vector Dreadlord_spells_cc(FROM_ARRAY(Dreadlord_spells_cc_arr)); +static const std::vector Dreadlord_spells_support(FROM_ARRAY(Dreadlord_spells_support_arr)); + +class dreadlord_bot : public CreatureScript +{ +public: + dreadlord_bot() : CreatureScript("dreadlord_bot") { } + + CreatureAI* GetAI(Creature* creature) const override + { + return new dreadlord_botAI(creature); + } +/* + bool OnGossipHello(Player* player, Creature* creature) + { + return creature->GetBotAI()->OnGossipHello(player, 0); + } + + bool OnGossipSelect(Player* player, Creature* creature, uint32 sender, uint32 action) + { + if (bot_ai* ai = creature->GetBotAI()) + return ai->OnGossipSelect(player, creature, sender, action); + return true; + } + + bool OnGossipSelectCode(Player* player, Creature* creature, uint32 sender, uint32 action, char const* code) + { + if (bot_ai* ai = creature->GetBotAI()) + return ai->OnGossipSelectCode(player, creature, sender, action, code); + return true; + } +*/ + struct dreadlord_botAI : public bot_ai + { + private: + //DelayedPetSpawnEvent - Dreadlord + //Impact anim, spawn, linked effects + class DelayedPetSpawnEvent : public BasicEvent + { + public: + DelayedPetSpawnEvent(Creature const* bot, Position const* pos) : _bot(bot), _pos(pos) { } + + protected: + bool Execute(uint64 /*e_time*/, uint32 /*p_time*/) + { + ((dreadlord_botAI*)_bot->AI())->SummonBotPet(_pos); + return true; + } + + private: + Creature const* _bot; + Position const* _pos; + DelayedPetSpawnEvent(DelayedPetSpawnEvent const&); + }; + + public: + dreadlord_botAI(Creature* creature) : bot_ai(creature) + { + _botclass = BOT_CLASS_DREADLORD; + + InitUnitFlags(); + + //dreadlord immunities + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_POSSESS, true); + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_CHARM, true); + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_SHAPESHIFT, true); + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_TRANSFORM, true); + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_DISARM, true); + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_DISARM_OFFHAND, true); + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_FEAR, true); + me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_DISARM, true); + me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_POLYMORPH, true); + me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_FEAR, true); + me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_HORROR, true); + me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_TURN, true); + me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_SLEEP, true); + me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_SILENCE, true); + me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_SNARE, true); + } + + bool doCast(Unit* victim, uint32 spellId) + { + if (CheckBotCast(victim, spellId) != SPELL_CAST_OK) + return false; + return bot_ai::doCast(victim, spellId); + } + + void StartAttack(Unit* u, bool force = false) + { + if (!bot_ai::StartAttack(u, force)) + return; + GetInPosition(force, u); + } + + void JustEnteredCombat(Unit* u) override { bot_ai::JustEnteredCombat(u); } + void KilledUnit(Unit* u) override { bot_ai::KilledUnit(u); } + void EnterEvadeMode(EvadeReason why = EVADE_REASON_OTHER) override { bot_ai::EnterEvadeMode(why); } + void MoveInLineOfSight(Unit* u) override { bot_ai::MoveInLineOfSight(u); } + void JustDied(Unit* u) override { UnsummonAll(false); bot_ai::JustDied(u); } + void DoNonCombatActions(uint32 /*diff*/) { } + + void CheckAura(uint32 diff) + { + if (checkAuraTimer > diff || GC_Timer > diff || IsCasting()) + return; + + checkAuraTimer = 10000; + + if (!me->HasAura(VAMPIRIC_AURA, me->GetGUID())) + RefreshAura(VAMPIRIC_AURA); + } + + void UpdateAI(uint32 diff) override + { + if (!GlobalUpdate(diff)) + return; + + DoVehicleActions(diff); + if (!CanBotAttackOnVehicle()) + return; + + //if (!me->IsInCombat()) + // DoNonCombatActions(diff); + + CheckAura(diff); + + if (IsPotionReady()) + { + if (me->GetPower(POWER_MANA) < CARRION_COST) + DrinkPotion(true); + else if (GetHealthPCT(me) < 50) + DrinkPotion(false); + } + + if (IsCasting()) + return; + + if (IsSpellReady(INFERNO_1, diff) && !botPet && me->IsInCombat() && + me->GetPower(POWER_MANA) >= INFERNAL_COST && Rand() < 60) + { + Unit* target = FindAOETarget(CalcSpellMaxRange(INFERNO_1)); + + if (target) + _infernoPos = target->GetPosition(); + else + me->GetNearPoint(me, _infernoPos.m_positionX, _infernoPos.m_positionY, _infernoPos.m_positionZ, 5.f, 0.f); + + me->CastSpell(_infernoPos, GetSpell(INFERNO_1), false); + return; + } + + if (ProcessImmediateNonAttackTarget()) + return; + + if (!CheckAttackTarget()) + return; + + CheckUsableItems(diff); + + CheckSleep(diff); + + Attack(diff); + } + + void Attack(uint32 diff) + { + Unit* mytar = opponent ? opponent : disttarget ? disttarget : nullptr; + if (!mytar) + return; + + StartAttack(mytar, IsMelee()); + + CheckAttackState(); + if (!me->IsAlive() || !mytar->IsAlive()) + return; + + MoveBehind(mytar); + + if (!HasRole(BOT_ROLE_DPS)) + return; + + if (IsSpellReady(CARRION_SWARM_1, diff) && me->GetPower(POWER_MANA) >= CARRION_COST && Rand() < 80) + { + bool cast = false; + if (me->HasInArc(float(M_PI)/2, mytar) && me->GetDistance(mytar) < 25 && + (IsTank() || GetManaPCT(me) > 60 || me->getAttackers().empty() || GetHealthPCT(me) < 50 || + mytar->HasAura(SLEEP_1))) + { + cast = true; + } + + if (!cast) + { + std::list targets; + GetNearbyTargetsInConeList(targets, 25); //real radius is 30 + if (targets.size() > 1) + { + cast = true; + } + } + + if (cast && doCast(me, GetSpell(CARRION_SWARM_1))) + return; + } + } + + void CheckSleep(uint32 diff) + { + if (!IsSpellReady(SLEEP_1, diff) || IsCasting() || Rand() > 50) + return; + + //fleeing/casting/solo enemy + Unit* u = me->GetVictim(); + if (u && IsSpellReady(CARRION_SWARM_1, diff, false) && !CCed(u) && me->GetDistance(u) < CalcSpellMaxRange(SLEEP_1) && + (u->IsNonMeleeSpellCast(false,false, true) || (u->IsInCombat() && u->getAttackers().size() == 1))) + { + if (doCast(u, GetSpell(SLEEP_1))) + return; + } + + if (Unit* target = FindCastingTarget(CalcSpellMaxRange(SLEEP_1), 0, SLEEP_1)) + { + if (doCast(target, GetSpell(SLEEP_1))) + return; + } + + if (Unit* target = FindStunTarget(CalcSpellMaxRange(SLEEP_1))) + { + if (doCast(target, GetSpell(SLEEP_1))) + return; + } + } + + void ApplyClassDamageMultiplierMelee(uint32& /*damage*/, CalcDamageInfo& damageinfo) const override + { + float pctbonus = 1.0f; + + //150% damage on CCed units + if (CCed(damageinfo.Target)) + pctbonus *= 1.5f; + + damageinfo.Damages[0].Damage = uint32(damageinfo.Damages[0].Damage * pctbonus); + } + + void ApplyClassDamageMultiplierSpell(int32& damage, SpellNonMeleeDamage& damageinfo, SpellInfo const* spellInfo, WeaponAttackType /*attackType*/, bool iscrit) const override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //uint8 lvl = me->GetLevel(); + float fdamage = float(damage); + + //apply bonus damage mods + float pctbonus = 1.0f; + if (iscrit) + pctbonus *= 1.333f; + + //double damage on CCed units + if (CCed(damageinfo.target)) + pctbonus *= 2.f; + + if (baseId == CARRION_SWARM_1) + fdamage += me->SpellBaseDamageBonusDone(SPELL_SCHOOL_MASK_MAGIC) * (spellInfo->_effects[0].BonusMultiplier - 1.f) * me->CalculateDefaultCoefficient(spellInfo, SPELL_DIRECT_DAMAGE) * me->CalculateSpellpowerCoefficientLevelPenalty(spellInfo); + + damage = int32(fdamage * pctbonus); + } + + void OnClassSpellGo(SpellInfo const* spellInfo) override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + + if (baseId == CARRION_SWARM_1) + { + me->resetAttackTimer(); + me->CastSpell(me, MH_ATTACK_ANIM, true); + } + + if (baseId == INFERNO_1) + { + me->CastSpell(_infernoPos, SPELL_INFERNO_METEOR_VISUAL, true); + DelayedPetSpawnEvent* spawnEvent = new DelayedPetSpawnEvent(me, &_infernoPos); + Events.AddEvent(spawnEvent, Events.CalculateTime(std::chrono::milliseconds(INFERNO_SPAWN_DELAY))); + } + } + + void SpellHitTarget(WorldObject* wtarget, SpellInfo const* spell) override + { + Unit* target = wtarget->ToUnit(); + if (!target) + return; + + OnSpellHitTarget(target, spell); + } + + void SpellHit(WorldObject* wcaster, SpellInfo const* spell) override + { + Unit* caster = wcaster->ToUnit(); + if (!caster) + return; + + OnSpellHit(caster, spell); + } + + void OnBotDamageDealt(Unit* victim, uint32 damage, CleanDamage const* /*cleanDamage*/, DamageEffectType /*damagetype*/, SpellInfo const* spellInfo) override + { + //Carrion swarm heal + if (damage && victim != me && spellInfo && spellInfo->GetFirstRankSpell()->Id == CARRION_SWARM_1) + { + int32 basepoints0 = std::min(damage, victim->GetHealth()); + //TC_LOG_ERROR("entities.unit", "OnBotDamageDealt(drl): {} on {} base val {} ({}),", + // me->GetName(), victim->GetName(), int32(damage), spellInfo->SpellName[0]); + CastSpellExtraArgs args(true); + args.AddSpellBP0(basepoints0); + me->CastSpell(me, SPELL_TRIGGERED_HEAL, args); + } + } + + void DamageDealt(Unit* victim, uint32& damage, DamageEffectType damageType) override + { + bot_ai::DamageDealt(victim, damage, damageType); + } + + void DamageTaken(Unit* u, uint32& damage, DamageEffectType /*damageType*/, SpellInfo const* /*spellInfo*/) override + { + if (damage) + { + BotSpellMap const& spells = GetSpellMap(); + for (BotSpellMap::const_iterator itr = spells.begin(); itr != spells.end(); ++itr) + { + //not affected if pet is alive + if (botPet && itr->first == INFERNO_1) + continue; + + uint32& cooldown = itr->second->cooldown; + if (!cooldown) + continue; + + cooldown = cooldown > DAMAGE_CD_REDUCTION ? cooldown - DAMAGE_CD_REDUCTION : 0; + } + } + + if (!u) + return; + if (!u->IsInCombat() && !me->IsInCombat()) + return; + OnOwnerDamagedBy(u); + } + + void OwnerAttackedBy(Unit* u) override + { + if (!u) + return; + OnOwnerDamagedBy(u); + } + + void SummonBotPet(Position const* sPos) + { + if (botPet) + UnsummonAll(false); + + uint32 entry = BOT_PET_INFERNAL; + + //Position pos; + + Creature* myPet = me->SummonCreature(entry, *sPos, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 2s); + //me->GetNearPoint(myPet, pos.m_positionX, pos.m_positionY, pos.m_positionZ, 0, 2, me->GetOrientation()); + //myPet->GetMotionMaster()->MovePoint(me->GetMapId(), pos); + myPet->SetCreator(master); + myPet->SetOwnerGUID(me->GetGUID()); + myPet->SetFaction(master->GetFaction()); + myPet->SetControlledByPlayer(!IAmFree()); + myPet->SetPvP(me->IsPvP()); + myPet->SetByteValue(UNIT_FIELD_BYTES_2, 1, master->GetByteValue(UNIT_FIELD_BYTES_2, 1)); + + //immune + myPet->SetUInt32Value(UNIT_CREATED_BY_SPELL, INFERNO_1); + //dreadlord immunities + myPet->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_SHAPESHIFT, true); + myPet->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_TRANSFORM, true); + myPet->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_FEAR, true); + myPet->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_POLYMORPH, true); + myPet->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_FEAR, true); + myPet->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_HORROR, true); + myPet->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_TURN, true); + myPet->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_SLEEP, true); + myPet->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_SNARE, true); + + //myPet->SetMeleeDamageSchool(SPELL_SCHOOL_FIRE); + + //infernal is immune to magic + //myPet->ApplySpellImmune(0, IMMUNITY_DAMAGE, SPELL_SCHOOL_MASK_MAGIC, true); + myPet->CastSpell(myPet, SPELL_INFERNO_EFFECT, true); //damage, stun + //myPet->CastSpell(myPet, SPELL_INFERNO_IMPACT_EXPLOSION, true); //visual + myPet->CastSpell(myPet, IMMOLATION, true); + + botPet = myPet; + } + + void UnsummonAll(bool savePets = true) override + { + UnsummonPet(savePets); + } + + void SummonedCreatureDies(Creature* /*summon*/, Unit* /*killer*/) override + { + } + + void SummonedCreatureDespawn(Creature* summon) override + { + //TC_LOG_ERROR("entities.unit", "SummonedCreatureDespawn: {}'s {}", me->GetName(), summon->GetName()); + if (summon == botPet) + botPet = nullptr; + } + + uint32 GetAIMiscValue(uint32 data) const override + { + switch (data) + { + case BOTAI_MISC_PET_TYPE: + return BOT_PET_INFERNAL; + default: + return 0; + } + } + + void Reset() override + { + checkAuraTimer = 0; + + DefaultInit(); + } + + void ReduceCD(uint32 diff) override + { + if (checkAuraTimer > diff) checkAuraTimer -= diff; + } + + void InitPowers() override + { + me->SetPowerType(POWER_MANA); + } + + void InitSpells() override + { + InitSpellMap(CARRION_SWARM_1, true, false); + InitSpellMap(SLEEP_1, true, false); + InitSpellMap(INFERNO_1); + } + + void ApplyClassPassives() const override + { + } + + bool CanUseManually(uint32 basespell) const override + { + switch (basespell) + { + case CARRION_SWARM_1: + return true; + default: + return false; + } + } + + std::vector const* GetDamagingSpellsList() const override + { + return &Dreadlord_spells_damage; + } + std::vector const* GetCCSpellsList() const override + { + return &Dreadlord_spells_cc; + } + //std::vector const* GetHealingSpellsList() const override + //{ + // return &Dreadlord_spells_heal; + //} + std::vector const* GetSupportSpellsList() const override + { + return &Dreadlord_spells_support; + } + + private: + uint32 checkAuraTimer; + Position _infernoPos; + }; +}; + +void AddSC_dreadlord_bot() +{ + new dreadlord_bot(); +} diff --git a/src/server/game/AI/NpcBots/bot_druid_ai.cpp b/src/server/game/AI/NpcBots/bot_druid_ai.cpp new file mode 100644 index 000000000..08f5ce2bf --- /dev/null +++ b/src/server/game/AI/NpcBots/bot_druid_ai.cpp @@ -0,0 +1,2934 @@ +#include "bot_ai.h" +#include "botmgr.h" +#include "bottext.h" +#include "bottraits.h" +#include "Containers.h" +#include "Group.h" +#include "Log.h" +#include "Map.h" +#include "MotionMaster.h" +#include "ObjectAccessor.h" +#include "Player.h" +#include "ScriptMgr.h" +#include "Spell.h" +#include "SpellAuraEffects.h" +#include "SpellMgr.h" +#include "TemporarySummon.h" +#include "World.h" +/* +Druid NpcBot (reworked by Trickerer onlysuffering@gmail.com) +Complete - 85-90% +TODO: Resolve remaining bugs with wrong power type after death +TODO2: PvP behaviour revamp (again, it's like 5th time?) +*/ + +#define MAX_TREANTS 3 + +enum DruidBaseSpells +{ + MARK_OF_THE_WILD_1 = 1126, + THORNS_1 = 467, + HEALING_TOUCH_1 = 5185, + REGROWTH_1 = 8936, + REJUVENATION_1 = 774, + LIFEBLOOM_1 = 33763, + NOURISH_1 = 50464, + WILD_GROWTH_1 = 48438, + SWIFTMEND_1 = 18562, + TRANQUILITY_1 = 740, + REVIVE_1 = 50769, + REBIRTH_1 = 20484, + BEAR_FORM_1 = 5487, + SWIPE_BEAR_1 = 779, + MANGLE_BEAR_1 = 33878, + BASH_1 = 5211, + MAUL_1 = 6807, + FERAL_CHARGE_BEAR_1 = 16979, + CHALLENGING_ROAR_1 = 5209, + ENRAGE_1 = 5229, + FRENZIED_REGENERATION_1 = 22842, + GROWL_1 = 6795, + LACERATE_1 = 33745, + SURVIVAL_INSTINCTS_1 = 61336, + FAERIE_FIRE_FERAL_1 = 16857,//chains threat and damage spell regardless of bot feral form + BERSERK_1 = 50334, + CAT_FORM_1 = 768, + CLAW_1 = 1082, + RAKE_1 = 1822, + SHRED_1 = 5221, + MANGLE_CAT_1 = 33876, + RIP_1 = 1079, + FEROCIOUS_BITE_1 = 22568, + POUNCE_1 = 9005, + RAVAGE_1 = 6785, + MAIM_1 = 22570, + SWIPE_CAT_1 = 62078, + SAVAGE_ROAR_1 = 52610, + FERAL_CHARGE_CAT_1 = 49376, + COWER_1 = 8998, + DASH_1 = 1850, + TIGERS_FURY_1 = 5217, + PROWL_1 = 5215, + MOONFIRE_1 = 8921, + STARFIRE_1 = 2912, + WRATH_1 = 5176, + HURRICANE_1 = 16914, + FAERIE_FIRE_NORMAL_1 = 770, + INSECT_SWARM_1 = 5570, + TYPHOON_1 = 50516, + STARFALL_1 = 48505, + MOONKIN_FORM_1 = 24858, + TREE_OF_LIFE_FORM_1 = 33891, + TRAVEL_FORM_1 = 783, + AQUATIC_FORM_1 = 1066, + //FLIGHT_FORM_1 = 0,//niy + ABOLISH_POISON_1 = 2893,//manual use only + CURE_POISON_1 = 8946, + REMOVE_CURSE_1 = 2782, + ENTANGLING_ROOTS_1 = 339, + CYCLONE_1 = 33786, + HIBERNATE_1 = 2637, + BARKSKIN_1 = 22812, + NATURES_GRASP_1 = 16689, + INNERVATE_1 = 29166, + NATURES_SWIFTNESS_1 = 17116 +}; +enum DruidPassives +{ +//Talents + OMEN_OF_CLARITY = 16864,//clearcast + NATURESGRACE = 61346,//rank 3 + NATURAL_PERFECTION1 = 33881, + NATURAL_PERFECTION2 = 33882, + NATURAL_PERFECTION3 = 33883, + LIVING_SEED1 = 48496, + LIVING_SEED2 = 48499, + LIVING_SEED3 = 48500, + REVITALIZE1 = 48539, + REVITALIZE2 = 48544, + REVITALIZE3 = 48545, + NATURALIST = 17073,//rank 5 + IMPROVED_MARK_OF_THE_WILD = 17051,//rank 2 + FUROR = 17061,//rank 5 + INTENSITY = 17108,//rank 3 + LIVING_SPIRIT = 34153,//rank 3 + GIFT_OF_THE_EARTHMOTHER = 51183,//rank 5 + ECLIPSE = 48525,//rank 3 + EARTH_AND_MOON = 48511,//rank 3 + SURVIVAL_OF_THE_FITTEST = 33856,//rank 3 + DREAMSTATE = 33956,//rank 3 + BALANCE_OF_POWER = 33596,//rank 2 + IMPROVED_MOONKIN_FORM = 48396,//rank 3 + OWLKIN_FRENZY = 48393,//rank 3 NOT REFRESHAURABLE + FERAL_SWIFTNESS = 24866,//rank 2 NOT REFRESHAURABLE + PRIMAL_PRECISION = 48410,//rank 2 expertise only, refund handled in Spell.cpp + NATURAL_REACTION = 57881,//rank 3 NOT REFRESHAURABLE + IMPROVED_LEADER_OF_THE_PACK = 34300,//rank 2 + PRIMAL_TENACITY = 33957,//rank 3 + PREDATORY_INSTINCTS = 33867,//rank 3 NOT REFRESHAURABLE + KING_OF_THE_JUNGLE = 48495,//rank 3 + PRIMAL_GORE = 63503,//rank 1 +//Glyphs + GLYPH_NOURISH = 62971, + GLYPH_SWIFTMEND = 54824,//no consumption + GLYPH_INNERVATE = 54832,//self regen + GLYPH_RAPID_REJUVENATION = 71013, + GLYPH_REGROWTH = 54743, + GLYPH_REJUVENATION = 54754, + GLYPH_FRENZIED_REGENERATION = 54810, + GLYPH_BARKSKIN = 63057, + GLYPH_RAKE = 54821, + GLYPH_SHRED = 54815, +//other + T10_RESTO_P4_BONUS = 70664,//rejuve jump + T9_RESTO_P4_BONUS = 67128,//rejuve crits + T8_RESTO_P4_BONUS = 64760,//rejuve init heal + + T8_BALANCE_P4_BONUS = 64824,//insect swarm periodic starfire instacast trigger + T9_BALANCE_P2_BONUS = 67125,//moonfire crits + T10_BALANCE_P2_BONUS = 70718,//omen of doom (15%) + T10_BALANCE_P4_BONUS = 70723,//Languish(DOT) + + T10_FERAL_P4_BONUS = 70726,//rake crit, enraged defense + T8_FERAL_P2_BONUS = 64752,//periodic clearcast trigger +}; +enum DruidSpecial +{ + STARFALL_DAMAGE_AOE_4 = 53190,//for radius mods + //STARFALL_DAMAGE_DIRECT_4 = 53195, + STARFALL_DUMMY_AOE_4 = 53198,//for radius mods + + HURRICANE_DAMAGE_1 = 42231, + //TRANQUILITY_HEAL_1 = 44203, + //TYPHOON_DAMAGE_1 = 61391, + + SAVAGE_ROAR_BUFF = 62071,//hidden buff + PREDATORS_SWIFTNESS_BUFF = 69369, + LEADER_OF_THE_PACK_BUFF = 24932, + NURTURING_INSTINCT_BUFF = 47180,//rank 2 hidden NOT REFRESHAURABLE + SURVIVAL_OF_THE_FITTEST_BUFF = 62069,//hidden buff + SAVAGE_DEFENSE_PASSIVE = 62600,//class passive lvl 40 + SAVAGE_DEFENSE_BUFF = 62606, + MASTER_SHAPESHIFTER_BEAR_BUFF = 48418, + MASTER_SHAPESHIFTER_CAT_BUFF = 48420, + MASTER_SHAPESHIFTER_MOONKIN_BUFF = 48421, + MASTER_SHAPESHIFTER_TREE_BUFF = 48422, + //NATURESGRACEBUFF = 16886, + ECLIPSE_SOLAR_BUFF = 48517,// from Starfire to Wrath + ECLIPSE_LUNAR_BUFF = 48518,// from Wrath to Starfire + ELUNES_WRATH_BUFF = 64823,//Starfire instacast + OMEN_OF_CLARITY_BUFF = 16870, + + //FERAL_CHARGE_EFFECT_BEAR_ROOT = 45334, + //FERAL_CHARGE_EFFECT_CAT_DAZE = 50259, + INFECTED_WOUNDS_EFFECT = 58181,//rank 3 + PRIMAL_FURY_EFFECT_ENERGIZE = 16959,//5 rage + + FORCE_OF_NATURE_1 = 33831 //not casted +}; + +static const uint32 Druid_spells_damage_arr[] = +{ FAERIE_FIRE_FERAL_1, CLAW_1, FEROCIOUS_BITE_1, MAIM_1, MANGLE_CAT_1, POUNCE_1, RAKE_1, RAVAGE_1, RIP_1, SHRED_1, +SWIPE_CAT_1, LACERATE_1, MANGLE_BEAR_1, MAUL_1,SWIPE_BEAR_1, ENTANGLING_ROOTS_1, HURRICANE_1, INSECT_SWARM_1, +WRATH_1, MOONFIRE_1, STARFALL_1, STARFIRE_1, TYPHOON_1, THORNS_1 }; + +static const uint32 Druid_spells_cc_arr[] = +{ BASH_1, CYCLONE_1, ENTANGLING_ROOTS_1, FERAL_CHARGE_BEAR_1, HIBERNATE_1, MAIM_1, POUNCE_1, TYPHOON_1 }; + +static const uint32 Druid_spells_heal_arr[] = +{ HEALING_TOUCH_1, LIFEBLOOM_1, NOURISH_1, REGROWTH_1, REJUVENATION_1, SWIFTMEND_1, TRANQUILITY_1, WILD_GROWTH_1 }; + +static const uint32 Druid_spells_support_arr[] = +{ ABOLISH_POISON_1, BARKSKIN_1, BERSERK_1, CHALLENGING_ROAR_1, COWER_1, CURE_POISON_1, DASH_1, ENRAGE_1, +FAERIE_FIRE_NORMAL_1, FAERIE_FIRE_FERAL_1, FERAL_CHARGE_BEAR_1, FERAL_CHARGE_CAT_1, FRENZIED_REGENERATION_1, +GROWL_1, INNERVATE_1, MARK_OF_THE_WILD_1, NATURES_GRASP_1, NATURES_SWIFTNESS_1, PROWL_1, REMOVE_CURSE_1, +REBIRTH_1, REVIVE_1, SAVAGE_ROAR_1, SURVIVAL_INSTINCTS_1, THORNS_1, TIGERS_FURY_1 }; + +static const std::vector Druid_spells_damage(FROM_ARRAY(Druid_spells_damage_arr)); +static const std::vector Druid_spells_cc(FROM_ARRAY(Druid_spells_cc_arr)); +static const std::vector Druid_spells_heal(FROM_ARRAY(Druid_spells_heal_arr)); +static const std::vector Druid_spells_support(FROM_ARRAY(Druid_spells_support_arr)); + +static float rageLossMult; + +class druid_bot : public CreatureScript +{ +public: + druid_bot() : CreatureScript("druid_bot") { } + + CreatureAI* GetAI(Creature* creature) const override + { + return new bot_druid_ai(creature); + } +/* + bool OnGossipHello(Player* player, Creature* creature) + { + return creature->GetBotAI()->OnGossipHello(player, 0); + } + + bool OnGossipSelect(Player* player, Creature* creature, uint32 sender, uint32 action) + { + if (bot_ai* ai = creature->GetBotAI()) + return ai->OnGossipSelect(player, creature, sender, action); + return true; + } + + bool OnGossipSelectCode(Player* player, Creature* creature, uint32 sender, uint32 action, char const* code) + { + if (bot_ai* ai = creature->GetBotAI()) + return ai->OnGossipSelectCode(player, creature, sender, action, code); + return true; + } +*/ + struct bot_druid_ai : public bot_ai + { + bot_druid_ai(Creature* creature) : bot_ai(creature) + { + _botclass = BOT_CLASS_DRUID; + + InitUnitFlags(); + } + + bool doCast(Unit* victim, uint32 spellId) + { + if (CheckBotCast(victim, spellId) != SPELL_CAST_OK) + return false; + return bot_ai::doCast(victim, spellId); + } + + void JustEnteredCombat(Unit* u) override { bot_ai::JustEnteredCombat(u); } + void KilledUnit(Unit* u) override { bot_ai::KilledUnit(u); } + void EnterEvadeMode(EvadeReason why = EVADE_REASON_OTHER) override { bot_ai::EnterEvadeMode(why); } + void MoveInLineOfSight(Unit* u) override { bot_ai::MoveInLineOfSight(u); } + void JustDied(Unit* u) override { removeShapeshiftForm(); UnsummonAll(false); bot_ai::JustDied(u); } + + uint8 GetBotStance() const override + { + return _form; + } + + bool removeShapeshiftForm() override + { + BotStances myform = _form; + _form = BOT_STANCE_NONE; + //ShapeshiftForm form = me->GetShapeshiftForm(); + //if (form != FORM_NONE) + { + switch (myform/*form*/) + { + //case FORM_DIREBEAR: + //case FORM_BEAR: + case DRUID_BEAR_FORM: + if (IsRegenActive()) + return false; + me->RemoveAurasDueToSpell(GetSpell(BEAR_FORM_1)); + me->RemoveAurasDueToSpell(MASTER_SHAPESHIFTER_BEAR_BUFF); + me->RemoveAurasDueToSpell(NATURAL_REACTION); + me->RemoveAurasDueToSpell(SURVIVAL_OF_THE_FITTEST_BUFF); + me->RemoveAurasDueToSpell(SAVAGE_DEFENSE_PASSIVE); + break; + //case FORM_CAT: + case DRUID_CAT_FORM: + me->RemoveAurasDueToSpell(GetSpell(CAT_FORM_1)); + me->RemoveAurasDueToSpell(FERAL_SWIFTNESS); + me->RemoveAurasDueToSpell(MASTER_SHAPESHIFTER_CAT_BUFF); + me->RemoveAurasDueToSpell(NURTURING_INSTINCT_BUFF); + me->RemoveAurasDueToSpell(PREDATORY_INSTINCTS); + break; + //case FORM_MOONKIN: + case DRUID_MOONKIN_FORM: + me->RemoveAurasDueToSpell(GetSpell(MOONKIN_FORM_1)); + me->RemoveAurasDueToSpell(GetSpell(OWLKIN_FRENZY)); + me->RemoveAurasDueToSpell(MASTER_SHAPESHIFTER_MOONKIN_BUFF); + break; + //case FORM_TREE: + case DRUID_TREE_FORM: + me->RemoveAurasDueToSpell(GetSpell(TREE_OF_LIFE_FORM_1)); + me->RemoveAurasDueToSpell(MASTER_SHAPESHIFTER_TREE_BUFF); + break; + //case FORM_TRAVEL: + case DRUID_TRAVEL_FORM: + me->RemoveAurasDueToSpell(GetSpell(TRAVEL_FORM_1)); + break; + //case FORM_AQUA: + case DRUID_AQUATIC_FORM: + me->RemoveAurasDueToSpell(GetSpell(AQUATIC_FORM_1)); + break; + //case FORM_FLIGHT: + //case FORM_FLIGHT_EPIC: + default: + break; + } + + if (me->GetPowerType() != POWER_MANA) + { + //TC_LOG_ERROR("entities.player", "druid_bot::removeShapeshiftForm(): still has poweType {}!", uint32(me->GetPowerType())); + me->SetPowerType(POWER_MANA); + } + if (me->GetShapeshiftForm() != FORM_NONE) + { + //TC_LOG_ERROR("entities.player", "druid_bot::removeShapeshiftForm(): still speshifted into {}!", uint32(me->GetShapeshiftForm())); + me->RemoveAurasByType(SPELL_AURA_MOD_SHAPESHIFT, me->GetGUID(), nullptr, false); + } + + setStats(BOT_STANCE_NONE); + } + return true; + } + + //bool IsMelee() const + //{ + // return bot_ai::IsMelee() && (_form == DRUID_BEAR_FORM || _form == DRUID_CAT_FORM); + //} + + void StartAttack(Unit* u, bool force = false) + { + if (!bot_ai::StartAttack(u, force)) + return; + if (_form == DRUID_BEAR_FORM && HasRole(BOT_ROLE_RANGED) && IsSpellReady(BASH_1, GetLastDiff(), false)) + return; + GetInPosition(force, u); + } + + bool MassGroupHeal(Player* gPlayer, uint32 diff) + { + if (!HasRole(BOT_ROLE_HEAL)) return false; + if (!gPlayer || GC_Timer > diff || IAmFree()) return false; + if (IsCasting()) return false; // if I'm already casting + if (Rand() > 30 + 50 * (me->GetMap()->IsRaid())) return false; + if (!gPlayer->GetGroup()) return false; + + bool tranq = IsSpellReady(TRANQUILITY_1, diff, false); + bool growt = IsSpellReady(WILD_GROWTH_1, diff, false) && !HasRole(BOT_ROLE_DPS); + if (!tranq && !growt) + return false; + + uint8 LHPcount = 0; + uint8 pct = 100; + Unit* healTarget = nullptr; + std::vector members = BotMgr::GetAllGroupMembers(master); + std::vector groupUnits; + groupUnits.reserve(members.size()); + + for (Unit* member : members) + { + if (me->GetMap() != member->FindMap() || member->isPossessed() || member->IsCharmed() || + !member->IsAlive() || me->GetDistance(member) > 40) + continue; + if (growt) + groupUnits.push_back(member); + if (tranq && GetHealthPCT(member) < 80) + { + if (GetHealthPCT(member) < pct) + { + pct = GetHealthPCT(member); + healTarget = member; + } + ++LHPcount; + if (LHPcount > 2) + break; + } + } + + if (LHPcount > 2 && tranq && + doCast(me, GetSpell(TRANQUILITY_1))) + return true; + + healTarget = nullptr; + for (Unit* gUnit : groupUnits) + { + LHPcount = 0; + for (Unit* member : members) + { + if (me->GetMap() != member->FindMap() || member->isPossessed() || member->IsCharmed() || + !member->IsAlive() || me->GetDistance(member) > 40) + continue; + if (gUnit->GetDistance(member) < 15 && (GetLostHP(member) > 2000 || GetHealthPCT(member) < 90)) + if (++LHPcount >= 3) + break; + } + + if (LHPcount >= 3) + { + healTarget = gUnit; + break; + } + } + + if (LHPcount >= 3 && growt && healTarget && + doCast(healTarget, GetSpell(WILD_GROWTH_1))) + return true; + + return false; + } + + //Powers + //rage + void getrage() + { + rage = me->GetPower(POWER_RAGE); + if (me->FindCurrentSpellBySpellId(GetSpell(MAUL_1))) + rage = std::max(rage - 150, 0); + } + + //energy + void getenergy() + { + energy = me->GetPower(POWER_ENERGY); + } + + //all + int32 acost(uint32 spellId) const + { + if (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId)) + return spellInfo->CalcPowerCost(me, spellInfo->GetSchoolMask()); + return 0; + } + + bool IsRegenActive() const + { + return me->IsAlive() && me->IsInCombat() && rage > 100 && GetHealthPCT(me) < 90 && + me->GetAuraEffect(SPELL_AURA_PERIODIC_DUMMY, SPELLFAMILY_DRUID, 0x0, 0x40000000, 0x0); + } + + void CheckBarkskin(uint32 diff) + { + if (!me->IsAlive()) + return; + if (me->GetVehicle()) + return; + //No GCD + if (IsSpellReady(BARKSKIN_1, diff, false) && !IsCasting() && !me->getAttackers().empty() && + Rand() < (25 + 20 * me->getAttackers().size()) && + GetHealthPCT(me) < (IsTank() ? 67 : 70 + 25 * me->getAttackers().size())) + { + if (doCast(me, GetSpell(BARKSKIN_1))) + {} + } + } + + void CheckHibery(uint32 diff) + { + if (hiberyCheckTimer <= diff) + { + hibery = FindAffectedTarget(GetSpell(HIBERNATE_1), me->GetGUID()); + hiberyCheckTimer = 2000; + } + } + + void CheckHibernate(uint32 diff) + { + if (!(_form == DRUID_MOONKIN_FORM || _form == BOT_STANCE_NONE)) + return; + //Skip Tranquility, Hurricane + if (GC_Timer > diff || IsCasting() || Rand() > 35) + return; + + if (hibery == false && IsSpellReady(HIBERNATE_1, diff)) + { + if (Unit* target = FindStunTarget(30)) + { + if (doCast(target, GetSpell(HIBERNATE_1))) + return; + } + } + } + + void Counter(uint32 diff) + { + if (!(_form == DRUID_MOONKIN_FORM || _form == BOT_STANCE_NONE)) + return; + //Skip Tranquility, Hurricane + if (GC_Timer > diff || Rand() > 35 || IsChanneling() || (HasRole(BOT_ROLE_HEAL) && IsCasting())) + return; + + if (IsSpellReady(CYCLONE_1, diff)) + { + if (Unit* target = FindCastingTarget(20, 0, CYCLONE_1)) + { + bool cast = false; + for (uint8 i = CURRENT_GENERIC_SPELL; i != CURRENT_AUTOREPEAT_SPELL; ++i) + { + Spell const* spell = target->GetCurrentSpell(CurrentSpellTypes(i)); + if (spell && spell->GetTimer() > 1500 && + (IAmFree() ? (spell->m_targets.GetUnitTarget() == me) : (master->GetGroup() && master->GetGroup()->IsMember(spell->m_targets.GetObjectTargetGUID())))) + { + cast = true; + break; + } + } + if (cast) + { + me->InterruptNonMeleeSpells(false); + if (doCast(target, GetSpell(CYCLONE_1))) + return; + } + } + } + } + + void UpdateAI(uint32 diff) override + { + if (me->GetPowerType() == POWER_RAGE && me->IsAlive()) + { + if (ragetimer <= diff) + { + if (!me->IsInCombat() && + !me->HasAuraTypeWithFamilyFlags(SPELL_AURA_PERIODIC_ENERGIZE, SPELLFAMILY_DRUID, 0x00080000) + /*!HasAuraName(me, ENRAGE_1)*/) + { + if (me->GetPower(POWER_RAGE) > uint32(10.f * rageLossMult)) + me->SetPower(POWER_RAGE, me->GetPower(POWER_RAGE) - uint32(10.f * rageLossMult)); //-1 rage per 1.5 sec + else + me->SetPower(POWER_RAGE, 0); + } + ragetimer = 1500; + } + getrage(); + } + else if (me->GetPowerType() == POWER_ENERGY) + getenergy(); + + if (!GlobalUpdate(diff)) + return; + + DoVehicleActions(diff); + if (!CanBotAttackOnVehicle()) + return; + + CheckHibery(diff); + CheckBarkskin(diff); + + if (IsPotionReady()) + { + if (me->GetPowerType() == POWER_MANA && GetManaPCT(me) < 33) + DrinkPotion(true); + else if (GetHealthPCT(me) < 35) + DrinkPotion(false); + } + + CheckRacials(diff); + + //Innervate + doInnervate(diff); + + MassGroupHeal(master, diff); + if (me->IsInCombat()) + CheckBattleRez(diff); + else + DoNonCombatActions(diff); + + if (HasRole(BOT_ROLE_RANGED) || !me->IsInCombat() || !me->GetVictim() || + (_form != DRUID_BEAR_FORM && (_form != DRUID_CAT_FORM || !me->GetMap()->IsRaid()))) + BuffAndHealGroup(diff); + if (_form != DRUID_BEAR_FORM && _form != DRUID_CAT_FORM) + { + CureGroup(GetSpell(CURE_POISON_1), diff); + CureGroup(GetSpell(REMOVE_CURSE_1), diff); + } + + if (ProcessImmediateNonAttackTarget()) + return; + + CheckTravel(diff); + + if (!CheckAttackTarget()) + { + if (!me->IsInCombat() && Rand() < 5 && me->HasAuraType(SPELL_AURA_MOD_STEALTH)) + me->RemoveAurasDueToSpell(PROWL_1); + return; + } + + CheckHibernate(diff); + Counter(diff); + + if (IsCasting()) + return; + + CheckUsableItems(diff); + + Attack(diff); + } + + void Attack(uint32 diff) + { + Unit* mytar = opponent ? opponent : disttarget ? disttarget : nullptr; + if (!mytar) + return; + + StartAttack(mytar, bot_ai::IsMelee()); + + CheckAttackState(); + if (!me->IsAlive() || !mytar->IsAlive()) + return; + + //NOT all forms abilities (prioritized) + //Cat Instaheal + if (_form == DRUID_CAT_FORM && GC_Timer <= diff && Rand() < 60 && + HasRole(BOT_ROLE_HEAL) && GetHealthPCT(me) < 45 && + (me->GetAuraEffect(SPELL_AURA_ADD_PCT_MODIFIER, SPELLFAMILY_DRUID, 0x0, 0x80000, 0x0) ||/*me->HasAura(PREDATORS_SWIFTNESS_BUFF)*/ + (IsSpellReady(NATURES_SWIFTNESS_1, diff, false) && doCast(me, GetSpell(NATURES_SWIFTNESS_1))))) + { + //TODO maybe istant spells if clearcast? + //heal myself with instant + //Healing Touch has same reqs + if ((GetSpell(REGROWTH_1) && !me->GetAuraEffect(SPELL_AURA_PERIODIC_HEAL, SPELLFAMILY_DRUID, 0x40, 0x0, 0x0) && + doCast(me, GetSpell(REGROWTH_1))) || doCast(me, GetSpell(HEALING_TOUCH_1))) + { + Position pos; + mytar->GetNearPoint(me, pos.m_positionX, pos.m_positionY, pos.m_positionZ, 15.f, mytar->GetAbsoluteAngle(me)); + GetInPosition(true, mytar, &pos); + return; + } + } + //Roots + if (_form != DRUID_BEAR_FORM && _form != DRUID_TREE_FORM && Rand() < 35 && + (HasRole(BOT_ROLE_DPS) || IAmFree()) && IsSpellReady(ENTANGLING_ROOTS_1, diff) && + (_form != DRUID_CAT_FORM || IAmFree() || me->GetAuraEffect(SPELL_AURA_ADD_PCT_MODIFIER, SPELLFAMILY_DRUID, 0x0, 0x80000, 0x0) + /*me->HasAura(PREDATORS_SWIFTNESS_BUFF)*/)) + CheckRoots(); + + //ALL forms abilities + //Nature's Grasp (no shapeshift) + if (IsSpellReady(NATURES_GRASP_1, diff) && HasRole(BOT_ROLE_DPS) && HasRole(BOT_ROLE_RANGED) && Rand() < 70 && + !me->getAttackers().empty()) + { + if (doCast(me, GetSpell(NATURES_GRASP_1))) + return; + } + //Survival Instincts + //No GCD, bear is lvl 10, SI is lvl 20 + //Shapeshift into bear if needed + if (IsSpellReady(SURVIVAL_INSTINCTS_1, diff, false) && Rand() < 75 && + (GetHealthPCT(me) < (30 + 20 * (me->getAttackers().size() > 1))) && + (_form == DRUID_BEAR_FORM || (GC_Timer <= diff && doCast(me, GetSpell(BEAR_FORM_1))))) + { + if (doCast(me, SURVIVAL_INSTINCTS_1)) + if (!IAmFree()) + ReportSpellCast(SURVIVAL_INSTINCTS_1, LocalizedNpcText(master, BOT_TEXT__USED), master); + } + //Bash + //Assuming Furor is present which is lvl 10 + //Shapeshift into bear if needed + //bear is lvl 10, bash is lvl 14 + //Retreat is triggered only if hit (SpellHitTarget) + if (IsSpellReady(BASH_1, diff) && !CCed(mytar, !mytar->IsNonMeleeSpellCast(false,false,true)) && + mytar->IsWithinMeleeRange(me)) + { + if ((_form == DRUID_BEAR_FORM && rage >= acost(BASH_1)) || + (IsSpellReady(BEAR_FORM_1, diff, false) && doCast(me, GetSpell(BEAR_FORM_1)))) + { + if (doCast(mytar, GetSpell(BASH_1))) + return; + } + } + + //Main mode + //Choose form. Mode should be selected considering bot_ai::CheckAttackTarget() positioning selection + //1 Tanking mode + if ((IsTank() || (IsWanderer() && bot_ai::IsMelee() && !GetSpell(CAT_FORM_1))) && GetSpell(BEAR_FORM_1)) + { + if (_form == DRUID_BEAR_FORM || (IsSpellReady(BEAR_FORM_1, diff, false) && doCast(me, GetSpell(BEAR_FORM_1)))) + doBearActions(mytar, diff); + } + //2 Melee (tanking cat impossible: cat lvl 20, bear lvl 10) + else if (bot_ai::IsMelee()) + { + //if lvl < 20 then bot gonna just melee its targets + if (_form == DRUID_CAT_FORM || (IsSpellReady(CAT_FORM_1, diff, false) && doCast(me, GetSpell(CAT_FORM_1)))) + doCatActions(mytar, diff); + } + //3 Ranged dps + else if (HasRole(BOT_ROLE_DPS)) + { + //pure dps goes moonkin + if (_form == DRUID_MOONKIN_FORM || + ((!GetSpell(MOONKIN_FORM_1) || HasRole(BOT_ROLE_HEAL)) && (_form == BOT_STANCE_NONE || removeShapeshiftForm())) || + (!HasRole(BOT_ROLE_HEAL) && IsSpellReady(MOONKIN_FORM_1, diff, false) && doCast(me, GetSpell(MOONKIN_FORM_1)))) + doBalanceActions(mytar, diff); + } + //4 Healer + else if (HasRole(BOT_ROLE_HEAL)) + { + //pure healer goes tree + if (_form == DRUID_TREE_FORM || + ((!GetSpell(TREE_OF_LIFE_FORM_1) || HasRole(BOT_ROLE_DPS)) && (_form == BOT_STANCE_NONE || removeShapeshiftForm())) || + (!HasRole(BOT_ROLE_DPS) && IsSpellReady(TREE_OF_LIFE_FORM_1, diff) && doCast(me, GetSpell(TREE_OF_LIFE_FORM_1)))) + {/*do nothing*/} //not a mistake + } + } + + void doBearActions(Unit* mytar, uint32 diff) + { + //debug + if (me->GetPowerType() != POWER_RAGE || (me->GetShapeshiftForm() != FORM_BEAR && me->GetShapeshiftForm() != FORM_DIREBEAR)) + return; + + //Enrage + if (IsSpellReady(ENRAGE_1, diff, false) && me->IsInCombat() && (rage < 400 || IsTank()) && Rand() < 40) + { + if (doCast(me, GetSpell(ENRAGE_1))) + getrage(); + } + //Frenzied Regeneration + if (IsSpellReady(FRENZIED_REGENERATION_1, diff) && rage > 700 && GetHealthPCT(me) < 70 && Rand() < 40) + { + if (doCast(me, GetSpell(FRENZIED_REGENERATION_1))) + return; + } + + float dist = me->GetDistance(mytar); + //GROWL //No GCD + Unit* u = mytar->GetVictim(); + if (IsSpellReady(GROWL_1, diff, false) && u && u != me && Rand() < 40 && dist < 30 && + mytar->GetTypeId() == TYPEID_UNIT && !mytar->IsControlledByPlayer() && + !CCed(mytar) && !mytar->HasAuraType(SPELL_AURA_MOD_TAUNT) && + (!IsTank(u) || (IsTank() && GetHealthPCT(me) > 67 && + (GetHealthPCT(u) < 30 || (IsOffTank() && !IsOffTank(u) && IsPointedOffTankingTarget(mytar)) || + (!IsOffTank() && IsOffTank(u) && IsPointedTankingTarget(mytar))))) && + ((!IsTankingClass(u->GetClass()) && GetHealthPCT(u) < 80) || IsTank()) && + IsInBotParty(u)) + { + if (doCast(mytar, GetSpell(GROWL_1))) + return; + } + //GROWL 2 (distant) + if (IsSpellReady(GROWL_1, diff, false) && !IAmFree() && u == me && Rand() < 20 && IsTank() && + (IsOffTank() || master->GetBotMgr()->GetNpcBotsCountByRole(BOT_ROLE_TANK_OFF) == 0) && + !(me->GetLevel() >= 40 && mytar->GetTypeId() == TYPEID_UNIT && + (mytar->ToCreature()->IsDungeonBoss() || mytar->ToCreature()->isWorldBoss()))) + { + if (Unit* tUnit = FindDistantTauntTarget()) + { + if (doCast(tUnit, GetSpell(GROWL_1))) + return; + } + } + //Challenging Roar + if (IsSpellReady(CHALLENGING_ROAR_1, diff) && + !(u == me && me->GetLevel() >= 40 && mytar->GetTypeId() == TYPEID_UNIT && + (mytar->ToCreature()->IsDungeonBoss() || mytar->ToCreature()->isWorldBoss())) && + rage >= acost(CHALLENGING_ROAR_1)) + { + u = mytar->GetVictim(); + if (u && u != me && !IsTank(u) && IsInBotParty(u) && !CCed(mytar) && dist <= 10 && Rand() < 25 && + (!IsTankingClass(u->GetClass()) || IsTank())) + { + if (doCast(me, GetSpell(CHALLENGING_ROAR_1))) + return; + } + if (IsTank() && Rand() < 20) + { + std::list targets; + GetNearbyTargetsList(targets, 9.f, 1); + uint8 count = 0; + for (std::list::const_iterator itr = targets.begin(); itr != targets.end(); ++itr) + { + if (!((*itr)->GetVictim() && IsTank((*itr)->GetVictim()))) + if (++count > 1) + break; + } + if (count > 1 && doCast(me, GetSpell(CHALLENGING_ROAR_1))) + return; + } + } + + if (!CanAffectVictimAny(mytar, SPELL_SCHOOL_NORMAL)) + return; + + //Feral Charge + if (IsSpellReady(FERAL_CHARGE_BEAR_1, diff, false) && rage >= acost(FERAL_CHARGE_BEAR_1) && + !HasBotCommandState(BOT_COMMAND_STAY) && + !CCed(mytar, true) && dist > 9 && dist < 25) + { + if (doCast(mytar, GetSpell(FERAL_CHARGE_BEAR_1))) + return; + } + + //Faerie Fire (Feral, Bear) + if (IsSpellReady(FAERIE_FIRE_FERAL_1, diff) && me->IsInCombat() && Rand() < 35 && dist < CalcSpellMaxRange(FAERIE_FIRE_FERAL_1) && + !mytar->HasAuraTypeWithFamilyFlags(SPELL_AURA_MOD_RESISTANCE_PCT, SPELLFAMILY_DRUID, 0x400)) + { + if (doCast(mytar, GetSpell(FAERIE_FIRE_FERAL_1))) + return; + } + + //range check (melee) to prevent fake casts + if (dist > 5) return; + + //Berserk (Bear) + if (IsSpellReady(BERSERK_1, diff) && !HasRole(BOT_ROLE_HEAL) && rage > 400 && Rand() < 40 && + me->getAttackers().size() > 2) + { + if (doCast(me, GetSpell(BERSERK_1))) + return; + } + + //BOT_ROLE_DPS is checked in Attack(uin32) + //if (!HasRole(BOT_ROLE_DPS)) return; + + //frenzied regeneration check + //we don't need to spend too much rage if regening + bool isRegenActive = IsRegenActive(); + + //Mangle (Bear) + if (IsSpellReady(MANGLE_BEAR_1, diff) && rage >= acost(MANGLE_BEAR_1) + 200*isRegenActive) + { + if (me->GetAuraEffect(SPELL_AURA_MECHANIC_IMMUNITY, SPELLFAMILY_DRUID, 0x0, 0x0, 0x40) || + (Rand() < 30 && !mytar->GetAuraEffect(SPELL_AURA_MOD_MECHANIC_DAMAGE_TAKEN_PERCENT, SPELLFAMILY_DRUID, 0x0, 0x40, 0x0))) + { + if (doCast(mytar, GetSpell(MANGLE_BEAR_1))) + return; + } + } + //Swipe (Bear) + if (IsSpellReady(SWIPE_BEAR_1, diff) && rage >= acost(SWIPE_BEAR_1) + 200*isRegenActive && + IsTank() && Rand() < 70) + { + std::list targets; + GetNearbyTargetsInConeList(targets, 5); + if (targets.size() > 2) + if (doCast(mytar, GetSpell(SWIPE_BEAR_1))) + return; + } + //Lacerate + if (IsSpellReady(LACERATE_1, diff) && rage >= acost(LACERATE_1) + 200*isRegenActive && + mytar->GetHealth() > me->GetMaxHealth() * 2 && Rand() < 45) + { + bool cast = rage >= 600; + if (!cast) + { + AuraApplication const* lacera = mytar->GetAuraApplicationOfRankedSpell(LACERATE_1); + cast = (!lacera || lacera->GetBase()->GetStackAmount() < 5 || lacera->GetBase()->GetDuration() < 6000); + } + + if (cast && doCast(mytar, GetSpell(LACERATE_1))) + return; + } + + //skip if maul is active + if (me->GetCurrentSpell(CURRENT_MELEE_SPELL)) + return; + + //Maul //No GCD + if (IsSpellReady(MAUL_1, diff, false) && rage >= acost(MAUL_1) + 200 + 200*isRegenActive) + { + if (doCast(mytar, GetSpell(MAUL_1))) + return; + } + } + + void doCatActions(Unit* mytar, uint32 diff) + { + //debug + if (me->GetPowerType() != POWER_ENERGY || me->GetShapeshiftForm() != FORM_CAT) + return; + + //Prowl (for Cooldown handling see bot_ai::ReleaseSpellCooldown) + if (IsSpellReady(PROWL_1, diff, false) && !me->IsInCombat() && Rand() < 50 && me->GetDistance(mytar) < 28 && !IsFlagCarrier(me)) + { + if (doCast(me, GetSpell(PROWL_1))) + {} + } + + if (!CanAffectVictimAny(mytar, SPELL_SCHOOL_NORMAL)) + return; + + //Faerie Fire (Feral, Cat) + if (IsSpellReady(FAERIE_FIRE_FERAL_1, diff) && me->IsInCombat() && !me->HasAuraType(SPELL_AURA_MOD_STEALTH) && + Rand() < 35 && me->GetDistance(mytar) < 30 && + !mytar->HasAuraTypeWithFamilyFlags(SPELL_AURA_MOD_RESISTANCE_PCT, SPELLFAMILY_DRUID, 0x400)) + { + if (doCast(mytar, GetSpell(FAERIE_FIRE_FERAL_1))) + return; + } + + if (!JumpingOrFalling() && !CCed(me, true)) + { + //leap here + //Feral Charge (Cat) + if (IsSpellReady(FERAL_CHARGE_CAT_1, diff, false) && energy >= acost(FERAL_CHARGE_CAT_1) && !me->GetMap()->IsDungeon() && + !HasBotCommandState(BOT_COMMAND_STAY) && + !me->HasAuraType(SPELL_AURA_MOD_STEALTH) && Rand() < 65 && + !me->GetAuraEffect(SPELL_AURA_MOD_INCREASE_SPEED, SPELLFAMILY_DRUID, 0x0, 0x0, 0x8) &&//not dashing + me->GetDistance(mytar) > 10 && me->GetDistance(mytar) < 25) + { + if (doCast(mytar, GetSpell(FERAL_CHARGE_CAT_1))) + return; //no gcd but jump time + } + + //Dash (no GCD) + if (IsSpellReady(DASH_1, diff, false) && + (me->HasAuraType(SPELL_AURA_MOD_STEALTH) || (me->IsInCombat() && !IsSpellReady(FERAL_CHARGE_CAT_1, diff, false))) && + Rand() < 85 && me->GetDistance(mytar) > 15) + { + if (doCast(me, GetSpell(DASH_1))) + {} + } + //Savage Roar + if (IsSpellReady(SAVAGE_ROAR_1, diff) && comboPoints >= 1 && (me->IsInCombat() || mytar->IsInCombat()) && + !me->HasAuraType(SPELL_AURA_MOD_STEALTH) && energy >= acost(SAVAGE_ROAR_1) && + !me->GetAuraEffect(SPELL_AURA_DUMMY, SPELLFAMILY_DRUID, 0, 0x10000000, 0)) + { + if (doCast(mytar, GetSpell(SAVAGE_ROAR_1))) + return; + } + } + + MoveBehind(mytar); + + //range check (melee) to prevent fake casts + if (me->GetDistance(mytar) > 5) + return; + + //Cower + if (mytar->CanHaveThreatList()) + { + if (IsSpellReady(COWER_1, diff) && mytar->GetVictim() == me && energy >= acost(COWER_1) && + int32(mytar->GetThreatManager().GetThreatListSize()) > 1 && + int32(mytar->getAttackers().size()) > 1 && Rand() < 45) + { + if (doCast(mytar, GetSpell(COWER_1))) + return; + } + } + //Tiger's Fury (no GCD) cannot use while Berserk is active + if (IsSpellReady(TIGERS_FURY_1, diff, false) && mytar->GetHealth() > me->GetHealth() / 4 && + (me->GetLevel() < 55 || energy <= 40) && Rand() < 40 && + !me->GetAuraEffect(SPELL_AURA_MECHANIC_IMMUNITY, SPELLFAMILY_DRUID, 0x0, 0x0, 0x40)) + { + if (doCast(me, GetSpell(TIGERS_FURY_1))) + getenergy(); + } + //Berserk can be used After Tiger's Fury without dispelling it + //Berserk (Cat) + if (IsSpellReady(BERSERK_1, diff) && !HasRole(BOT_ROLE_HEAL) && (!me->HasAuraType(SPELL_AURA_MOD_STEALTH) || energy >= 40) && Rand() < 50 && + (mytar->GetTypeId() == TYPEID_PLAYER || mytar->GetHealth() + 5000 > me->GetHealth())) + { + if (doCast(me, GetSpell(BERSERK_1))) + return; + } + + //Openers + if (me->HasAuraType(SPELL_AURA_MOD_STEALTH)) + { + uint32 opener = + GetSpell(POUNCE_1) && + !mytar->HasAuraType(SPELL_AURA_MOD_STUN) && + mytar->GetDiminishing(DIMINISHING_OPENING_STUN) < DIMINISHING_LEVEL_3 && + (mytar->GetTypeId() == TYPEID_PLAYER || (!IAmFree() && master->GetNpcBotsCount() > 1)) ? POUNCE_1 : + GetSpell(RAVAGE_1) ? RAVAGE_1 : + GetSpell(SHRED_1) ? SHRED_1 : 0; + + //all opener spells disabled + if (!opener) + { + me->RemoveAurasByType(SPELL_AURA_MOD_STEALTH); + return; + } + + if (opener != POUNCE_1 && mytar->HasInArc(float(M_PI), me)) + return; + + //We do not check combo points amount + if (IsSpellReady(opener, diff) && energy >= acost(opener)) + { + if (doCast(mytar, GetSpell(opener))) + return; + } + + return; + } + + //Finishers + if (comboPoints > 0) + { + //Maim + if (IsSpellReady(MAIM_1, diff) && !CCed(mytar) && energy >= acost(MAIM_1) && + (comboPoints >= 4 || mytar->IsNonMeleeSpellCast(false,false,true))) + { + if (doCast(mytar, GetSpell(MAIM_1))) + return; + } + //Ferocious Bite + if (IsSpellReady(FEROCIOUS_BITE_1, diff) && (comboPoints >= 4 || mytar->GetHealth() < me->GetMaxHealth() / 4) && + energy >= acost(FEROCIOUS_BITE_1) && Rand() < (50 + comboPoints * 20)) + { + if (doCast(mytar, GetSpell(FEROCIOUS_BITE_1))) + return; + } + //Rip + if (IsSpellReady(RIP_1, diff) && (comboPoints < 4 || !GetSpell(FEROCIOUS_BITE_1)) && + energy >= acost(RIP_1) && mytar->GetHealth() > me->GetMaxHealth() / 4 && + Rand() < (50 + 40 * (mytar->GetTypeId() == TYPEID_PLAYER && IsMeleeClass(mytar->GetClass()))) && + !mytar->GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_DRUID, 0x800000, 0x0, 0x0, me->GetGUID())) + { + if (doCast(mytar, GetSpell(RIP_1))) + return; + } + } + + //Combo points generating + //Swipe (Cat) + if (IsSpellReady(SWIPE_CAT_1, diff) && me->getAttackers().empty() && energy >= acost(SWIPE_CAT_1) && Rand() < (35 + 100 * (me->GetMap()->IsDungeon()))) + { + std::list targets; + GetNearbyTargetsInConeList(targets, 5); + if (targets.size() > 2) + if (doCast(mytar, GetSpell(SWIPE_CAT_1))) + return; + } + //Shred + if (IsSpellReady(SHRED_1, diff) && comboPoints < 4 && energy >= acost(SHRED_1) && Rand() < 85 && + !mytar->HasInArc(float(M_PI), me)) + { + if (doCast(mytar, GetSpell(SHRED_1))) + return; + } + //Mangle (Cat) + if (IsSpellReady(MANGLE_CAT_1, diff) && comboPoints < 5 && energy >= acost(MANGLE_CAT_1) && + (Rand() < 20 || !mytar->GetAuraEffect(SPELL_AURA_MOD_MECHANIC_DAMAGE_TAKEN_PERCENT, SPELLFAMILY_DRUID, 0x0, 0x400, 0x0))) + { + if (doCast(mytar, GetSpell(MANGLE_CAT_1))) + return; + } + //Rake + if (IsSpellReady(RAKE_1, diff) && comboPoints < 3 && energy >= acost(RAKE_1) && Rand() < 60 && + !mytar->GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_DRUID, 0x1000, 0x0, 0x0, me->GetGUID())) + { + if (doCast(mytar, GetSpell(RAKE_1))) + return; + } + //Claw + if (IsSpellReady(CLAW_1, diff) && comboPoints < 5 && Rand() < 50 && (!GetSpell(SHRED_1) || mytar->HasInArc(float(M_PI), me)) && + energy >= acost(CLAW_1)) + { + if (doCast(mytar, GetSpell(CLAW_1))) + return; + } + } + + void doBalanceActions(Unit* mytar, uint32 diff) + { + //debug + if (me->GetPowerType() != POWER_MANA) + return; + + MoveBehind(mytar); + + if (HasRole(BOT_ROLE_HEAL) && GetManaPCT(me) < 25) + return; + + //BOT_ROLE_DPS is checked in Attack(uint32) + + float dist = me->GetDistance(mytar); + if (dist > CalcSpellMaxRange(WRATH_1)) + return; + + auto [can_do_nature, can_do_arcane] = CanAffectVictimBools(mytar, SPELL_SCHOOL_NATURE, SPELL_SCHOOL_ARCANE); + if (!can_do_nature && !can_do_arcane) + return; + + //spell reflections + if (IsSpellReady(FAERIE_FIRE_NORMAL_1, diff) && can_do_nature && CanRemoveReflectSpells(mytar, FAERIE_FIRE_NORMAL_1) && + doCast(mytar, FAERIE_FIRE_NORMAL_1)) + return; + + //Starfall + if (IsSpellReady(STARFALL_1, diff) && Rand() < 40) + { + bool cast = (mytar->GetTypeId() == TYPEID_PLAYER || me->getAttackers().size() > 1); + if (!cast) + { + std::list targets; + GetNearbyTargetsList(targets, 30.f, 0); + if (targets.size() > 3) + cast = true; + } + + if (cast && doCast(me, GetSpell(STARFALL_1))) + return; + + SetSpellCooldown(STARFALL_1, 1500); //fail + } + //Hurricane + if (IsSpellReady(HURRICANE_1, diff) && !JumpingOrFalling() && Rand() < 50) + { + if (Unit* target = FindAOETarget(CalcSpellMaxRange(HURRICANE_1))) + { + if (doCast(target, GetSpell(HURRICANE_1))) + return; + } + SetSpellCooldown(HURRICANE_1, 1000); //fail + } + //Typhoon + if (IsSpellReady(TYPHOON_1, diff) && Rand() < 75) + { + std::list targets; + GetNearbyTargetsInConeList(targets, 25); + if (targets.size() > 2) + if (doCast(me, GetSpell(TYPHOON_1))) + return; + + SetSpellCooldown(TYPHOON_1, 1000); //fail + } + + if (IsSpellReady(FORCE_OF_NATURE_1, diff)) + { + SummonBotPet(mytar); + SetSpellCooldown(FORCE_OF_NATURE_1, 180000); + return; + } + + //Faerie Fire (non-feral): moonkin or non-shapeshifted + if (IsSpellReady(FAERIE_FIRE_NORMAL_1, diff) && can_do_nature && mytar->getAttackers().size() > 2 && Rand() < 50 && + dist < CalcSpellMaxRange(FAERIE_FIRE_NORMAL_1) && + !mytar->HasAuraTypeWithFamilyFlags(SPELL_AURA_MOD_RESISTANCE_PCT, SPELLFAMILY_DRUID, 0x400) + /*!HasAuraName(mytar, FAERIE_FIRE_ANY)*/) + { + if (doCast(mytar, GetSpell(FAERIE_FIRE_NORMAL_1))) + return; + } + + Unit const* u = mytar->GetVictim(); + //Insect Swarm + if (IsSpellReady(INSECT_SWARM_1, diff) && can_do_nature && u && mytar->GetDistance(u) < 8 && Rand() < 30 && + dist < CalcSpellMaxRange(INSECT_SWARM_1) && + !mytar->GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_DRUID, 0x200000, 0x0, 0x0, me->GetGUID()) + /*!HasAuraName(mytar, INSECT_SWARM_1, me->GetGUID())*/) + { + if (doCast(mytar, GetSpell(INSECT_SWARM_1))) + return; + } + + if (IsSpellReady(MOONFIRE_1, diff) && can_do_arcane && Rand() < 60 && dist < CalcSpellMaxRange(MOONFIRE_1) && + !mytar->GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_DRUID, 0x2, 0x0, 0x0, me->GetGUID()) + /*!HasAuraName(mytar, MOONFIRE_1, me->GetGUID())*/) + { + if (doCast(mytar, GetSpell(MOONFIRE_1))) + return; + } + //TODO: balance starfire/wrath frequency based on mana effeciency + if (IsSpellReady(STARFIRE_1, diff) && can_do_arcane && dist < CalcSpellMaxRange(STARFIRE_1)) + { + AuraEffect const* eclipeLunar = me->GetAuraEffect(SPELL_AURA_ADD_FLAT_MODIFIER, SPELLFAMILY_DRUID, 0x0, 0x0, 0x4000); + int32 rand = 30 + 100 * (eclipeLunar && eclipeLunar->GetBase()->GetDuration() > 3000); + + if (Rand() < rand && doCast(mytar, GetSpell(STARFIRE_1))) + return; + } + if (IsSpellReady(WRATH_1, diff) && can_do_nature) + { + if (doCast(mytar, GetSpell(WRATH_1))) + return; + } + } + + void BreakCC(uint32 diff) override + { + if (GC_Timer <= diff && Rand() < 25 && GetManaPCT(me) > 15 && + (me->IsPolymorphed() || me->HasAuraWithMechanic((1<getAttackers().empty() && + (!me->GetVictim() || me->GetDistance(me->GetVictim()) < 5) ? 0 : + GetSpell(BEAR_FORM_1); break; + case DRUID_CAT_FORM: sshift = GetSpell(CAT_FORM_1); break; + case DRUID_MOONKIN_FORM: sshift = GetSpell(MOONKIN_FORM_1); break; + case DRUID_TREE_FORM: sshift = GetSpell(TREE_OF_LIFE_FORM_1);break; + //case DRUID_FLIGHT_FORM: sshift = GetSpell(FLIGHT_FORM_1); break; + case DRUID_TRAVEL_FORM: sshift = GetSpell(TRAVEL_FORM_1); break; + case DRUID_AQUATIC_FORM: sshift = GetSpell(AQUATIC_FORM_1); break; + case BOT_STANCE_NONE: sshift = GetSpell(TRAVEL_FORM_1); break; + default: sshift = 0; break; + } + if (sshift && doCast(me, sshift)) + { + GetInPosition(false, nullptr); + return; + } + } + if (IsSpellReady(BERSERK_1, diff) && Rand() < 10 && me->HasAuraWithMechanic(1<IsAlive() || target->GetShapeshiftForm() == FORM_SPIRITOFREDEMPTION || me->GetDistance(target) > 40) + return false; + uint8 hp = GetHealthPCT(target); + if (hp > GetHealHpPctThreshold()) + return false; + bool pointed = IsPointedHealTarget(target); + if (hp > 95 && !(pointed && me->GetMap()->IsRaid()) && + (!target->IsInCombat() || target->getAttackers().empty() || !IsTank(target) || !me->GetMap()->IsRaid())) + return false; + + int32 hps = GetHPS(target); + int32 xphp = target->GetHealth() + hps * 2; + int32 hppctps = int32(hps * 100.f / float(target->GetMaxHealth())); + int32 xphploss = xphp > int32(target->GetMaxHealth()) ? 0 : abs(int32(xphp - target->GetMaxHealth())); + int32 xppct = hp + hppctps * 2; + if (xppct >= 95 && hp >= 25 && !pointed) + return false; + + if (IsTank() && xppct > 25) + return false; + if (hp > 50 && !IsTank(target) && (_form == DRUID_BEAR_FORM || _form == DRUID_CAT_FORM)) + return false; //do not waste heal if in feral or so + + if (IsSpellReady(NATURES_SWIFTNESS_1, diff, false) && Rand() < 80 && + (me->IsInCombat() || target->IsInCombat()) &&//may just revive + hp <= 20 && xppct <= 0 && xphploss > _heals[HEALING_TOUCH_1] / 2 && + (target->GetTypeId() == TYPEID_PLAYER || IsTank(target) || target->IsInCombat() || !target->getAttackers().empty())) + { + me->InterruptNonMeleeSpells(false); + if (doCast(me, GetSpell(NATURES_SWIFTNESS_1))) + { + if (doCast(target, GetSpell(HEALING_TOUCH_1))) + return true; + } + } + if (IsSpellReady(NOURISH_1, diff) && xppct <= 65 && xphploss > _heals[REJUVENATION_1]) + { + static uint8 minHots = 2; + uint8 hots = 0; + Unit::AuraEffectList const& effectList = target->GetAuraEffectsByType(SPELL_AURA_PERIODIC_HEAL); + for (Unit::AuraEffectList::const_iterator itr = effectList.begin(); itr != effectList.end(); ++itr) + { + AuraEffect const* eff = *itr; + if (eff->GetCasterGUID() != me->GetGUID()) + continue; + SpellInfo const* spellInfo = eff->GetSpellInfo(); + if (spellInfo->SpellFamilyName != SPELLFAMILY_DRUID) + continue; + //rejuv,regro,lifeb,wildg + if (!((spellInfo->SpellFamilyFlags[0] & 0x50) || (spellInfo->SpellFamilyFlags[1] & 0x4000010))) + continue; + hots += eff->GetBase()->GetStackAmount(); + if (hots >= minHots) + break; + } + if (hots >= minHots && doCast(target, GetSpell(NOURISH_1))) + return true; + } + if (IsSpellReady(SWIFTMEND_1, diff, false) && !HasRole(BOT_ROLE_DPS|BOT_ROLE_TANK) && hp < 60 && + (xppct <= 15 || int32(GetLostHP(target)) > _heals[REJUVENATION_1]) && + //rejuv,regro + target->GetAuraEffect(SPELL_AURA_PERIODIC_HEAL, SPELLFAMILY_DRUID, 0x50, 0x0, 0x0, me->GetGUID()) + /*(HasAuraName(target, REGROWTH_1) || HasAuraName(target, REJUVENATION_1))*/) + { + if (doCast(target, GetSpell(SWIFTMEND_1))) + return true; + } + //maintain HoTs + Unit const* u = target->GetVictim(); + bool tanking = u && IsTank(target) && u->ToCreature() && u->ToCreature()->isWorldBoss(); + if (IsSpellReady(REGROWTH_1, diff) && Rand() < 80 && (tanking || xphploss > _heals[REGROWTH_1]) && + (xppct <= 45 || !target->GetAuraEffect(SPELL_AURA_PERIODIC_HEAL, SPELLFAMILY_DRUID, 0x40, 0x0, 0x0, me->GetGUID())) + /*!HasAuraName(target, REGROWTH_1, me->GetGUID())*/) + { + if (doCast(target, GetSpell(REGROWTH_1))) + return true; + } + if (IsSpellReady(LIFEBLOOM_1, diff) && xppct >= 40 && hp < 75 + 10 * tanking) + { + AuraEffect const* bloom = target->GetAuraEffect(SPELL_AURA_PERIODIC_HEAL, SPELLFAMILY_DRUID, 0x0, 0x10, 0x0, me->GetGUID()); + //Aura const* bloom = target->GetAura(GetSpell(LIFEBLOOM_1), me->GetGUID()); + if ((!bloom || bloom->GetBase()->GetStackAmount() < 3 || + (bloom->GetBase()->GetDuration() < 2000 && !target->getAttackers().empty())) && + doCast(target, GetSpell(LIFEBLOOM_1))) + return true; + } + if (IsSpellReady(HEALING_TOUCH_1, diff) && (xppct > 15 || !GetSpell(REGROWTH_1)) && + xphploss > _heals[HEALING_TOUCH_1] && + doCast(target, GetSpell(HEALING_TOUCH_1))) + return true; + if (IsSpellReady(REJUVENATION_1, diff) && xppct > 45 && + (tanking || xphploss > _heals[REJUVENATION_1]) && + !target->GetAuraEffect(SPELL_AURA_PERIODIC_HEAL, SPELLFAMILY_DRUID, 0x10, 0x0, 0x0, me->GetGUID()) + /*!HasAuraName(target, REJUVENATION_1, me->GetGUID())*/) + { + if (doCast(target, GetSpell(REJUVENATION_1))) + return true; + } + return false; + } + + bool BuffTarget(Unit* target, uint32 /*diff*/) override + { + if (me->IsInCombat() && !master->GetMap()->IsRaid()) return false; + + if (uint32 MARK_OF_THE_WILD = GetSpell(MARK_OF_THE_WILD_1)) + { + if (!target->HasAuraTypeWithFamilyFlags(SPELL_AURA_MOD_RESISTANCE, SPELLFAMILY_DRUID, 0x40000) + /*!HasAuraName(target, MARK_OF_THE_WILD_1)*/) + if (doCast(target, MARK_OF_THE_WILD)) + return true; + } + if (uint32 THORNS = GetSpell(THORNS_1)) + { + if (IsTank(target) && + !target->HasAuraTypeWithFamilyFlags(SPELL_AURA_DAMAGE_SHIELD, SPELLFAMILY_DRUID, 0x100) + /*!HasAuraName(target, THORNS_1)*/) + if (doCast(target, THORNS)) + return true; + } + + return false; + } + + void CheckTravel(uint32 diff) + { + if ((!IAmFree() && !HasBotCommandState(BOT_COMMAND_FOLLOW)) || Rand() > 35 || me->IsMounted() || IsCasting() || + me->HasUnitMovementFlag(MOVEMENTFLAG_SWIMMING | MOVEMENTFLAG_FLYING)) + return; + + bool can_use_travel_form = IsSpellReady(TRAVEL_FORM_1, diff) && me->GetShapeshiftForm() == FORM_NONE && IsOutdoors(); + + if (IAmFree()) + { + InstanceTemplate const* instt = sObjectMgr->GetInstanceTemplate(me->GetMap()->GetId()); + bool map_allows_mount = (!me->GetMap()->IsDungeon() || me->GetMap()->IsBattlegroundOrArena()) && (!instt || instt->AllowMount); + if (me->HasUnitMovementFlag(MOVEMENTFLAG_FORWARD) && + (!me->GetVictim() ? + (me->IsInCombat() || !map_allows_mount || IsFlagCarrier(me)) : + !me->IsWithinDist(me->GetVictim(), 8.0f + (IsMelee() ? 0.0f : GetSpellAttackRange(true))))) + { + if (me->GetShapeshiftForm() == FORM_CAT && IsSpellReady(DASH_1, diff, false)) + { + if (doCast(me, GetSpell(DASH_1))) + return; + } + else if (can_use_travel_form) + { + if (doCast(me, GetSpell(TRAVEL_FORM_1))) + return; + } + } + + return; + } + + if (me->GetExactDist2d(master) > std::max(master->GetBotMgr()->GetBotFollowDist(), 30)) + { + if (me->GetShapeshiftForm() == FORM_CAT && IsSpellReady(DASH_1, diff, false)) + { + if (doCast(me, GetSpell(DASH_1))) + return; + } + else if (can_use_travel_form) + { + if (doCast(me, GetSpell(TRAVEL_FORM_1))) + return; + } + } + } + + void DoNonCombatActions(uint32 diff) + { + if (GC_Timer > diff || me->IsMounted() || IsCasting()) + return; + + ResurrectGroup(GetSpell(REVIVE_1)); + + if (!IAmFree() && HasBotCommandState(BOT_COMMAND_FOLLOW) && !master->IsMounted() && Rand() < 35) + { + int32 dist = int32(me->GetDistance(master)); + if (me->HasUnitMovementFlag(MOVEMENTFLAG_SWIMMING)) + { + if (_form != DRUID_AQUATIC_FORM && dist > 30 && GetSpell(AQUATIC_FORM_1) && + doCast(me, GetSpell(AQUATIC_FORM_1))) + return; + } + //if (me->HasUnitMovementFlag(MOVEMENTFLAG_FLYING)) + //{ + // if (_form != DRUID_AQUATIC_FORM && dist > 30 && GetSpell(AQUATIC_FORM_1) && + // doCast(me, GetSpell(AQUATIC_FORM_1))) + // return; + //} + else if (_form != DRUID_TRAVEL_FORM && dist > 30 && GetSpell(TRAVEL_FORM_1)) + { + if (doCast(me, GetSpell(TRAVEL_FORM_1))) + return; + } + else if (_form == DRUID_AQUATIC_FORM) + removeShapeshiftForm(); + } + } + + void doInnervate(uint32 diff) + { + if (!IsSpellReady(INNERVATE_1, diff) || Rand() > 25) + return; + if (_form != BOT_STANCE_NONE && _form != DRUID_MOONKIN_FORM && _form != DRUID_TREE_FORM && + (IsTank() || me->getAttackers().size() > 3)) + return; + + static const uint8 minmanaval = 30; + Unit* iTarget = nullptr; + + if (master->IsInCombat() && master->GetPowerType() == POWER_MANA && + GetManaPCT(master) < minmanaval && !master->GetAuraEffect(SPELL_AURA_PERIODIC_ENERGIZE, SPELLFAMILY_DRUID, 0x0, 0x1000, 0x0)) + iTarget = master; + else if (me->IsInCombat() && me->GetPowerType() == POWER_MANA && + GetManaPCT(me) < minmanaval && !me->GetAuraEffect(SPELL_AURA_PERIODIC_ENERGIZE, SPELLFAMILY_DRUID, 0x0, 0x1000, 0x0)) + iTarget = me; + + if (!IAmFree()) + { + Group const* group = master->GetGroup(); + if (!iTarget && !group) //first check master's bots + { + BotMap const* map = master->GetBotMgr()->GetBotMap(); + for (BotMap::const_iterator itr = map->begin(); itr != map->end(); ++itr) + { + Creature* bot = itr->second; + if (!bot || !bot->IsInCombat() || !bot->IsAlive() || bot->IsTempBot()) continue; + if (bot->GetPowerType() != POWER_MANA) continue; + if (bot->GetBotClass() == BOT_CLASS_HUNTER || bot->GetBotClass() == BOT_CLASS_WARLOCK || + bot->GetBotClass() == BOT_CLASS_SPHYNX || bot->GetBotClass() == BOT_CLASS_SPELLBREAKER || + bot->GetBotClass() == BOT_CLASS_NECROMANCER) continue; + if (me->GetExactDist(bot) > 30) continue; + if (GetManaPCT(bot) < minmanaval && !bot->GetAuraEffect(SPELL_AURA_PERIODIC_ENERGIZE, SPELLFAMILY_DRUID, 0x0, 0x1000, 0x0)) + { + iTarget = bot; + break; + } + } + } + if (!iTarget && group) //cycle through player members... + { + std::vector members = BotMgr::GetAllGroupMembers(group); + for (uint8 i = 0; i < 2 && !iTarget; ++i) + { + for (Unit* member : members) + { + if (!(i == 0 ? member->IsPlayer() : member->IsNPCBot()) || !member->IsInWorld() || !member->IsInCombat() || + !member->IsAlive() || me->GetExactDist(member) > 30 || GetManaPCT(member) > minmanaval || + member->GetAuraEffect(SPELL_AURA_PERIODIC_ENERGIZE, SPELLFAMILY_DRUID, 0x0, 0x1000, 0x0)) + continue; + if (i == 1) + { + Creature const* bot = member->ToCreature(); + if (bot->IsTempBot() || bot->GetPowerType() != POWER_MANA || + bot->GetBotClass() == BOT_CLASS_HUNTER || bot->GetBotClass() == BOT_CLASS_WARLOCK || + bot->GetBotClass() == BOT_CLASS_SPHYNX || bot->GetBotClass() == BOT_CLASS_SPELLBREAKER || + bot->GetBotClass() == BOT_CLASS_NECROMANCER) + continue; + } + iTarget = member; + break; + } + } + } + } + + if (iTarget && doCast(iTarget, INNERVATE_1)) + { + if (iTarget->GetTypeId() == TYPEID_PLAYER) + ReportSpellCast(INNERVATE_1, LocalizedNpcText(iTarget->ToPlayer(), BOT_TEXT__ON_YOU), iTarget->ToPlayer()); + + if (!IAmFree() && iTarget != master) + { + std::string msg = iTarget == me ? LocalizedNpcText(master, BOT_TEXT__ON_MYSELF) : (LocalizedNpcText(master, BOT_TEXT__ON_) + iTarget->GetName() + '!'); + ReportSpellCast(INNERVATE_1, msg, master); + } + + return; + } + + SetSpellCooldown(INNERVATE_1, 1500); //fail + } + + void CheckRoots() + { + if (uint32 ENTANGLING_ROOTS = GetSpell(ENTANGLING_ROOTS_1)) + { + if (FindAffectedTarget(ENTANGLING_ROOTS, me->GetGUID(), 60)) + return; + if (Unit* target = FindRootTarget(30, ENTANGLING_ROOTS)) + if (doCast(target, ENTANGLING_ROOTS)) + return; + } + } + + void CheckBattleRez(uint32 diff) + { + if (!IsSpellReady(REBIRTH_1, diff, false) || IAmFree() || me->IsMounted() || + IsTank() || IsCasting() || Rand() > 20) return; + + Group const* gr = master->GetGroup(); + std::list targets; + if (!gr) + { + Unit* target = master; + if (master->IsAlive()) return; + if (master->IsResurrectRequested() || master->GetUInt32Value(PLAYER_SELF_RES_SPELL)) return; //resurrected + if (master->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST)) + target = (Unit*)master->GetCorpse(); + if (!target || !target->IsInWorld()) + return; + if (me->GetExactDist(target) > 30 && !HasBotCommandState(BOT_COMMAND_STAY)) + { + me->GetMotionMaster()->MovePoint(master->GetMapId(), *target); + SetSpellCooldown(REBIRTH_1, 1500); + return; + } + else if (!target->IsWithinLOSInMap(me, LINEOFSIGHT_ALL_CHECKS, VMAP::ModelIgnoreFlags::M2)) + me->Relocate(*target); + + if (doCast(target, GetSpell(REBIRTH_1))) //rezzing + { + BotWhisper(LocalizedNpcText(master, BOT_TEXT_REZZING_YOU)); + return; + } + } + else + { + for (GroupReference const* itr = gr->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* tPlayer = itr->GetSource(); + Unit* target = tPlayer; + if (!tPlayer || tPlayer->IsAlive()) continue; + if (tPlayer->IsResurrectRequested() || tPlayer->GetUInt32Value(PLAYER_SELF_RES_SPELL)) continue; //resurrected + if (tPlayer->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST)) + target = (Unit*)tPlayer->GetCorpse(); + if (!target || !target->IsInWorld()) continue; + if (master->GetMap() != target->FindMap()) continue; + if (me->GetDistance(target) > 100) continue; + targets.push_back(target); + } + } + + BotMap const* botMap = master->GetBotMgr()->GetBotMap(); + for (BotMap::const_iterator itr = botMap->begin(); itr != botMap->end(); ++itr) + { + Creature* bot = itr->second; + if (bot && bot->IsInWorld() && !bot->IsAlive() && !bot->GetBotAI()->GetSelfRezSpell() && IsTank(bot) && me->GetDistance(bot) < 80) + targets.push_back(bot); + } + + if (Unit* targetOrCorpse = !targets.empty() ? Trinity::Containers::SelectRandomContainerElement(targets) : nullptr) + { + if (me->GetExactDist(targetOrCorpse) > 30 && !HasBotCommandState(BOT_COMMAND_STAY)) + { + me->GetMotionMaster()->MovePoint(targetOrCorpse->GetMapId(), *targetOrCorpse); + return; + } + else if (!targetOrCorpse->IsWithinLOSInMap(me, LINEOFSIGHT_ALL_CHECKS, VMAP::ModelIgnoreFlags::M2)) + me->Relocate(*targetOrCorpse); + + if (doCast(targetOrCorpse, GetSpell(REBIRTH_1))) //rezzing + { + if (targetOrCorpse->GetTypeId() == TYPEID_PLAYER) + BotWhisper(LocalizedNpcText(targetOrCorpse->ToPlayer(), BOT_TEXT_REZZING_YOU), targetOrCorpse->ToPlayer()); + if (targetOrCorpse != master) + { + std::string rezstr = LocalizedNpcText(master, BOT_TEXT_REZZING_) + targetOrCorpse->GetName(); + if (targetOrCorpse->GetTypeId() == TYPEID_UNIT) + rezstr += " (" + LocalizedNpcText(master, BOT_TEXT_BOT_TANK) + ')'; + BotWhisper(rezstr); + } + return; + } + } + } + + void setStats(BotStances form) + { + _form = form; + switch (form) + { + case DRUID_BEAR_FORM: + if (me->GetPowerType() != POWER_RAGE) + { + //TC_LOG_ERROR("entities.player", "druid_bot::setStats(): has to set powerType to POWER_RAGE"); + me->SetPowerType(POWER_RAGE); + } + RefreshAura(MASTER_SHAPESHIFTER_BEAR_BUFF, me->GetLevel() >= 20); + if (GetSpec() == BOT_SPEC_DRUID_FERAL) + { + RefreshAura(NATURAL_REACTION, me->GetLevel() >= 35); + RefreshAura(SURVIVAL_OF_THE_FITTEST_BUFF, me->GetLevel() >= 35); + RefreshAura(SAVAGE_DEFENSE_PASSIVE, me->GetLevel() >= 40); + } + break; + case DRUID_CAT_FORM: + if (me->GetPowerType() != POWER_ENERGY) + { + //TC_LOG_ERROR("entities.player", "druid_bot::setStats(): has to set powerType to POWER_ENERGY"); + me->SetPowerType(POWER_ENERGY); + } + RefreshAura(MASTER_SHAPESHIFTER_CAT_BUFF, me->GetLevel() >= 20); + RefreshAura(FERAL_SWIFTNESS, me->GetLevel() >= 20); //talents ignore forms for creatures so put that here + if (GetSpec() == BOT_SPEC_DRUID_FERAL) + { + RefreshAura(NURTURING_INSTINCT_BUFF, me->GetLevel() >= 30); + RefreshAura(PREDATORY_INSTINCTS, me->GetLevel() >= 45); + } + break; + case DRUID_MOONKIN_FORM: + if (me->GetPowerType() != POWER_MANA) + { + //TC_LOG_ERROR("entities.player", "druid_bot::setStats(): has to set powerType to POWER_MANA (moonkin)"); + me->SetPowerType(POWER_MANA); + } + RefreshAura(MASTER_SHAPESHIFTER_MOONKIN_BUFF, me->GetLevel() >= 20); + RefreshAura(OWLKIN_FRENZY, me->GetLevel() >= 45); + break; + case DRUID_TREE_FORM: + if (me->GetPowerType() != POWER_MANA) + { + //TC_LOG_ERROR("entities.player", "druid_bot::setStats(): has to set powerType to POWER_MANA (tree)"); + me->SetPowerType(POWER_MANA); + } + RefreshAura(MASTER_SHAPESHIFTER_TREE_BUFF, me->GetLevel() >= 20); + break; + case DRUID_TRAVEL_FORM: + if (me->GetPowerType() != POWER_MANA) + { + //TC_LOG_ERROR("entities.player", "druid_bot::setStats(): has to set powerType to POWER_MANA (travel)"); + me->SetPowerType(POWER_MANA); + } + break; + case DRUID_AQUATIC_FORM: + if (me->GetPowerType() != POWER_MANA) + { + //TC_LOG_ERROR("entities.player", "druid_bot::setStats(): has to set powerType to POWER_MANA (aquatic)"); + me->SetPowerType(POWER_MANA); + } + break; + //case DRUID_FLIGHT_FORM: + // if (me->GetPowerType() != POWER_MANA) + // { + // //TC_LOG_ERROR("entities.player", "druid_bot::setStats(): has to set powerType to POWER_MANA (flight)"); + // me->SetPowerType(POWER_MANA); + // } + // break; + case BOT_STANCE_NONE: + if (me->GetPowerType() != POWER_MANA) + { + //TC_LOG_ERROR("entities.player", "druid_bot::setStats(): has to set powerType to POWER_MANA (deshape)"); + me->SetPowerType(POWER_MANA); + } + break; + default: + TC_LOG_ERROR("entities.player", "druid_bot::setStats(): NYI form {}", uint32(form)); + setStats(BOT_STANCE_NONE); + return; + } + + SetShouldUpdateStats(); + SetStats(false); + } + + void ApplyClassDamageMultiplierMelee(uint32& /*damage*/, CalcDamageInfo& damageinfo) const override + { + uint8 lvl = me->GetLevel(); + float pctbonus = 0.0f; + + if (damageinfo.HitOutCome == MELEE_HIT_CRIT) + { + //!!!Melee spell damage is not yet critical, all reduced by half + //Primal Fury (white attacks): 100% to gain 5 rage at crit in (Dire) Bear Form + if ((GetSpec() == BOT_SPEC_DRUID_FERAL) && lvl >= 25 && _form == DRUID_BEAR_FORM) + me->CastSpell(me, PRIMAL_FURY_EFFECT_ENERGIZE, true); + //Predatory Instincts (part 1): 10% additional crit damage bonus for melee attacks in Cat form + if ((GetSpec() == BOT_SPEC_DRUID_FERAL) && lvl >= 45 && _form == DRUID_CAT_FORM) + pctbonus += 0.05f; + } + + damageinfo.Damages[0].Damage = uint32(damageinfo.Damages[0].Damage * (1.0f + pctbonus)); + } + + void ApplyClassDamageMultiplierMeleeSpell(int32& damage, SpellNonMeleeDamage& damageinfo, SpellInfo const* spellInfo, WeaponAttackType /*attackType*/, bool iscrit) const override + { + //uint32 spellId = spellInfo->Id; + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + uint8 lvl = me->GetLevel(); + float fdamage = float(damage); + + //apply bonus damage mods + float pctbonus = 0.0f; + if (iscrit) + { + //!!!Melee spell damage is not yet critical, all reduced by half + ////Elemental Fury (part 2): 50% additional crit damage bonus for Nature, Fire and Frost (all) spells + //if (lvl >= 21) + // pctbonus += 0.25f; + } + + //Feral Aggression: 15% bonus damage for Ferocious Bite + if (lvl >= 10 && baseId == FEROCIOUS_BITE_1) + pctbonus += 0.15f; + //Feral Instinct: 30% bonus damage for Swipe (Bear) + if (lvl >= 15 && baseId == SWIPE_BEAR_1) + pctbonus += 0.3f; + //Savage Fury: 20% bonus damage for Claw, Rake, Mangle (Cat), Mangle (Bear) and Maul + if (lvl >= 15 && + (baseId == CLAW_1 || + baseId == RAKE_1 || + baseId == MANGLE_CAT_1 || + baseId == MANGLE_BEAR_1 || + baseId == MAUL_1)) + pctbonus += 0.2f; + //Rend and Tear: 20% bonus damage on bleeding targets for Maul and Shred + if ((GetSpec() == BOT_SPEC_DRUID_FERAL) && + lvl >= 55 && damageinfo.target && damageinfo.target->HasAuraState(AURA_STATE_BLEEDING) && + (baseId == MAUL_1 || baseId == SHRED_1)) + pctbonus += 0.2f; + //Glyph of Mangle: 10% bonus damage for Mangle (all) + if (lvl >= 50 && (baseId == MANGLE_BEAR_1 || baseId == MANGLE_CAT_1)) + pctbonus += 0.1f; + + //Primal Fury (yellow attacks): 100% to gain 5 rage at crit in (Dire) Bear Form + //Primal Fury (yellow attacks): 100% to gain 1 combo point at crit in Cat Form + if (_form == DRUID_BEAR_FORM && iscrit && lvl >= 25) + me->CastSpell(me, PRIMAL_FURY_EFFECT_ENERGIZE, true); + if (_form == DRUID_CAT_FORM && + (baseId == CLAW_1 || baseId == MANGLE_CAT_1 || baseId == POUNCE_1 || + baseId == RAKE_1 || baseId == RAVAGE_1 || baseId == SHRED_1)) + primalFuryProc = iscrit && lvl >= 25; + + damage = int32(fdamage * (1.0f + pctbonus)); + } + + void ApplyClassSpellCritMultiplierAll(Unit const* victim, float& crit_chance, SpellInfo const* spellInfo, SpellSchoolMask /*schoolMask*/, WeaponAttackType /*attackType*/) const override + { + //uint32 spellId = spellInfo->Id; + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + uint8 lvl = me->GetLevel(); + + //Nature's bounty: 25% additional critical chance for Regrowth and Nourish + if ((GetSpec() == BOT_SPEC_DRUID_RESTORATION) && lvl >= 35 && (baseId == REGROWTH_1 || baseId == NOURISH_1)) + crit_chance += 25.f; + //Rend and Tear (part 2): 25% additional critical chance on bleeding targets for Ferocious Bite (handled in Unit.cpp) + //if (lvl >= 55 && victim->HasAuraState(AURA_STATE_BLEEDING) && baseId == FEROCIOUS_BITE_1) + // crit_chance += 25.f; + //Improved Moonfire + if (lvl >= 15 && baseId == MOONFIRE_1) + crit_chance += 10.f; + //Nature's Majesty: 4% additional critical chance for Wrath, Starfire, Starfall, Nourish and Healing Touch + if (lvl >= 15 && ((spellInfo->SpellFamilyFlags[0] & 0x25) || (spellInfo->SpellFamilyFlags[1] & 0x2800000))) + crit_chance += 4.f; + //Eclipse (Lunar): 40% additional critical chance for Starfire + if (lvl >= 50 && baseId == STARFIRE_1 && me->HasAura(ECLIPSE_LUNAR_BUFF)) + crit_chance += 40.f; + //Improved Faerie Fire (part 2): 3% additional critical chance for all spells on target affected by Faerie Fire + if (GetSpec() == BOT_SPEC_DRUID_BALANCE && lvl >= 40 && victim && victim->HasAuraState(AURA_STATE_FAERIE_FIRE)) + crit_chance += 3.f; + //Natural Perfection: 3% additional critical chance for all spells + if (GetSpec() == BOT_SPEC_DRUID_RESTORATION && lvl >= 40) + crit_chance += 3.f; + } + + void ApplyClassDamageMultiplierSpell(int32& damage, SpellNonMeleeDamage& /*damageinfo*/, SpellInfo const* spellInfo, WeaponAttackType /*attackType*/, bool iscrit) const override + { + //uint32 spellId = spellInfo->Id; + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + uint8 lvl = me->GetLevel(); + float fdamage = float(damage); + + //apply bonus damage mods + float pctbonus = 0.0f; + if (iscrit) + { + //!!!spell damage is not yet critical and will be multiplied by 1.5 + //so we should put here bonus damage mult /1.5 + //Vengeance: 100% additional crit damage bonus for Starfire, Starfall, Moonfire and Wrath + if (lvl >= 25 && ((spellInfo->SpellFamilyFlags[0] & 0x7) || (spellInfo->SpellFamilyFlags[1] & 0x800000))) + pctbonus += 0.333f; + } + //Genesis: 5% bonus damage for Dots + if (lvl >= 10 && ((spellInfo->SpellFamilyFlags[0] & 0x6002D2) || (spellInfo->SpellFamilyFlags[1] & 0x4000010))) + pctbonus += 0.05f; + //Improved Insect Swarm: SpellEffects.cpp, Unit.cpp + //Brambles: 75% bonus damage for Throns and Entangling Roots (Thorns are handled in Unit.cpp) + if (lvl >= 20 && (spellInfo->SpellFamilyFlags[0] & 0x200)) + pctbonus += 0.75f; + //Moonfury: 10% bonus damage for Starfire, Moonfire and Wrath + if (lvl >= 35 && (spellInfo->SpellFamilyFlags[0] & 0x7)) + pctbonus += 0.1f; + //Glyph of Focus (part 1): 10% bonus damage for Starfall + if (lvl >= 70 && (spellInfo->SpellFamilyFlags[1] & 0x800000)) + pctbonus += 0.1f; + //Wrath of Cenarius: 20%/10% Increased spellpower bonus for Starfire/Wrath + if ((GetSpec() == BOT_SPEC_DRUID_BALANCE) && lvl >= 45) + { + if (baseId == STARFIRE_1) + fdamage += me->SpellBaseDamageBonusDone(SPELL_SCHOOL_MASK_MAGIC) * 0.2f * me->CalculateDefaultCoefficient(spellInfo, SPELL_DIRECT_DAMAGE) * me->CalculateSpellpowerCoefficientLevelPenalty(spellInfo); + if (baseId == WRATH_1) + fdamage += me->SpellBaseDamageBonusDone(SPELL_SCHOOL_MASK_MAGIC) * 0.1f * me->CalculateDefaultCoefficient(spellInfo, SPELL_DIRECT_DAMAGE) * me->CalculateSpellpowerCoefficientLevelPenalty(spellInfo); + } + //Eclipse (Solar): 40% bonus damage for Wrath + if (lvl >= 50 && baseId == WRATH_1 && me->HasAura(ECLIPSE_SOLAR_BUFF)) + pctbonus += 0.4f; + //Gale Winds: 30% bonus damage for Hurricane and Typhoon + if ((GetSpec() == BOT_SPEC_DRUID_BALANCE) && + lvl >= 50 && ((spellInfo->SpellFamilyFlags[0] & 0x400000) || (spellInfo->SpellFamilyFlags[1] & 0x1000000))) + pctbonus += 0.3f; + + damage = int32(fdamage * (1.0f + pctbonus)); + } + + void ApplyClassDamageMultiplierHeal(Unit const* /*victim*/, float& heal, SpellInfo const* spellInfo, DamageEffectType damagetype, uint32 stack) const override + { + //uint32 spellId = spellInfo->Id; + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + uint8 lvl = me->GetLevel(); + float pctbonus = 0.0f; + float flat_mod = 0.0f; + + //Genesis: 5% bonus healing for Hots + if (lvl >= 10 && ((spellInfo->SpellFamilyFlags[0] & 0x6002D2) || (spellInfo->SpellFamilyFlags[1] & 0x4000010))) + pctbonus += 0.05f; + //Improved Rejuvenation: 15% bonus healing for Rejuvenation + if (lvl >= 25 && baseId == REJUVENATION_1) + pctbonus += 0.15f; + //Improved Rejuvenation (17114,17115): 27% bonus healing for Rejuvenation + //if (baseId == REJUVENATION_1) + // pctbonus += 0.27f; + //Gift of Nature: 10% bonus healing for all spells + if ((GetSpec() == BOT_SPEC_DRUID_RESTORATION) && lvl >= 30) + pctbonus += 0.1f; + //Empowered Touch: 40% bonus (from spellpower) for Healing Touch and 20% bonus (from spellpower) for Nourish + if ((GetSpec() == BOT_SPEC_DRUID_RESTORATION) && lvl >= 35) + { + if (baseId == HEALING_TOUCH_1) + flat_mod += me->SpellBaseHealingBonusDone(SPELL_SCHOOL_MASK_MAGIC) * 0.4f * me->CalculateDefaultCoefficient(spellInfo, damagetype) * 1.88f * me->CalculateSpellpowerCoefficientLevelPenalty(spellInfo) * stack; + else if (baseId == NOURISH_1) + flat_mod += me->SpellBaseHealingBonusDone(SPELL_SCHOOL_MASK_MAGIC) * 0.2f * me->CalculateDefaultCoefficient(spellInfo, damagetype) * 1.88f * me->CalculateSpellpowerCoefficientLevelPenalty(spellInfo) * stack; + } + //Empowered Rejuvenation: 20% bonus healing for healing over time effects + if ((GetSpec() == BOT_SPEC_DRUID_RESTORATION) && + lvl >= 45 && ((spellInfo->SpellFamilyFlags[0] & 0xD0) || (spellInfo->SpellFamilyFlags[1] & 0x4000010))) + flat_mod += me->SpellBaseHealingBonusDone(SPELL_SCHOOL_MASK_MAGIC) * 0.2f * me->CalculateDefaultCoefficient(spellInfo, damagetype) * 1.88f * me->CalculateSpellpowerCoefficientLevelPenalty(spellInfo) * stack; + + heal = heal * (1.0f + pctbonus) + flat_mod; + } + + void ApplyClassSpellCostMods(SpellInfo const* spellInfo, int32& cost) const override + { + //uint32 spellId = spellInfo->Id; + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + uint8 lvl = me->GetLevel(); + float fcost = float(cost); + int32 flatbonus = 0; + float pctbonus = 0.0f; + + //100% mods + //Clearcasting: -100% mana/rage/energy cost for any spell + if (AuraEffect const* eff = me->GetAuraEffect(OMEN_OF_CLARITY_BUFF, 0, me->GetGUID())) + if (eff->IsAffectingSpell(spellInfo)) + pctbonus += 1.0f; + + //percent mods + //Tree of Life Passive (5420) (activates when learned): + if ((GetSpec() == BOT_SPEC_DRUID_RESTORATION) && + lvl >= 50 && ((spellInfo->SpellFamilyFlags[0] & 0x50) || (spellInfo->SpellFamilyFlags[1] & 0x4000010))) + pctbonus += 0.5f; + //Glyph of the Wild: + if (lvl >= 15 && baseId == MARK_OF_THE_WILD_1) + pctbonus += 0.5f; + //Natural Shapeshifter: + if (lvl >= 15 && + spellInfo->_effects[0].Effect == SPELL_EFFECT_APPLY_AURA && + spellInfo->_effects[0].ApplyAuraName == SPELL_AURA_MOD_SHAPESHIFT) + pctbonus += 0.3f; + //King of the Jungle part 3: + if (lvl >= 15 && (spellInfo->SpellFamilyFlags[0] & 0xC0000000)) + pctbonus += 0.6f; + //Tranquil Spirit: + if (lvl >= 25 && + (baseId == HEALING_TOUCH_1 || baseId == NOURISH_1 || baseId == TRANQUILITY_1)) + pctbonus += 0.1f; + //Moonglow: + if (lvl >= 15 && ((spellInfo->SpellFamilyFlags[0] & 0x77) || (spellInfo->SpellFamilyFlags[1] & 0x2800000))) + pctbonus += 0.09f; + //Berserk part 2: + if ((GetSpec() == BOT_SPEC_DRUID_FERAL) && lvl >= 60 && _form == DRUID_CAT_FORM && + //((spellInfo->SpellFamilyFlags[0] & 0x839000) || + //(spellInfo->SpellFamilyFlags[1] & 0x30000480) || + //(spellInfo->SpellFamilyFlags[2] & 0x40420)) && + me->GetAuraEffect(SPELL_AURA_MECHANIC_IMMUNITY, SPELLFAMILY_DRUID, 0x0, 0x0, 0x40)) + pctbonus += 0.5f; + + //flat mods + //Shredding Attacks: + if (lvl >= 25) + { + if (spellInfo->SpellFamilyFlags[0] & 0x8000) + flatbonus += 18; + else if (spellInfo->SpellFamilyFlags[1] & 0x100) + flatbonus += 20; + } + //Ferocity: + if (lvl >= 10) + { + if ((spellInfo->SpellFamilyFlags[0] & 0x800) || (spellInfo->SpellFamilyFlags[1] & 0x100040)) + flatbonus += 50; + else if ((spellInfo->SpellFamilyFlags[0] & 0x1000) || + (spellInfo->SpellFamilyFlags[1] & 0x400) || + (spellInfo->SpellFamilyFlags[2] & 0x40400)) + flatbonus += 5; + } + //Improved Mangle part 2: + if ((GetSpec() == BOT_SPEC_DRUID_FERAL) && lvl >= 50 && (spellInfo->SpellFamilyFlags[1] & 0x400)) + flatbonus += 6; + + //cost can be < 0 + cost = int32(fcost * (1.0f - pctbonus)) - flatbonus; + } + + void ApplyClassSpellCastTimeMods(SpellInfo const* spellInfo, int32& casttime) const override + { + //casttime is in milliseconds + //uint32 spellId = spellInfo->Id; + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + uint8 lvl = me->GetLevel(); + int32 timebonus = 0; + float pctbonus = 0.0f; + + //100% mods + //Elune's Wrath: -100% cast time for Starfire + AuraEffect const* elun = me->GetAuraEffect(ELUNES_WRATH_BUFF, 0); + //Predator's Swiftness: -100% cast time + AuraEffect const* pred = me->GetAuraEffect(PREDATORS_SWIFTNESS_BUFF, 0); + //Nature's Swiftness: -100% cast time + AuraEffect const* natu = me->GetAuraEffect(NATURES_SWIFTNESS_1, 0); + if ((elun && elun->IsAffectingSpell(spellInfo)) || + (pred && pred->IsAffectingSpell(spellInfo)) || + (natu && natu->IsAffectingSpell(spellInfo))) + pctbonus += 1.0f; + + //pct mods + //Celestial Focus: 3% haste + if (lvl >= 25) + pctbonus += 0.03f; + + //flat mods + //Starlight Wrath: -0.5 sec cast time for Wrath and Starfire + if (lvl >= 10 && (baseId == WRATH_1 || baseId == STARFIRE_1)) + timebonus += 500; + //Naturalist: -0.5 sec cast time for Healing Touch + if (lvl >= 15 && baseId == HEALING_TOUCH_1) + timebonus += 500; + //Regrowth bonus (ids:21872,46834): -0.2 x2 sec cast time for Regrowth + if (lvl >= 68 && baseId == REGROWTH_1) + timebonus += 400; + + casttime = std::max(int32((float(casttime) * (1.0f - pctbonus)) - timebonus), 0); + } + + void ApplyClassSpellCooldownMods(SpellInfo const* spellInfo, uint32& cooldown) const override + { + //cooldown is in milliseconds + //uint32 spellId = spellInfo->Id; + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + uint8 lvl = me->GetLevel(); + int32 timebonus = 0; + float pctbonus = 0.0f; + + //pct mods + //Improved Tranquility: -60% cooldown for Tanquility + if ((GetSpec() == BOT_SPEC_DRUID_RESTORATION) && lvl >= 30 && baseId == TRANQUILITY_1) + pctbonus += 0.6f; + + //flat mods + //Glyph of Turn Evil: +8 sec cooldown for Turn Evil (3 sec for bots) + //if (lvl >= 24 && spellId == GetSpell(TURN_EVIL_1)) + // timebonus -= 3000; + + cooldown = std::max(int32((float(cooldown) * (1.0f - pctbonus)) - timebonus), 0); + } + + void ApplyClassSpellCategoryCooldownMods(SpellInfo const* spellInfo, uint32& cooldown) const override + { + //cooldown is in milliseconds + //uint32 spellId = spellInfo->Id; + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + uint8 lvl = me->GetLevel(); + int32 timebonus = 0; + float pctbonus = 0.0f; + + //pct mods + //Glyph of Dash: -20% cooldown for Dash + if (lvl >= 16 && baseId == DASH_1) + pctbonus += 0.2f; + //Berserk part 1: + if (lvl >= 60 && _form == DRUID_BEAR_FORM && (spellInfo->SpellFamilyFlags[1] & 0x40) && + me->GetAuraEffect(SPELL_AURA_MECHANIC_IMMUNITY, SPELLFAMILY_DRUID, 0x0, 0x0, 0x40)) + pctbonus += 1.0f; + + //flat mods + //Genesis Rebirth Bonus (26106): -5 min cooldown for Rebirth + if (baseId == REBIRTH_1) + timebonus += 300000; + //Improved Mangle part 1: -1.5 sec cooldown for Mangle (Bear) + if (lvl >= 50 && baseId == MANGLE_BEAR_1) + timebonus += 1500; + //Brutal Impact: -30 sec cooldown for Bash + if ((GetSpec() == BOT_SPEC_DRUID_FERAL) && lvl >= 30 && baseId == BASH_1) + timebonus += 30000; + //Glyph of Typhoon: -3 sec cooldown for Typhoon + if (lvl >= 70 && baseId == TYPHOON_1) + timebonus += 3000; + //Starfall: increase cooldown for bots + if (baseId == STARFALL_1) + timebonus -= 90000; //x2 + + cooldown = std::max(int32((float(cooldown) * (1.0f - pctbonus)) - timebonus), 0); + } + + void ApplyClassSpellGlobalCooldownMods(SpellInfo const* spellInfo, float& cooldown) const override + { + //cooldown is in milliseconds + //uint32 spellId = spellInfo->Id; + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + uint8 lvl = me->GetLevel(); + float timebonus = 0.0f; + float pctbonus = 0.0f; + + //pct bonus + //Gift of the Earthmother part 2: + if (lvl >= 55 && baseId == LIFEBLOOM_1) + pctbonus += 0.1f; + + //flat bonus + ////Unrelenting Assault (part 1, special): -0.5 sec global cooldown for Overpower and Revenge (not for tanks) + //if (lvl >= 50 && !IsTank() && (spellId == GetSpell(OVERPOWER_1) || spellId == GetSpell(REVENGE_1))) + // timebonus += 500.f; + + cooldown = (cooldown * (1.0f - pctbonus)) - timebonus; + } + + void ApplyClassSpellRadiusMods(SpellInfo const* spellInfo, float& radius) const override + { + uint32 spellId = spellInfo->Id; + //uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + //uint8 lvl = me->GetLevel(); + float flatbonus = 0.0f; + float pctbonus = 0.0f; + + //pct mods + + //flat mods + //Starfall + if (spellId == STARFALL_DUMMY_AOE_4) + flatbonus -= 10.f; //20 = 36 talented - 18 glyphed + 2 custom + if (spellId == STARFALL_DAMAGE_AOE_4) + flatbonus += 4.f; //5 nominal + 4 custom + + radius = radius * (1.0f + pctbonus) + flatbonus; + } + + void ApplyClassSpellRangeMods(SpellInfo const* spellInfo, float& maxrange) const override + { + //uint32 spellId = spellInfo->Id; + //uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + uint8 lvl = me->GetLevel(); + float flatbonus = 0.0f; + float pctbonus = 0.0f; + + //pct mods + //Nature's Reach: +20% range for Balance Spells and Faerie Fire (Feral) + if (lvl >= 20 && ((spellInfo->SpellFamilyFlags[0] & 0x600707) || (spellInfo->SpellFamilyFlags[1] & 0x1821220))) + pctbonus += 0.2f; + + //flat mods + //Glyph of Curse of Exhaustion: +5 yd range for Curse of Exhaustion + //if (lvl >= 70 && baseId == CURSE_OF_EXHAUSTION_1) + // flatbonus += 5.f; + + maxrange = maxrange * (1.0f + pctbonus) + flatbonus; + } + + void ApplyClassSpellMaxTargetsMods(SpellInfo const* spellInfo, uint32& targets) const override + { + uint32 bonusTargets = 0; + + //Glyph of Maul: + 1 target + if (spellInfo->SpellFamilyFlags[0] & 0x800) + bonusTargets += 1; + //Glyph of Wild Growth: + 1 target + if (spellInfo->SpellFamilyFlags[1] & 0x4000000) + bonusTargets += 1; + //Berserk: + 2 Mangle (Bear) targets + if ((spellInfo->SpellFamilyFlags[1] & 0x40) && + me->GetAuraEffect(SPELL_AURA_MECHANIC_IMMUNITY, SPELLFAMILY_DRUID, 0x0, 0x0, 0x40)) + bonusTargets += 2; + + targets = targets + bonusTargets; + } + + void ApplyClassEffectMods(SpellInfo const* spellInfo, uint8 effIndex, float& value) const override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + float pctbonus = 1.0f; + + //Improved Leader of the Pack: chance + if (baseId == LEADER_OF_THE_PACK_BUFF && effIndex == EFFECT_1) + value += 4.f; + + value = value * pctbonus; + } + + void OnClassSpellGo(SpellInfo const* spellInfo) override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + + //Nature's Swiftness: notify master + if (baseId == NATURES_SWIFTNESS_1) + { + if (!IAmFree()) + ReportSpellCast(NATURES_SWIFTNESS_1, LocalizedNpcText(master, BOT_TEXT__USED), master); + } + + //On next attack spells cooldown handle + //if (baseId == MAUL_1) + // SetSpellCooldown(baseId, me->getAttackTimer(BASE_ATTACK) - 250); + + //Handle clearcasting + //Notes: bugged with hurricane (periodic) + if (AuraEffect const* eff = me->GetAuraEffect(OMEN_OF_CLARITY_BUFF, 0, me->GetGUID())) + if (eff->IsAffectingSpell(spellInfo) && !spellInfo->IsRankOf(sSpellMgr->GetSpellInfo(HURRICANE_DAMAGE_1))) + me->RemoveAurasDueToSpell(OMEN_OF_CLARITY_BUFF); + + //Elune's Wrath: -100% takes priority since only Starfire + AuraEffect const* elun = me->GetAuraEffect(ELUNES_WRATH_BUFF, 0); + //Predator's Swiftness takes priority since duration + AuraEffect const* pred = me->GetAuraEffect(PREDATORS_SWIFTNESS_BUFF, 0); + //Nature's Swiftness + AuraEffect const* natu = me->GetAuraEffect(NATURES_SWIFTNESS_1, 0); + if (elun && elun->IsAffectingSpell(spellInfo)) + me->RemoveAurasDueToSpell(ELUNES_WRATH_BUFF); + else if (pred && pred->IsAffectingSpell(spellInfo)) + me->RemoveAurasDueToSpell(PREDATORS_SWIFTNESS_BUFF); + else if (natu && natu->IsAffectingSpell(spellInfo)) + me->RemoveAurasDueToSpell(NATURES_SWIFTNESS_1); + } + + void SpellHitTarget(WorldObject* wtarget, SpellInfo const* spell) override + { + Unit* target = wtarget->ToUnit(); + if (!target) + return; + + uint32 spellId = spell->Id; + uint32 baseId = spell->GetFirstRankSpell()->Id; + uint8 lvl = me->GetLevel(); + + if (_form == DRUID_CAT_FORM) + { + //Combo point generating + if (baseId == CLAW_1 || baseId == MANGLE_CAT_1 || baseId == POUNCE_1 || + baseId == RAKE_1 || baseId == RAVAGE_1 || baseId == SHRED_1) + { + comboPoints++; + //debug + //TC_LOG_ERROR("entities.player", "druid_bot CP GEN: {} adds 1, now {}", spell->SpellName[0], uint32(comboPoints)); + if (primalFuryProc) + { + comboPoints++; + //debug + //TC_LOG_ERROR("entities.player", "druid_bot CP EX: now {}", uint32(comboPoints)); + } + if (comboPoints > 5) + { + comboPoints = 5; + //debug + //TC_LOG_ERROR("entities.player", "druid_bot CP NOR: now {}", uint32(comboPoints)); + } + } + //Combo point spending + //else if (baseId == FEROCIOUS_BITE_1 || baseId == MAIM_1 || baseId == RIP_1 || baseId == SAVAGE_ROAR_1) + else if (spell->NeedsComboPoints()) + { + //debug + //TC_LOG_ERROR("entities.player", "druid_bot CP SPEND1: {} to 0", uint32(comboPoints)); + if (lvl >= 25 && comboPoints > 0) + { + if (urand(1,100) <= uint32(comboPoints * 20)) + { + me->CastSpell(me, PREDATORS_SWIFTNESS_BUFF, true); + //debug + //TC_LOG_ERROR("entities.player", "druid_bot CP SPEND1: PS proc!"); + } + } + comboPoints = 0; + } + + //Maim helper + if (baseId == MAIM_1) + MoveBehind(target); + } + + //Hibernate helper + if (spellId == GetSpell(HIBERNATE_1)) + { + hibery = true; + hiberyCheckTimer = 2000; + } + + //Bash desperate use (ranged): retreat + //Only if hit + if (baseId == BASH_1 && HasRole(BOT_ROLE_RANGED) && !HasBotCommandState(BOT_COMMAND_MASK_UNMOVING)) + { + //if (GC_Timer <= lastdiff && GetSpell(TRAVEL_FORM_1)) + // doCast(me, GetSpell(TRAVEL_FORM_1)); + GetInPosition(true, target); + } + + //Infected Wound: handle proc + if (baseId == SHRED_1 || baseId == MAUL_1 || baseId == MANGLE_BEAR_1 || baseId == MANGLE_CAT_1) + { + if ((GetSpec() == BOT_SPEC_DRUID_FERAL) && lvl >= 45) + { + CastSpellExtraArgs args(true); + args.SetOriginalCaster(me->GetGUID()); + target->CastSpell(target, INFECTED_WOUNDS_EFFECT, args); + } + } + + //Brutal Impact: +1 sec duration for Bash and Pounce stun + if (baseId == BASH_1 || baseId == POUNCE_1) + { + if (Aura* stu = target->GetAura(spellId)) + { + //1 extra second on creatures + uint32 dur = stu->GetDuration() + target->GetTypeId() == TYPEID_PLAYER ? 1000 : 2000; + stu->SetDuration(dur); + stu->SetMaxDuration(dur); + } + } + + //Glyph of Starfire: Increase max duration of Moonfire and refresh + if (baseId == STARFIRE_1 && GetSpell(MOONFIRE_1)) + { + if (lvl >= 20) + { + if (Aura* aur = target->GetAura(GetSpell(MOONFIRE_1), me->GetGUID())) + { + //extra 9 sec base + 3 sec Nature's Splendor + if (aur->GetMaxDuration() < spell->GetMaxDuration() + 12000) + { + aur->SetDuration(aur->GetDuration() + 3000); + aur->SetMaxDuration(aur->GetMaxDuration() + 3000); + } + } + } + } + //Nature's Splendor: Increased duraion for + //Moonfire (3 sec), Rejuvenation (3 sec, let 6), Regrowth (6 sec, let 9), + //Insect Swarm (2 sec) and Lifebloom (2 sec) + if (baseId == MOONFIRE_1 || baseId == REJUVENATION_1 || baseId == REGROWTH_1 || + baseId == INSECT_SWARM_1 || baseId == LIFEBLOOM_1) + { + if (lvl >= 20) + { + if (Aura* aur = target->GetAura(spellId, me->GetGUID())) + { + uint32 dur = aur->GetDuration(); + + switch (baseId) + { + case MOONFIRE_1: + dur += 3000; + break; + case REJUVENATION_1: + dur += 6000; + break; + case REGROWTH_1: + dur += 9000; + break; + case INSECT_SWARM_1: + case LIFEBLOOM_1: + dur += 2000; + break; + default: + break; + } + + aur->SetDuration(dur); + aur->SetMaxDuration(dur); + } + } + } + + if (baseId == THORNS_1) + { + //30 min duration for Thorns + if (Aura* thorn = target->GetAura(spellId, me->GetGUID())) + { + uint32 dur = 30 * MINUTE * IN_MILLISECONDS; + thorn->SetDuration(dur); + thorn->SetMaxDuration(dur); + } + } + if (baseId == MARK_OF_THE_WILD_1) + { + //1 hour duration for Mark of the Wild + if (Aura* mark = target->GetAura(spellId, me->GetGUID())) + { + uint32 dur = 1 * HOUR * IN_MILLISECONDS; + mark->SetDuration(dur); + mark->SetMaxDuration(dur); + + //Improved Mark of the Wild: +40% effect + for (uint8 i = 0; i != MAX_SPELL_EFFECTS; ++i) + if (AuraEffect* app = mark->GetEffect(i)) + app->ChangeAmount((app->GetAmount() * 14) / 10); + } + } + if ((baseId == FAERIE_FIRE_NORMAL_1 || baseId == FAERIE_FIRE_FERAL_1) && lvl >= 40) + { + //Improved Faerie Fire (part 1): incrase crit chance taken by 3% (effect2) + if (AuraEffect* faf = target->GetAuraEffect(SPELL_AURA_MOD_ATTACKER_SPELL_HIT_CHANCE, SPELLFAMILY_DRUID, 0x400, 0x0, 0x0, me->GetGUID())) + faf->ChangeAmount(faf->GetAmount() + 3); + } + + OnSpellHitTarget(target, spell); + } + + void SpellHit(WorldObject* wcaster, SpellInfo const* spell) override + { + Unit* caster = wcaster->ToUnit(); + if (!caster) + return; + + uint32 spellId = spell->Id; + uint32 baseId = spell->GetFirstRankSpell()->Id; + uint8 lvl = me->GetLevel(); + + //Savage Roar duration + if (spellId == SAVAGE_ROAR_BUFF || spellId == SAVAGE_ROAR_1) + { + if (Aura* sav = me->GetAura(spellId)) + { + uint32 dur = sav->GetDuration() + 6000; + sav->SetDuration(dur); + sav->SetMaxDuration(dur); + } + //Glyph of Savage Roar: 3% increase (20 for bot) + if (spellId == SAVAGE_ROAR_BUFF) + { + AuraEffect* sav = me->GetAuraEffect(spellId, EFFECT_0); + if (sav) + sav->ChangeAmount(sav->GetAmount() + 20); + } + } + + //Glyph of Berserk: duration + if (baseId == BERSERK_1) + { + if (Aura* ber = me->GetAura(spellId)) + { + uint32 dur = ber->GetDuration() + 5000; + ber->SetDuration(dur); + ber->SetMaxDuration(dur); + } + } + //Glyph of Survival Instincts + if (lvl >= 20 && spellId == SURVIVAL_INSTINCTS_1) + { + AuraEffect* sur = me->GetAuraEffect(spellId, EFFECT_0); + if (sur) + sur->ChangeAmount(sur->GetAmount() + 15); + } + //Starfall: duration + if (baseId == STARFALL_1) + { + if (Aura* sta = me->GetAura(spellId)) + { + uint32 dur = sta->GetDuration() * 3; + sta->SetDuration(dur); + sta->SetMaxDuration(dur); + } + } + //Predatoe's Swiftness: duration + if (spellId == PREDATORS_SWIFTNESS_BUFF) + { + if (Aura* swi = me->GetAura(spellId)) + { + swi->SetDuration(15000); + swi->SetMaxDuration(15000); + } + } + //Eclipse (helper): cooldown + if (spellId == ECLIPSE_SOLAR_BUFF || spellId == ECLIPSE_LUNAR_BUFF) + SetSpellCooldown(spellId, 30000); + //Improved Leader of the Pack: handle SPELLMOD_EFFECT_2 MOVED to effect mods + /* + if (spellId == LEADER_OF_THE_PACK_BUFF) + { + AuraEffect* lea = me->GetAuraEffect(spellId, EFFECT_1, me->GetGUID()); + if (lea) + lea->ChangeAmount(lea->GetAmount() + 4); + } + */ + //Improved Barkskin: 10% additional damage reduction + if ((GetSpec() == BOT_SPEC_DRUID_RESTORATION) && baseId == BARKSKIN_1) + { + AuraEffect* bar = me->GetAuraEffect(spellId, EFFECT_1, me->GetGUID()); + if (bar) + bar->ChangeAmount(bar->GetAmount() - 10); + } + + //Forms helper + if (baseId == BEAR_FORM_1) + setStats(DRUID_BEAR_FORM); + else if (baseId == CAT_FORM_1) + setStats(DRUID_CAT_FORM); + else if (baseId == MOONKIN_FORM_1) + setStats(DRUID_MOONKIN_FORM); + else if (baseId == TREE_OF_LIFE_FORM_1) + setStats(DRUID_TREE_FORM); + else if (baseId == TRAVEL_FORM_1) + setStats(DRUID_TRAVEL_FORM); + else if (baseId == AQUATIC_FORM_1) + setStats(DRUID_AQUATIC_FORM); + //else if (baseId == FLIGHT_FORM_1) + // setStats(DRUID_FLIGHT_FORM); + + //Cat Form: delay prowl just a little bit + if (baseId == CAT_FORM_1 && GetSpell(PROWL_1) && GetSpellCooldown(PROWL_1) < 300) + SetSpellCooldown(PROWL_1, 300); + + //Leader of the Pack helper + if (baseId == CAT_FORM_1 || baseId == BEAR_FORM_1) + { + if ((GetSpec() == BOT_SPEC_DRUID_FERAL) && lvl >= 40) + me->CastSpell(me, LEADER_OF_THE_PACK_BUFF, true); + } + + OnSpellHit(caster, spell); + } + + void DamageDealt(Unit* victim, uint32& damage, DamageEffectType damageType) override + { + bot_ai::DamageDealt(victim, damage, damageType); + } + + void DamageTaken(Unit* u, uint32& /*damage*/, DamageEffectType /*damageType*/, SpellInfo const* /*spellInfo*/) override + { + if (!u) + return; + if (!u->IsInCombat() && !me->IsInCombat()) + return; + OnOwnerDamagedBy(u); + } + + void OwnerAttackedBy(Unit* u) override + { + if (!u) + return; + OnOwnerDamagedBy(u); + } + + float GetSpellAttackRange(bool longRange) const override + { + return longRange ? CalcSpellMaxRange(WRATH_1) : 20.f; + } + + uint8 GetPetPositionNumber(Creature const* summon) const override + { + for (uint8 i = 0; i != MAX_TREANTS; ++i) + if (_treants[i] == summon->GetGUID()) + return i; + + return 0; + } + + void SummonBotPet(Unit* target) + { + UnsummonTreants(); + + uint32 entry = BOT_PET_FORCE_OF_NATURE; + + for (uint8 i = 0; i != MAX_TREANTS; ++i) + { + //Position pos; + + //30 sec duration + Creature* myPet = me->SummonCreature(entry, (me->GetDistance(target) < 36.f ? *target : *me), TEMPSUMMON_CORPSE_TIMED_DESPAWN, 5s); + //me->GetNearPoint(myPet, pos.m_positionX, pos.m_positionY, pos.m_positionZ, 0, 2, me->GetOrientation()); + //myPet->GetMotionMaster()->MovePoint(me->GetMapId(), pos); + myPet->SetCreator(master); + myPet->SetOwnerGUID(me->GetGUID()); + myPet->SetFaction(master->GetFaction()); + myPet->SetControlledByPlayer(!IAmFree()); + myPet->SetPvP(me->IsPvP()); + myPet->SetByteValue(UNIT_FIELD_BYTES_2, 1, master->GetByteValue(UNIT_FIELD_BYTES_2, 1)); + myPet->SetUInt32Value(UNIT_CREATED_BY_SPELL, FORCE_OF_NATURE_1); + //botPet = myPet; + + myPet->Attack(target, true); + if (!HasBotCommandState(BOT_COMMAND_MASK_UNCHASE)) + myPet->GetMotionMaster()->MoveChase(target); + } + } + + void JustSummoned(Creature* summon) override + { + if (summon->GetEntry() == BOT_PET_FORCE_OF_NATURE) + { + bool found = false; + for (uint8 i = 0; i != MAX_TREANTS; ++i) + { + if (!_treants[i]) + { + _treants[i] = summon->GetGUID(); + found = true; + break; + } + } + if (!found) + { + TC_LOG_ERROR("entities.unit", "Druid_bot:JustSummoned() treants array is full"); + ASSERT(false); + } + } + } + + void SummonedCreatureDespawn(Creature* summon) override + { + //TC_LOG_ERROR("entities.unit", "SummonedCreatureDespawn: {}'s {}", me->GetName(), summon->GetName()); + //if (summon == botPet) + // botPet = nullptr; + if (summon->GetEntry() == BOT_PET_FORCE_OF_NATURE) + { + //bool found = false; + for (uint8 i = 0; i != MAX_TREANTS; ++i) + { + if (_treants[i] == summon->GetGUID()) + { + _treants[i] = ObjectGuid::Empty; + //found = true; + break; + } + } + //if (!found) + //{ + // TC_LOG_ERROR("entities.unit", "Druid_bot:SummonedCreatureDespawn() treant is not found in array"); + // ASSERT(false); + //} + } + } + + void UnsummonTreants() + { + for (uint8 i = 0; i != MAX_TREANTS; ++i) + { + if (_treants[i]) + { + if (Unit* tr = ObjectAccessor::GetUnit(*me, _treants[i])) + tr->ToTempSummon()->UnSummon(); + else + _treants[i] = ObjectGuid::Empty; + } + } + } + + void UnsummonAll(bool /*savePets*/ = true) override + { + for (uint8 i = 0; i != MAX_TREANTS; ++i) + { + if (_treants[i]) + if (Unit* tr = ObjectAccessor::GetUnit(*me, _treants[i])) + tr->ToTempSummon()->UnSummon(); + } + } + + uint32 GetAIMiscValue(uint32 data) const override + { + switch (data) + { + case BOTAI_MISC_COMBO_POINTS: + return comboPoints; + case BOTAI_MISC_PET_TYPE: + return BOT_PET_FORCE_OF_NATURE; + default: + return 0; + } + } + + void Reset() override + { + UnsummonAll(false); + for (uint8 i = 0; i != MAX_TREANTS; ++i) + _treants[i] = ObjectGuid::Empty; + + //_form = BOT_STANCE_NONE; + rage = 0; + removeShapeshiftForm(); + + energy = 0; + comboPoints = 0; + primalFuryProc = false; + + hibery = false; + hiberyCheckTimer = 0; + + me->SetMaxPower(POWER_ENERGY, 100); //for regeneration + rageLossMult = sWorld->getRate(RATE_POWER_RAGE_LOSS); + + ragetimer = 0; + + DefaultInit(); + } + + void ReduceCD(uint32 diff) override + { + if (ragetimer > diff) ragetimer -= diff; + + if (hiberyCheckTimer > diff) hiberyCheckTimer -= diff; + } + + void InitPowers() override + { + switch (me->GetShapeshiftForm()) + { + case FORM_BEAR: + case FORM_DIREBEAR: + me->SetPowerType(POWER_RAGE); + break; + case FORM_CAT: + me->SetPowerType(POWER_ENERGY); + break; + default: + me->SetPowerType(POWER_MANA); + break; + } + } + + void InitSpells() override + { + uint8 lvl = me->GetLevel(); + bool isBala = GetSpec() == BOT_SPEC_DRUID_BALANCE; + bool isFera = GetSpec() == BOT_SPEC_DRUID_FERAL; + bool isRest = GetSpec() == BOT_SPEC_DRUID_RESTORATION; + + InitSpellMap(MARK_OF_THE_WILD_1); + InitSpellMap(THORNS_1); + InitSpellMap(HEALING_TOUCH_1); + InitSpellMap(REGROWTH_1); + InitSpellMap(REJUVENATION_1); + InitSpellMap(LIFEBLOOM_1); + InitSpellMap(NOURISH_1); + InitSpellMap(TRANQUILITY_1); + InitSpellMap(REVIVE_1); + InitSpellMap(REBIRTH_1); + InitSpellMap(BEAR_FORM_1); + InitSpellMap(SWIPE_BEAR_1); + InitSpellMap(BASH_1); + InitSpellMap(MAUL_1); + InitSpellMap(CHALLENGING_ROAR_1); + InitSpellMap(ENRAGE_1); + InitSpellMap(FRENZIED_REGENERATION_1); + InitSpellMap(GROWL_1); + InitSpellMap(LACERATE_1); + InitSpellMap(SURVIVAL_INSTINCTS_1); + InitSpellMap(FAERIE_FIRE_FERAL_1); + InitSpellMap(CAT_FORM_1); + InitSpellMap(CLAW_1); + InitSpellMap(RAKE_1); + InitSpellMap(SHRED_1); + InitSpellMap(POUNCE_1); + InitSpellMap(RAVAGE_1); + InitSpellMap(RIP_1); + InitSpellMap(FEROCIOUS_BITE_1); + InitSpellMap(MAIM_1); + InitSpellMap(SWIPE_CAT_1); + InitSpellMap(SAVAGE_ROAR_1); + InitSpellMap(TIGERS_FURY_1); + InitSpellMap(COWER_1); + InitSpellMap(DASH_1); + /*custom*/lvl >= 22 ? InitSpellMap(PROWL_1) : RemoveSpell(PROWL_1); //base lvl 20 + InitSpellMap(MOONFIRE_1); + InitSpellMap(STARFIRE_1); + InitSpellMap(WRATH_1); + InitSpellMap(HURRICANE_1); + InitSpellMap(FAERIE_FIRE_NORMAL_1); + InitSpellMap(TRAVEL_FORM_1); + InitSpellMap(AQUATIC_FORM_1); + InitSpellMap(CURE_POISON_1); + InitSpellMap(ABOLISH_POISON_1); + InitSpellMap(REMOVE_CURSE_1); + InitSpellMap(ENTANGLING_ROOTS_1); + InitSpellMap(CYCLONE_1); + InitSpellMap(HIBERNATE_1); + InitSpellMap(BARKSKIN_1); + InitSpellMap(NATURES_GRASP_1); + InitSpellMap(INNERVATE_1); + + /*tal*/lvl >= 30 && isBala ? InitSpellMap(INSECT_SWARM_1) : RemoveSpell(INSECT_SWARM_1); + /*tal*/lvl >= 40 && isBala ? InitSpellMap(MOONKIN_FORM_1) : RemoveSpell(MOONKIN_FORM_1); + /*tal*/lvl >= 50 && isBala ? InitSpellMap(TYPHOON_1) : RemoveSpell(TYPHOON_1); + /*tal*/lvl >= 50 && isBala ? InitSpellMap(FORCE_OF_NATURE_1) : RemoveSpell(FORCE_OF_NATURE_1); //not casted + /*tal*/lvl >= 60 && isBala ? InitSpellMap(STARFALL_1) : RemoveSpell(STARFALL_1); + + /*tal*/lvl >= 30 && isFera ? InitSpellMap(FERAL_CHARGE_BEAR_1) : RemoveSpell(FERAL_CHARGE_BEAR_1); + /*tal*/lvl >= 30 && isFera ? InitSpellMap(FERAL_CHARGE_CAT_1) : RemoveSpell(FERAL_CHARGE_CAT_1); + /*tal*/lvl >= 50 && isFera ? InitSpellMap(MANGLE_BEAR_1) : RemoveSpell(MANGLE_BEAR_1); + /*tal*/lvl >= 50 && isFera ? InitSpellMap(MANGLE_CAT_1) : RemoveSpell(MANGLE_CAT_1); + /*tal*/lvl >= 60 && isFera ? InitSpellMap(BERSERK_1) : RemoveSpell(BERSERK_1); + + /*tal*/lvl >= 30 && isRest ? InitSpellMap(NATURES_SWIFTNESS_1) : RemoveSpell(NATURES_SWIFTNESS_1); + /*tal*/lvl >= 40 && isRest ? InitSpellMap(SWIFTMEND_1) : RemoveSpell(SWIFTMEND_1); + /*tal*/lvl >= 50 && isRest ? InitSpellMap(TREE_OF_LIFE_FORM_1) : RemoveSpell(TREE_OF_LIFE_FORM_1); + /*tal*/lvl >= 60 && isRest ? InitSpellMap(WILD_GROWTH_1) : RemoveSpell(WILD_GROWTH_1); + } + + void ApplyClassPassives() const override + { + uint8 level = master->GetLevel(); + bool isBala = GetSpec() == BOT_SPEC_DRUID_BALANCE; + bool isFera = GetSpec() == BOT_SPEC_DRUID_FERAL; + bool isRest = GetSpec() == BOT_SPEC_DRUID_RESTORATION; + + RefreshAura(NATURESGRACE, level >= 20 ? 1 : 0); + RefreshAura(DREAMSTATE, isBala && level >= 35 ? 1 : 0); + RefreshAura(BALANCE_OF_POWER, isBala && level >= 35 ? 1 : 0); + RefreshAura(IMPROVED_MOONKIN_FORM, !IAmFree() && isBala && level >= 40 ? 1 : 0); + RefreshAura(ECLIPSE, isBala && level >= 50 ? 1 : 0); + RefreshAura(EARTH_AND_MOON, isBala && level >= 55 ? 1 : 0); + + RefreshAura(PRIMAL_PRECISION, isFera && level >= 25 ? 1 : 0); + RefreshAura(SURVIVAL_OF_THE_FITTEST, isFera && level >= 35 ? 1 : 0); + RefreshAura(IMPROVED_LEADER_OF_THE_PACK, isFera && level >= 40 ? 1 : 0); + RefreshAura(PRIMAL_TENACITY, isFera && level >= 40 ? 1 : 0); + RefreshAura(KING_OF_THE_JUNGLE, isFera && level >= 50 ? 1 : 0); + RefreshAura(PRIMAL_GORE, isFera && level >= 55 ? 1 : 0); + + RefreshAura(IMPROVED_MARK_OF_THE_WILD, level >= 10 ? 1 : 0); + RefreshAura(FUROR, level >= 10 ? 1 : 0); + RefreshAura(NATURALIST, level >= 15 ? 1 : 0); + RefreshAura(INTENSITY, level >= 20 ? 1 : 0); + RefreshAura(OMEN_OF_CLARITY, level >= 20 ? 1 : 0); + RefreshAura(NATURAL_PERFECTION3, isRest && level >= 45 ? 1 : 0); + RefreshAura(NATURAL_PERFECTION2, isRest && level >= 43 && level < 45 ? 1 : 0); + RefreshAura(NATURAL_PERFECTION1, isRest && level >= 41 && level < 43 ? 1 : 0); + RefreshAura(LIVING_SEED3, isRest && level >= 50 ? 1 : 0); + RefreshAura(LIVING_SEED2, isRest && level >= 48 && level < 50 ? 1 : 0); + RefreshAura(LIVING_SEED1, isRest && level >= 46 && level < 48 ? 1 : 0); + RefreshAura(REVITALIZE3, isRest && level >= 55 ? 1 : 0); + RefreshAura(REVITALIZE2, isRest && level >= 53 && level < 55 ? 1 : 0); + RefreshAura(REVITALIZE1, isRest && level >= 51 && level < 53 ? 1 : 0); + RefreshAura(GIFT_OF_THE_EARTHMOTHER, isRest && level >= 55 ? 1 : 0); + + RefreshAura(GLYPH_NOURISH, level >= 80 ? 1 : 0); + RefreshAura(GLYPH_SWIFTMEND, level >= 45 ? 1 : 0); + RefreshAura(GLYPH_INNERVATE, level >= 40 ? 1 : 0); + RefreshAura(GLYPH_RAPID_REJUVENATION, level >= 15 ? 1 : 0); + RefreshAura(GLYPH_REGROWTH, level >= 15 ? 1 : 0); + RefreshAura(GLYPH_REJUVENATION, level >= 15 ? 1 : 0); + RefreshAura(GLYPH_FRENZIED_REGENERATION, level >= 36 ? 1 : 0); + RefreshAura(GLYPH_BARKSKIN, level >= 44 ? 1 : 0); + RefreshAura(GLYPH_RAKE, level >= 24 ? 1 : 0); + RefreshAura(GLYPH_SHRED, level >= 22 ? 1 : 0); + RefreshAura(T10_RESTO_P4_BONUS, level >= 80 ? 1 : 0); + RefreshAura(T9_RESTO_P4_BONUS, level >= 80 ? 1 : 0); + RefreshAura(T8_RESTO_P4_BONUS, level >= 78 ? 1 : 0); + RefreshAura(T9_BALANCE_P2_BONUS, level >= 78 ? 1 : 0); + RefreshAura(T10_BALANCE_P2_BONUS, level >= 78 ? 1 : 0); + RefreshAura(T10_BALANCE_P4_BONUS, level >= 78 ? 1 : 0); + RefreshAura(T8_FERAL_P2_BONUS, level >= 78 ? 1 : 0); + RefreshAura(T10_FERAL_P4_BONUS, level >= 80 ? 1 : 0); + } + + bool CanUseManually(uint32 basespell) const override + { + switch (basespell) + { + case MARK_OF_THE_WILD_1: + case THORNS_1: + case HEALING_TOUCH_1: + case REJUVENATION_1: + case LIFEBLOOM_1: + case REGROWTH_1: + case NOURISH_1: + case WILD_GROWTH_1: + case SWIFTMEND_1: + case TRANQUILITY_1: + case HURRICANE_1: + case INNERVATE_1: + case ABOLISH_POISON_1: + case NATURES_GRASP_1: + case BARKSKIN_1: + case BEAR_FORM_1: + case CAT_FORM_1: + case TRAVEL_FORM_1: + return true; + case AQUATIC_FORM_1: + return me->HasUnitMovementFlag(MOVEMENTFLAG_SWIMMING) && me->IsUnderWater(); + case TYPHOON_1: + case STARFALL_1: + case MOONKIN_FORM_1: + return (GetBotRoles() & BOT_ROLE_MASK_MAIN) == (BOT_ROLE_DPS|BOT_ROLE_RANGED); + case TREE_OF_LIFE_FORM_1: + return ((GetBotRoles() & BOT_ROLE_MASK_MAIN) & ~BOT_ROLE_RANGED) == BOT_ROLE_HEAL; + case SURVIVAL_INSTINCTS_1: + case FRENZIED_REGENERATION_1: + case ENRAGE_1: + case CHALLENGING_ROAR_1: + return _form == DRUID_BEAR_FORM; + case TIGERS_FURY_1: + case DASH_1: + case PROWL_1: + return _form == DRUID_CAT_FORM; + case BERSERK_1: + return (_form == DRUID_BEAR_FORM || _form == DRUID_CAT_FORM) && + (((GetBotRoles() & BOT_ROLE_MASK_MAIN) & BOT_ROLE_TANK) || (GetBotRoles() & BOT_ROLE_MASK_MAIN) == BOT_ROLE_DPS); + default: + return false; + } + } + + std::vector const* GetDamagingSpellsList() const override + { + return &Druid_spells_damage; + } + std::vector const* GetCCSpellsList() const override + { + return &Druid_spells_cc; + } + std::vector const* GetHealingSpellsList() const override + { + return &Druid_spells_heal; + } + std::vector const* GetSupportSpellsList() const override + { + return &Druid_spells_support; + } + + void InitHeals() override + { + SpellInfo const* spellInfo; + if (InitSpell(me, NOURISH_1)) + { + spellInfo = sSpellMgr->GetSpellInfo(InitSpell(me, NOURISH_1)); + _heals[NOURISH_1] = me->SpellHealingBonusDone(me, spellInfo, spellInfo->_effects[0].CalcValue(me), HEAL, spellInfo->GetEffect(EFFECT_0), {}); + } + else + _heals[NOURISH_1] = 0; + + if (InitSpell(me, REGROWTH_1)) + { + spellInfo = sSpellMgr->GetSpellInfo(InitSpell(me, REGROWTH_1)); + _heals[REGROWTH_1] = me->SpellHealingBonusDone(me, spellInfo, spellInfo->_effects[0].CalcValue(me), HEAL, spellInfo->GetEffect(EFFECT_0), {}); + } + else + _heals[REGROWTH_1] = 0; + + if (InitSpell(me, HEALING_TOUCH_1)) + { + spellInfo = sSpellMgr->GetSpellInfo(InitSpell(me, HEALING_TOUCH_1)); + _heals[HEALING_TOUCH_1] = me->SpellHealingBonusDone(me, spellInfo, spellInfo->_effects[0].CalcValue(me), HEAL, spellInfo->GetEffect(EFFECT_0), {}); + } + else + _heals[HEALING_TOUCH_1] = 0; + + if (InitSpell(me, REJUVENATION_1)) + { + spellInfo = sSpellMgr->GetSpellInfo(InitSpell(me, REJUVENATION_1)); + _heals[REJUVENATION_1] = me->SpellHealingBonusDone(me, spellInfo, spellInfo->_effects[0].CalcValue(me), DOT, spellInfo->GetEffect(EFFECT_0), {}); + } + else + _heals[REJUVENATION_1] = 0; + } + + private: + //Treants + ObjectGuid _treants[MAX_TREANTS]; + //Timers/other +/*Form*/BotStances _form; +/*Misc*/mutable bool primalFuryProc; +/*Misc*/uint8 comboPoints; +/*Misc*/uint32 ragetimer; + bool hibery; + uint32 hiberyCheckTimer; +/*Misc*/int32 rage, energy; + + typedef std::unordered_map HealMap; + HealMap _heals; + }; +}; + +void AddSC_druid_bot() +{ + new druid_bot(); +} diff --git a/src/server/game/AI/NpcBots/bot_hunter_ai.cpp b/src/server/game/AI/NpcBots/bot_hunter_ai.cpp new file mode 100644 index 000000000..875428cf0 --- /dev/null +++ b/src/server/game/AI/NpcBots/bot_hunter_ai.cpp @@ -0,0 +1,2253 @@ +#include "bot_ai.h" +#include "botmgr.h" +#include "botspell.h" +#include "bottext.h" +#include "bottraits.h" +#include "Containers.h" +#include "GridNotifiersImpl.h" +#include "Group.h" +#include "Map.h" +#include "MotionMaster.h" +#include "ObjectAccessor.h" +#include "Player.h" +#include "ScriptMgr.h" +#include "Spell.h" +#include "SpellAuraEffects.h" +#include "SpellMgr.h" +#include "TemporarySummon.h" +#include "World.h" +/* +Hunter NpcBot (reworked by Trickerer onlysuffering@gmail.com) +Complete - around 95% +TODO: Master's Call +*/ + +enum HunterBaseSpells +{ + AUTO_SHOT_1 = 75, + ARCANE_SHOT_1 = 3044, + TRANQ_SHOT_1 = 19801, + BLACK_ARROW_1 = 3674, + SILENCING_SHOT_1 = 34490, + CHIMERA_SHOT_1 = 53209, + AIMED_SHOT_1 = 19434, + STEADY_SHOT_1 = 56641, + EXPLOSIVE_SHOT_1 = 53301, + KILL_SHOT_1 = 53351, + MULTISHOT_1 = 2643, + VOLLEY_1 = 1510, + SCATTER_SHOT_1 = 19503, + CONCUSSIVE_SHOT_1 = 5116, + DISTRACTING_SHOT_1 = 20736, + SERPENT_STING_1 = 1978, + SCORPID_STING_1 = 3043, + VIPER_STING_1 = 3034, + RAPID_FIRE_1 = 3045, + FLARE_1 = 1543, + TRUESHOT_AURA_1 = 19506, + WYVERN_STING_1 = 19386, + WING_CLIP_1 = 2974, + RAPTOR_STRIKE_1 = 2973, + MONGOOSE_BITE_1 = 1495, + COUNTERATTACK_1 = 19306, + DISENGAGE_1 = 781, + IMMOLATION_TRAP_1 = 13795, + FREEZING_TRAP_1 = 1499, + FROST_TRAP_1 = 13809, + EXPLOSIVE_TRAP_1 = 13813, + FREEZING_ARROW_1 = 60192, + SNAKE_TRAP_1 = 34600,//NIY + HUNTERS_MARK_1 = 1130, + SCARE_BEAST_1 = 1513, + FEIGN_DEATH_1 = 5384, + READINESS_1 = 23989, + DETERRENCE_1 = 19263, + MISDIRECTION_1 = 34477, + MEND_PET_1 = 136, + + ASPECT_OF_THE_MONKEY_1 = 13163, + ASPECT_OF_THE_HAWK_1 = 13165, + ASPECT_OF_THE_CHEETAH_1 = 5118, + ASPECT_OF_THE_VIPER_1 = 34074, + ASPECT_OF_THE_BEAST_1 = 13161,//NIY + ASPECT_OF_THE_PACK_1 = 13159, + ASPECT_OF_THE_WILD_1 = 20043, + ASPECT_OF_THE_DRAGONHAWK_1 = 61846 +}; + +enum HunterPassives +{ +//Talents + SUREFOOTED = 24283,//rank 3 + ENTRAPMENT = 19388,//rank 3 + RAPID_KILLING = 34949,//rank 2 + IMPROVED_MEND_PET = 19573,//rank 2 + LOCK_AND_LOAD = 56344,//rank 3 + CONCUSSIVE_BARRAGE = 35102,//rank 2 + PIERCING_SHOTS = 53238,//rank 3 + EXPOSE_WEAKNESS = 34503,//rank 3 + THRILL_OF_THE_HUNT = 34499,//rank 3 + MASTER_MARKSMAN = 34489,//rank 5 + MASTER_TACTICIAN1 = 34506, + MASTER_TACTICIAN2 = 34507, + MASTER_TACTICIAN3 = 34508, + MASTER_TACTICIAN4 = 34838, + MASTER_TACTICIAN5 = 34839, + NOXIOUS_STINGS = 53297,//rank 3 + WILD_QUIVER = 53217,//rank 3 + SNIPER_TRAINING = 53304,//rank 3 + + GLYPH_RAPTOR_STRIKE = 63086, + GLYPH_ASPECT_OF_THE_VIPER = 56851, + GLYPH_FREEZING_TRAP = 56845, + GLYPH_EXPLOSIVE_TRAP = 63068, + + HUNTER_T8_P2 = 67150,//serpent sting crits + HUNTER_T10_P2 = 70727,//autoshot 15% dmg proc + HUNTER_T10_P4 = 70730 //sting 20% ap proc +}; + +enum HunterSpecial +{ + ASPECT_NONE = 0, + ASPECT_MONKEY = 1, + ASPECT_HAWK = 2, + ASPECT_CHEETAH = 3, + ASPECT_VIPER = 4, + ASPECT_BEAST = 5, + ASPECT_PACK = 6, + ASPECT_WILD = 7, + ASPECT_DRAGONHAWK = 8, + + SPECIFIC_ASPECT_MONKEY = 0x001, + SPECIFIC_ASPECT_HAWK = 0x002, + SPECIFIC_ASPECT_CHEETAH = 0x004, + SPECIFIC_ASPECT_VIPER = 0x008, + SPECIFIC_ASPECT_BEAST = 0x010, + SPECIFIC_ASPECT_PACK = 0x020, + SPECIFIC_ASPECT_WILD = 0x040, + SPECIFIC_ASPECT_DRAGONHAWK = 0x080, + SPECIFIC_ASPECT_MY_ASPECT = 0x100, + //SPECIFIC_ASPECT_ALL_AREA_AUTOUSE = (SPECIFIC_ASPECT_PACK), + + IMPROVED_CONCUSSION = 28445, + IMPROVED_WING_CLIP_NORMAL = 47168, + IMPROVED_WING_CLIP_EX = 35963, + + VOLLEY_DAMAGE_1 = 42243,//rank 1 + //EXPLOSIVE_TRAP_DAMAGE_1 = 13812,//same as EXPLOSIVE_TRAP_AURA_1 + + QUICK_SHOTS_BUFF = 6150, + RAPID_KILLING_BUFF = 35099,//rank 2 + LOCK_AND_LOAD_BUFF = 56453,//rank 3 + SNIPER_TRAINING_BUFF = 64420,//rank 3 + RAPID_RECUPERATION_BUFF = 54227,//rank 2, rapid fire, hidden + RAPID_RECUPERATION_BUFF2 = 58882,//rapid killing, 6 sec + IMPROVED_STEADY_SHOT_BUFF = 53220, + + FROST_TRAP_AURA = 13810, + FREEZING_TRAP_AURA_1 = 3355, + IMMOLATION_TRAP_AURA_1 = 13797, + EXPLOSIVE_TRAP_AURA_1 = 13812,//same as EXPLOSIVE_TRAP_DAMAGE_1 + WYVERN_STING_DOT_AURA_1 = 24131, + FREEZING_ARROW_AURA = 60210, + EXPLOSIVE_SHOT_PERIODIC_DUMMY_AURA = 53352, + + ASPECT_OF_THE_DRAGONHAWK_MONKEY = 61848, //linked, hidden + + GLYPH_OF_ARCANE_SHOT_ENERGIZE = 61389, + RAPID_RECUPERATION_ENERGIZE = 58883,//rapid killing + RAPID_RECUPERATION_ENERGIZE_PCT_1 = 64180,//rank 1, 2% + + GIFT_OF_NAARU_HUNTER = 59543, + + SPIRIT_BOND_PET = 24529, + KINDRED_SPIRITS_PET = 57475, + //INTIMIDATION_1 = 24394, + BESTIAL_WRATH_1 = 19574, + //BEAST_WITHIN_1 = 34471, + + GO_FOR_THE_THROAT_ENERGIZE = 34953, + FRENZY_BUFF = 19615 +}; +//talent tiers 20-32-44-56-68-80 + +static const uint32 Hunter_spells_damage_arr[] = +{ AIMED_SHOT_1, ARCANE_SHOT_1, BLACK_ARROW_1, COUNTERATTACK_1, CHIMERA_SHOT_1, EXPLOSIVE_SHOT_1, EXPLOSIVE_TRAP_1, +IMMOLATION_TRAP_1, KILL_SHOT_1, MONGOOSE_BITE_1, MULTISHOT_1, RAPTOR_STRIKE_1, SCATTER_SHOT_1, SERPENT_STING_1, +STEADY_SHOT_1, VOLLEY_1, WYVERN_STING_1 }; + +static const uint32 Hunter_spells_cc_arr[] = +{ CONCUSSIVE_SHOT_1, FREEZING_ARROW_1, FREEZING_TRAP_1, FROST_TRAP_1, SCARE_BEAST_1, SCATTER_SHOT_1, +SILENCING_SHOT_1, WING_CLIP_1, WYVERN_STING_1 }; + +static const uint32 Hunter_spells_support_arr[] = +{ /*ASPECT_OF_THE_BEAST_1, */ASPECT_OF_THE_MONKEY_1, ASPECT_OF_THE_HAWK_1, ASPECT_OF_THE_DRAGONHAWK_1, +ASPECT_OF_THE_CHEETAH_1, ASPECT_OF_THE_PACK_1, ASPECT_OF_THE_VIPER_1, ASPECT_OF_THE_WILD_1, +DETERRENCE_1, DISENGAGE_1, DISTRACTING_SHOT_1, FEIGN_DEATH_1, FLARE_1, HUNTERS_MARK_1, MEND_PET_1, +MISDIRECTION_1, RAPID_FIRE_1, READINESS_1, SCORPID_STING_1, /*SNAKE_TRAP_1, */TRANQ_SHOT_1, VIPER_STING_1 }; + +static const std::vector Hunter_spells_damage(FROM_ARRAY(Hunter_spells_damage_arr)); +static const std::vector Hunter_spells_cc(FROM_ARRAY(Hunter_spells_cc_arr)); +static const std::vector Hunter_spells_support(FROM_ARRAY(Hunter_spells_support_arr)); + +class hunter_bot : public CreatureScript +{ +public: + hunter_bot() : CreatureScript("hunter_bot") { } + + CreatureAI* GetAI(Creature* creature) const override + { + return new hunter_botAI(creature); + } +/* + bool OnGossipHello(Player* player, Creature* creature) + { + return creature->GetBotAI()->OnGossipHello(player, 0); + } + + bool OnGossipSelect(Player* player, Creature* creature, uint32 sender, uint32 action) + { + if (bot_ai* ai = creature->GetBotAI()) + return ai->OnGossipSelect(player, creature, sender, action); + return true; + } + + bool OnGossipSelectCode(Player* player, Creature* creature, uint32 sender, uint32 action, char const* code) + { + if (bot_ai* ai = creature->GetBotAI()) + return ai->OnGossipSelectCode(player, creature, sender, action, code); + return true; + } +*/ + struct hunter_botAI : public bot_ai + { + hunter_botAI(Creature* creature) : bot_ai(creature) + { + _botclass = BOT_CLASS_HUNTER; + + InitUnitFlags(); + } + + bool doCast(Unit* victim, uint32 spellId) + { + if (CheckBotCast(victim, spellId) != SPELL_CAST_OK) + return false; + + return bot_ai::doCast(victim, spellId); + } + + void StartAttack(Unit* u, bool force = false) + { + if (!bot_ai::StartAttack(u, force)) + return; + GetInPosition(force, u); + } + + void JustEnteredCombat(Unit* u) override { aspectTimer = 0; bot_ai::JustEnteredCombat(u); } + void KilledUnit(Unit* u) override { bot_ai::KilledUnit(u); } + void EnterEvadeMode(EvadeReason why = EVADE_REASON_OTHER) override { bot_ai::EnterEvadeMode(why); } + void MoveInLineOfSight(Unit* u) override { bot_ai::MoveInLineOfSight(u); } + void JustDied(Unit* u) override { Aspect = 0; UnsummonAll(false); bot_ai::JustDied(u); } + void DoNonCombatActions(uint32 /*diff*/) { } + + void CheckAspects(uint32 diff) + { + if (aspectTimer > diff || me->IsMounted() || Feasting() || IsCasting() || Rand() > 55) + return; + + aspectTimer = urand(5000, 10000); + + if (Aspect == ASPECT_VIPER && GetManaPCT(me) < 50) + return; + + uint32 ASPECT_OF_THE_MONKEY = GetSpell(ASPECT_OF_THE_MONKEY_1); + uint32 ASPECT_OF_THE_HAWK = GetSpell(ASPECT_OF_THE_HAWK_1); + uint32 ASPECT_OF_THE_CHEETAH = GetSpell(ASPECT_OF_THE_CHEETAH_1); + uint32 ASPECT_OF_THE_VIPER = GetSpell(ASPECT_OF_THE_VIPER_1); + //uint32 ASPECT_OF_THE_BEAST = GetSpell(ASPECT_OF_THE_BEAST_1); + uint32 ASPECT_OF_THE_PACK = GetSpell(ASPECT_OF_THE_PACK_1); + uint32 ASPECT_OF_THE_WILD = GetSpell(ASPECT_OF_THE_WILD_1); + uint32 ASPECT_OF_THE_DRAGONHAWK = GetSpell(ASPECT_OF_THE_DRAGONHAWK_1); + + std::map idMap; + uint32 mask = _getAspectsMask(idMap); + + if (Aspect == ASPECT_WILD) //manual + { + if (idMap[ASPECT_OF_THE_WILD_1] != ASPECT_OF_THE_WILD) + if (doCast(me, ASPECT_OF_THE_WILD)) + return; + return; + } + + if (GetManaPCT(me) < 20) + { + if (ASPECT_OF_THE_VIPER) + { + if (doCast(me, ASPECT_OF_THE_VIPER)) + return; + } + return; + } + else if (Aspect == ASPECT_VIPER && GetManaPCT(me) > 50) + { + me->RemoveAurasDueToSpell(ASPECT_OF_THE_VIPER_1, me->GetGUID()); + Aspect = ASPECT_NONE; + } + + if (IAmFree()) + { + InstanceTemplate const* instt = sObjectMgr->GetInstanceTemplate(me->GetMap()->GetId()); + bool map_allows_mount = (!me->GetMap()->IsDungeon() || me->GetMap()->IsBattlegroundOrArena()) && (!instt || instt->AllowMount); + if (me->HasUnitMovementFlag(MOVEMENTFLAG_FORWARD) && + (!me->GetVictim() ? + (me->IsInCombat() || !map_allows_mount || !IsOutdoors() || IsFlagCarrier(me)) : + !me->IsWithinDist(me->GetVictim(), 8.0f + GetSpellAttackRange(true)))) + { + if (ASPECT_OF_THE_CHEETAH && !(mask & (SPECIFIC_ASPECT_CHEETAH | SPECIFIC_ASPECT_PACK)) && Aspect != ASPECT_CHEETAH) + { + if (doCast(me, ASPECT_OF_THE_CHEETAH)) + return; + } + + return; + } + else if (Aspect == ASPECT_CHEETAH) + { + me->RemoveAurasDueToSpell(ASPECT_OF_THE_CHEETAH_1, me->GetGUID()); + Aspect = ASPECT_NONE; + } + } + else + { + //choose movement aspect first + if (!master->GetBotMgr()->IsPartyInCombat()) + { + if (!(mask & SPECIFIC_ASPECT_PACK)) + { + uint32 movFlags; + if (ASPECT_OF_THE_PACK) + { + movFlags = master->m_movementInfo.GetMovementFlags(); + if ((movFlags & MOVEMENTFLAG_FORWARD) && !(movFlags & (MOVEMENTFLAG_FALLING_FAR))) + { + if (doCast(me, ASPECT_OF_THE_PACK)) + return; + } + } + if (ASPECT_OF_THE_CHEETAH && Aspect != ASPECT_CHEETAH) + { + movFlags = me->m_movementInfo.GetMovementFlags(); + if ((movFlags & MOVEMENTFLAG_FORWARD) && !(movFlags & (MOVEMENTFLAG_FALLING_FAR)) && + me->GetDistance(master) > 20) + { + if (doCast(me, ASPECT_OF_THE_CHEETAH)) + return; + } + } + } + + return; + } + else if (Aspect == ASPECT_PACK) + { + me->RemoveAurasDueToSpell(ASPECT_OF_THE_PACK_1, me->GetGUID()); + Aspect = ASPECT_NONE; + } + } + + if ((Aspect == ASPECT_DRAGONHAWK && idMap[ASPECT_OF_THE_DRAGONHAWK_1] == ASPECT_OF_THE_DRAGONHAWK) || + (!ASPECT_OF_THE_DRAGONHAWK && ((Aspect == ASPECT_HAWK && idMap[ASPECT_OF_THE_HAWK_1] == ASPECT_OF_THE_HAWK) || + Aspect == ASPECT_MONKEY))) + return; + + if (ASPECT_OF_THE_DRAGONHAWK && + (Aspect != ASPECT_DRAGONHAWK || idMap[ASPECT_OF_THE_DRAGONHAWK_1] != ASPECT_OF_THE_DRAGONHAWK)) + { + if (doCast(me, ASPECT_OF_THE_DRAGONHAWK)) + return; + return; + } + if (ASPECT_OF_THE_HAWK && (!IsTank() || (!ASPECT_OF_THE_MONKEY && !ASPECT_OF_THE_DRAGONHAWK)) && + (Aspect != ASPECT_HAWK || idMap[ASPECT_OF_THE_HAWK_1] != ASPECT_OF_THE_HAWK)) + { + if (doCast(me, ASPECT_OF_THE_HAWK)) + return; + return; + } + if (ASPECT_OF_THE_MONKEY && Aspect != ASPECT_MONKEY) + { + if (doCast(me, ASPECT_OF_THE_MONKEY)) + return; + return; + } + } + + void Counter(uint32 diff) + { + if (IsCasting() || Rand() > 35) + return; + + Unit* target = nullptr; + + if (IsSpellReady(SCATTER_SHOT_1, diff) && HasRole(BOT_ROLE_DPS)) + { + target = FindCastingTarget(CalcSpellMaxRange(SCATTER_SHOT_1), 0, SCATTER_SHOT_1); + if (target && doCast(target, GetSpell(SCATTER_SHOT_1))) + return; + } + if (!target && IsSpellReady(WYVERN_STING_1, diff) && HasRole(BOT_ROLE_DPS)) + { + target = FindCastingTarget(CalcSpellMaxRange(WYVERN_STING_1), 5, WYVERN_STING_1); + if (target && doCast(target, GetSpell(WYVERN_STING_1))) + return; + } + //if (!target && IsSpellReady(FREEZING_ARROW_1, diff)) + //{ + // target = FindCastingTarget(40, 0, false, FREEZING_ARROW_1); + // if (target && doCast(target, GetSpell(FREEZING_ARROW_1))) + // return; + //} + //if (!target && IsSpellReady(SCARE_BEAST_1, diff)) + //{ + // target = FindCastingTarget(30, 0, SCARE_BEAST_1); + // if (target && doCast(target, GetSpell(SCARE_BEAST_1))) + // return; + //} + if (!target && IsSpellReady(SILENCING_SHOT_1, diff, false) && HasRole(BOT_ROLE_DPS)) + { + target = FindCastingTarget(CalcSpellMaxRange(SILENCING_SHOT_1), 5, SILENCING_SHOT_1); + if (target && doCast(target, GetSpell(SILENCING_SHOT_1))) + return; + } + } + + void CheckScatter(uint32 diff) + { + if (!IsSpellReady(SCATTER_SHOT_1, diff) || !HasRole(BOT_ROLE_DPS) || Rand() > 50) + return; + + for (Unit* mtar : { opponent, disttarget }) + { + if (mtar && mtar->GetVictim() == me && mtar->GetDistance(me) < 10 && + !mtar->HasAuraType(SPELL_AURA_PERIODIC_DAMAGE) && mtar->getAttackers().size() <= 1) + { + if (doCast(mtar, GetSpell(SCATTER_SHOT_1))) + { + GetInPosition(true, nullptr); + return; + } + } + } + if (Unit* target = FindStunTarget(CalcSpellMaxRange(SCATTER_SHOT_1))) + { + if (doCast(target, GetSpell(SCATTER_SHOT_1))) + return; + } + } + + void CheckWyvernSting(uint32 diff) + { + if (!IsSpellReady(WYVERN_STING_1, diff) || !HasRole(BOT_ROLE_DPS) || Rand() > 50) + return; + + if (Unit* target = FindStunTarget(CalcSpellMaxRange(WYVERN_STING_1))) + { + if (doCast(target, GetSpell(WYVERN_STING_1))) + return; + } + } + + void CheckFreezingArrow(uint32 diff) + { + //Freezing Trap shares cooldown with frosty traps + if (!IsSpellReady(FREEZING_ARROW_1, diff) || Rand() > 35) + return; + + if (Unit* target = FindStunTarget(25)) + { + if (doCast(target, GetSpell(FREEZING_ARROW_1))) + return; + } + } + + void CheckTraps(uint32 diff) + { + if (trapTimer > diff || IsCasting() || Rand() > 35) + return; + + trapTimer = urand(1000, 2000); + + //trap summon spell is 2yd radius + std::list targets; + GetNearbyTargetsInConeList(targets, 4); + if (targets.empty()) + return; + + //frost trap, freezing trap, freezing arrow: cat 411 + if (IsSpellReady(FROST_TRAP_1, diff) && !IsTank()) + { + //uint8 movingCount = 0; + //for (std::list::const_iterator itr = targets.begin(); itr != targets.end(); ++itr) + //{ + // if ((*itr)->isMoving() || ((*itr)->GetVictim() && !IsTank((*itr)->GetVictim()))) + // { + // if (++movingCount >= 2) + //if (targets.size() > 1) + // { + if (doCast(me, GetSpell(FROST_TRAP_1))) + return; + // break; + // } + // } + //} + } + //only if taming beast (or manual) + if (IsSpellReady(FREEZING_TRAP_1, diff) && !HasRole(BOT_ROLE_DPS) && !IAmFree() && + master->GetAuraEffect(SPELL_AURA_MOD_RESISTANCE_PCT, SPELLFAMILY_GENERIC, 255, 2)) + { + if (doCast(me, GetSpell(FREEZING_TRAP_1))) + return; + } + //black arrow, immolation trap, explosive trap: cat 1250 + if (IsSpellReady(EXPLOSIVE_TRAP_1, diff) && HasRole(BOT_ROLE_DPS)) + { + if (targets.size() > 1) + { + if (doCast(me, GetSpell(EXPLOSIVE_TRAP_1))) + return; + } + } + if (IsSpellReady(IMMOLATION_TRAP_1, diff) && HasRole(BOT_ROLE_DPS) && !(*targets.begin())->IsControlledByPlayer()) + { + if (targets.size() > 1) + { + if (doCast(me, GetSpell(IMMOLATION_TRAP_1))) + return; + } + } + //snake trap: cat 1249 + //if (IsSpellReady(SNAKE_TRAP_1, diff) && HasRole(BOT_ROLE_DPS) && (*targets.begin())->IsControlledByPlayer()) + //{ + // if (doCast(me, GetSpell(SNAKE_TRAP_1))) + // return; + //} + } + + void CheckMendPet(uint32 diff) + { + if (!IsSpellReady(MEND_PET_1, diff) || checkMendTimer > diff || Rand() > 75 || + !botPet || !botPet->IsAlive() || GetHealthPCT(botPet) > 80 || + me->GetDistance(botPet) > CalcSpellMaxRange(MEND_PET_1, false) || IsCasting()) + return; + + checkMendTimer = urand(2000, 4000); + + Aura const* mend = botPet->GetAura(GetSpell(MEND_PET_1)); + if (!mend || mend->GetDuration() < 3000) + { + if (doCast(me, GetSpell(MEND_PET_1))) + return; + } + } + + void CheckScare(uint32 diff) + { + if (!IsSpellReady(SCARE_BEAST_1, diff) || IsCasting() || Rand() > 25) + return; + + if (FindAffectedTarget(GetSpell(SCARE_BEAST_1), me->GetGUID(), 60)) + { + SetSpellCooldown(SCARE_BEAST_1, 2000); + return; + } + + if (Unit* scareTarget = FindFearTarget()) + { + if (doCast(scareTarget, GetSpell(SCARE_BEAST_1))) + return; + } + + SetSpellCooldown(SCARE_BEAST_1, 1500); //fail + } + + void doDefend(uint32 diff) + { + if (IsTank() || Rand() > 55) + return; + + bool feignReady = IsSpellReady(FEIGN_DEATH_1, diff, false); + bool deterReady = IsSpellReady(DETERRENCE_1, diff, false); + if (!feignReady && !deterReady) + return; + + Unit::AttackerSet const& b_attackers = me->getAttackers(); + if (b_attackers.empty()) + return; + + bool cast = false; + + if (b_attackers.size() == 1) + { + if (Creature* cre = (*b_attackers.begin())->ToCreature()) + if (cre->isWorldBoss() || cre->IsDungeonBoss() || cre->GetMaxHealth() > me->GetMaxHealth() * 10) + cast = true; + } + else + cast = (uint8(b_attackers.size()) > (GetHealthPCT(me) > 20 ? 1 : 0)); + + if (!cast) + return; + + if (feignReady && (*b_attackers.begin())->getAttackers().size() > 1) + { + if (doCast(me, GetSpell(FEIGN_DEATH_1))) + return; + } + + if (deterReady) + { + if (doCast(me, GetSpell(DETERRENCE_1))) + return; + } + } + + void CheckTranquil(uint32 diff) + { + if (!IsSpellReady(TRANQ_SHOT_1, diff) || Rand() > 20) + return; + + //First check current target + for (Unit* mtar : { opponent, disttarget }) + { + if (mtar && me->GetDistance(mtar) > 5 && me->GetDistance(mtar) < CalcSpellMaxRange(TRANQ_SHOT_1) && + !mtar->IsImmunedToSpell(sSpellMgr->GetSpellInfo(TRANQ_SHOT_1), me)) + { + AuraApplication const* aurApp; + SpellInfo const* spellInfo; + Unit::AuraMap const& auras = mtar->GetOwnedAuras(); + for (Unit::AuraMap::const_iterator itr = auras.begin(); itr != auras.end(); ++itr) + { + spellInfo = itr->second->GetSpellInfo(); + if (spellInfo->Dispel != DISPEL_MAGIC && spellInfo->Dispel != DISPEL_ENRAGE) continue; + if (spellInfo->Attributes & (SPELL_ATTR0_PASSIVE | SPELL_ATTR0_HIDDEN_CLIENTSIDE)) continue; + //if (spellInfo->AttributesEx & SPELL_ATTR1_DONT_DISPLAY_IN_AURA_BAR) continue; + aurApp = itr->second->GetApplicationOfTarget(mtar->GetGUID()); + if (aurApp && aurApp->IsPositive()) + { + if (doCast(mtar, GetSpell(TRANQ_SHOT_1))) + return; + } + } + } + } + + Unit* target = FindTranquilTarget(5, CalcSpellMaxRange(TRANQ_SHOT_1)); + if (target && doCast(target, GetSpell(TRANQ_SHOT_1))) + return; + } + + void CheckMisdirect(uint32 diff) + { + if (!IsSpellReady(MISDIRECTION_1, diff) || misdirectionTimer > diff || IAmFree() || + !master->GetGroup() || Rand() > 20) + return; + + misdirectionTimer = urand(3000, 6000); + + //find tank + //stacks + std::list tanks; + for (Unit* member : BotMgr::GetAllGroupMembers(master)) + { + if (member->IsInWorld() && me->GetMap() == member->FindMap() && member->IsAlive() && + member->GetVictim() && member->IsInCombat() && IsTank(member)) + { + tanks.push_back(member); + } + } + + if (tanks.empty()) + return; + + Unit* target = tanks.size() == 1 ? *tanks.begin() : Trinity::Containers::SelectRandomContainerElement(tanks); + if (doCast(target, GetSpell(MISDIRECTION_1))) + return; + } + + void UpdateAI(uint32 diff) override + { + if (!GlobalUpdate(diff)) + return; + + DoVehicleActions(diff); + if (!CanBotAttackOnVehicle()) + return; + + //pet is killed or unreachable + if (GC_Timer <= diff && !me->IsInCombat() && !me->IsMounted() && !me->GetVictim() && !IsCasting() && Rand() < 25 && + (!botPet || me->GetDistance2d(botPet) > sWorld->GetMaxVisibleDistanceOnContinents())) + SummonBotPet(); + + //Scare Beast interrupt + Spell const* spell = me->GetCurrentSpell(CURRENT_GENERIC_SPELL); + if (spell && spell->GetSpellInfo()->Id == GetSpell(SCARE_BEAST_1)) + { + Unit const* target = ObjectAccessor::GetUnit(*me, spell->m_targets.GetObjectTargetGUID()); + if (target && target->HasAuraType(SPELL_AURA_MOD_FEAR)) + me->InterruptSpell(CURRENT_GENERIC_SPELL); + } + + if (IsPotionReady()) + { + if (GetManaPCT(me) < 10) + DrinkPotion(true); + else if (GetHealthPCT(me) < 50) + DrinkPotion(false); + } + + CheckRacials(diff); + + if (!me->IsInCombat()) + DoNonCombatActions(diff); + else + doDefend(diff); + + CheckAspects(diff); + + if (IsSpellReady(TRUESHOT_AURA_1, diff) && !IAmFree() && Rand() < 5 && + !me->GetAuraEffect(SPELL_AURA_MOD_RANGED_ATTACK_POWER_PCT, SPELLFAMILY_HUNTER, 0x0, 0x200000, 0x0, me->GetGUID())) + { + if (doCast(me, GetSpell(TRUESHOT_AURA_1))) + return; + } + + CheckMendPet(diff); + + if (master->IsInCombat() || me->IsInCombat()) + CheckScare(diff); + + //Deterrence check + if (me->HasUnitFlag(UNIT_FLAG_PACIFIED) && !IsCasting()) + { + if (!me->isMoving()) + GetInPosition(true, nullptr); + return; + } + + if (ProcessImmediateNonAttackTarget()) + return; + + if (!CheckAttackTarget()) + { + me->InterruptSpell(CURRENT_AUTOREPEAT_SPELL); + return; + } + + if (IsCasting()) + return; + + CheckFlare(diff); + CheckReadiness(diff); + + CheckUsableItems(diff); + + DoRangedAttack(diff); + } + + void DoRangedAttack(uint32 diff) + { + Unit* mytar = opponent ? opponent : disttarget ? disttarget : nullptr; + if (!mytar) + return; + + StartAttack(mytar, IsMelee()); + + CheckAttackState(); + if (!me->IsAlive() || !mytar->IsAlive()) + return; + + Counter(diff); + CheckTranquil(diff); + + float dist = me->GetDistance(mytar); + float maxRangeLong = me->GetLevel() >= 10 ? 51.f : 45.f; + float maxRangeNormal = me->GetLevel() >= 10 ? 41.f : 35.f; + + bool inposition = !mytar->HasAuraType(SPELL_AURA_MOD_CONFUSE) || dist > maxRangeNormal - 15.f; + + //Auto Shot + if (Spell const* shot = me->GetCurrentSpell(CURRENT_AUTOREPEAT_SPELL)) + { + if (shot->GetSpellInfo()->Id == AUTO_SHOT_1 && (shot->m_targets.GetUnitTarget() != mytar || !inposition)) + me->InterruptSpell(CURRENT_AUTOREPEAT_SPELL); + } + else if (HasRole(BOT_ROLE_DPS) && dist > 5 && dist < maxRangeNormal) + { + if (doCast(mytar, AUTO_SHOT_1)) + {} + } + + CheckScatter(diff); + CheckFreezingArrow(diff); + CheckWyvernSting(diff); + + //TRAPS + CheckTraps(diff); + + auto [can_do_nature, can_do_fire, can_do_arcane, can_do_shadow, can_do_normal] = + CanAffectVictimBools(mytar, SPELL_SCHOOL_NATURE, SPELL_SCHOOL_FIRE, SPELL_SCHOOL_ARCANE, SPELL_SCHOOL_SHADOW, SPELL_SCHOOL_NORMAL); + + //scatter pvp + if (IsSpellReady(SCATTER_SHOT_1, diff) && can_do_normal && HasRole(BOT_ROLE_DPS) && + mytar->GetTypeId() == TYPEID_PLAYER && dist < 10 && Rand() < 60) + { + if (doCast(mytar, GetSpell(SCATTER_SHOT_1))) + { + me->InterruptSpell(CURRENT_AUTOREPEAT_SPELL); + me->AttackStop(); + GetInPosition(true, mytar); + return; + } + } + + //DISENGAGE + if (IsSpellReady(DISENGAGE_1, diff, false) && me->IsInCombat() && !IsTank() && Rand() < 70 && + !HasBotCommandState(BOT_COMMAND_STAY) && + !me->getAttackers().empty() && me->GetDistance(*me->getAttackers().begin()) < 5 && + me->HasInArc(float(M_PI), *me->getAttackers().begin())) + { + if (doCast(me, GetSpell(DISENGAGE_1))) + return; + } + + MoveBehind(mytar); + + //MELEE SECTION + if (dist < 5) + { + if (!can_do_normal) + return; + + //MONGOOSE BITE + if (IsSpellReady(MONGOOSE_BITE_1, diff) && HasRole(BOT_ROLE_DPS) && Rand() < 50) + { + if (doCast(mytar, GetSpell(MONGOOSE_BITE_1))) + return; + } + //COUNTERATTACK + if (IsSpellReady(COUNTERATTACK_1, diff) && HasRole(BOT_ROLE_DPS) && + me->HasReactive(REACTIVE_HUNTER_PARRY) && Rand() < 90) + { + if (doCast(mytar, GetSpell(COUNTERATTACK_1))) + return; + } + //WING CLIP + if (IsSpellReady(WING_CLIP_1, diff) && (!IsTank() || mytar->isMoving()) && + Rand() < 80 && !CCed(mytar, true) && !mytar->HasAuraWithMechanic(1<GetCurrentSpell(CURRENT_MELEE_SPELL)) + { + if (doCast(mytar, GetSpell(RAPTOR_STRIKE_1))) + return; + } + + return; //don't try to do anything else in melee + } + + //RANGED SECTION + + //HUNTERS MARK //100 yd range so don't check it + if (IsSpellReady(HUNTERS_MARK_1, diff) && can_do_arcane && Rand() < 65 && + !mytar->HasAuraTypeWithFamilyFlags(SPELL_AURA_MOD_STALKED, SPELLFAMILY_HUNTER, 0x400)) + { + if (doCast(mytar, GetSpell(HUNTERS_MARK_1))) + return; + } + + CheckMisdirect(diff); + + //attack range check 1 + if (dist > maxRangeLong) + return; + + //KILL SHOT + if (IsSpellReady(KILL_SHOT_1, diff) && can_do_normal && HasRole(BOT_ROLE_DPS) && + mytar->HasAuraState(AURA_STATE_HEALTHLESS_20_PERCENT)) + { + if (doCast(mytar, GetSpell(KILL_SHOT_1))) + return; + } + + //attack range check 2 + if (dist > maxRangeNormal) + return; + + if (!inposition && me->getAttackers().empty()) + return; + + //CONCUSSIVE SHOT + if (IsSpellReady(CONCUSSIVE_SHOT_1, diff) && can_do_arcane && Rand() < 35 && + !CCed(mytar, true) && !mytar->HasAuraWithMechanic(1<GetVictim(); + if (IsSpellReady(DISTRACTING_SHOT_1, diff) && can_do_arcane && u && u != me && IsTank() && !CCed(mytar) && + IsInBotParty(u) && Rand() < 75 && (!IsTank(u) || (dist > 25 && GetHealthPCT(u) < 25))) + { + if (doCast(mytar, GetSpell(DISTRACTING_SHOT_1))) + return; + } + //MULTI-SHOT shares cd with aimed shot + if (IsSpellReady(MULTISHOT_1, diff) && HasRole(BOT_ROLE_DPS) && Rand() < 70) + { + if (Rand() < 30 || !GetSpell(STEADY_SHOT_1) || FindSplashTarget(maxRangeNormal)) + { + if (doCast(mytar, GetSpell(MULTISHOT_1))) + return; + } + } + //VOLLEY + if (IsSpellReady(VOLLEY_1, diff) && HasRole(BOT_ROLE_DPS) && !JumpingOrFalling() && Rand() < 75) + { + if (Unit* target = FindAOETarget(maxRangeNormal)) + { + if (doCast(target, GetSpell(VOLLEY_1))) + return; + } + } + //RAPID FIRE + if (IsSpellReady(RAPID_FIRE_1, diff, false) && can_do_normal && HasRole(BOT_ROLE_DPS) && !me->isMoving() && Rand() < 55 && + (mytar->GetHealth() > me->GetMaxHealth() * (1 + mytar->getAttackers().size()) || + mytar->GetTypeId() == TYPEID_PLAYER) && + !me->HasAuraTypeWithFamilyFlags(SPELL_AURA_MOD_RANGED_HASTE, SPELLFAMILY_HUNTER, 0x20)) + { + if (doCast(me, GetSpell(RAPID_FIRE_1))) + {} + } + //BLACK ARROW + //Black Arrow shares cooldown with fire traps + if (IsSpellReady(BLACK_ARROW_1, diff) && can_do_shadow && HasRole(BOT_ROLE_DPS) && + mytar->GetHealth() > me->GetMaxHealth()/4 * (1 + mytar->getAttackers().size())) + { + if (doCast(mytar, GetSpell(BLACK_ARROW_1))) + return; + } + //CHIMERA SHOT: no viper + if (IsSpellReady(CHIMERA_SHOT_1, diff) && can_do_nature && HasRole(BOT_ROLE_DPS)) + { + //Serpent + if (mytar->GetAuraEffect(SPELL_AURA_MOD_DAMAGE_FROM_CASTER, SPELLFAMILY_HUNTER, 0x4000, 0x0, 0x0, me->GetGUID())) + { + if (doCast(mytar, GetSpell(CHIMERA_SHOT_1))) + return; + } + //Scorpid + else if (mytar->GetAuraEffect(SPELL_AURA_MOD_HIT_CHANCE, SPELLFAMILY_HUNTER, 0x8000, 0x0, 0x0, me->GetGUID())) + { + if (!mytar->HasAuraType(SPELL_AURA_MOD_DISARM) && + (mytar->GetTypeId() == TYPEID_PLAYER || mytar->GetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID))) + { + if (doCast(mytar, GetSpell(CHIMERA_SHOT_1))) + return; + } + } + + SetSpellCooldown(CHIMERA_SHOT_1, 500); //fail + } + //STING + if (GetSpellCooldown(SERPENT_STING_1) <= diff && can_do_nature && stingTimer <= diff && Rand() < 60) + { + uint32 STING = 0; + AuraEffect const* sting = nullptr; + if (!STING && GetSpell(SCORPID_STING_1) && mytar->GetTypeId() == TYPEID_UNIT && + mytar->ToCreature()->GetCreatureTemplate()->rank != CREATURE_ELITE_NORMAL) + { + sting = mytar->GetAuraEffect(SPELL_AURA_MOD_HIT_CHANCE, SPELLFAMILY_HUNTER, 0x8000, 0x0, 0x0); + if (!sting || sting->GetBase()->GetCasterGUID() == me->GetGUID()) + STING = SCORPID_STING_1; + } + //VIPER STING: pvp only + if (!STING && GetSpell(VIPER_STING_1) && mytar->GetTypeId() == TYPEID_PLAYER && + mytar->GetPowerType() == POWER_MANA && mytar->GetHealth() > me->GetMaxHealth()/2 && + mytar->GetMaxPower(POWER_MANA) > me->GetMaxPower(POWER_MANA)) + { + sting = mytar->GetAuraEffect(SPELL_AURA_PERIODIC_MANA_LEECH, SPELLFAMILY_HUNTER, 0x0, 0x80, 0x0, me->GetGUID()); + if (!sting) + STING = VIPER_STING_1; + } + if (!STING && GetSpell(SERPENT_STING_1) && HasRole(BOT_ROLE_DPS) && + mytar->GetHealth() > me->GetMaxHealth()/2 * (1 + mytar->getAttackers().size())) + { + sting = mytar->GetAuraEffect(SPELL_AURA_MOD_DAMAGE_FROM_CASTER, SPELLFAMILY_HUNTER, 0x4000, 0x0, 0x0, me->GetGUID()); + if (!sting) + STING = SERPENT_STING_1; + } + + if (sting && sting->GetBase()->GetCasterGUID() == me->GetGUID() && + sting->GetBase()->GetDuration() >= 3000) + { + stingTimer = 3000; + } + else + { + if (STING && doCast(mytar, GetSpell(STING))) + { + stingTimer = 8000; + return; + } + } + } + //EXPLOSIVE SHOT: replaces Arcane Shot at 60 + if (IsSpellReady(EXPLOSIVE_SHOT_1, diff) && can_do_fire && HasRole(BOT_ROLE_DPS)) + { + if (doCast(mytar, GetSpell(EXPLOSIVE_SHOT_1))) + return; + } + //ARCANE SHOT: shares cd with Explosive Shot + if (IsSpellReady(ARCANE_SHOT_1, diff) && can_do_arcane && HasRole(BOT_ROLE_DPS) && !GetSpell(EXPLOSIVE_SHOT_1)) + { + if (doCast(mytar, GetSpell(ARCANE_SHOT_1))) + return; + } + //AIMED SHOT shares cd with multishot + if (IsSpellReady(AIMED_SHOT_1, diff) && can_do_normal && HasRole(BOT_ROLE_DPS)) + { + if (doCast(mytar, GetSpell(AIMED_SHOT_1))) + return; + } + //STEADY SHOT + if (IsSpellReady(STEADY_SHOT_1, diff) && can_do_normal && HasRole(BOT_ROLE_DPS)) + { + if (doCast(mytar, GetSpell(STEADY_SHOT_1))) + return; + } + } + + void CheckFlare(uint32 diff) + { + if (!IsSpellReady(FLARE_1, diff) || flareTimer > diff || me->IsMounted() || Rand() > 25) + return; + + flareTimer = urand(2000, 4000); + + std::set targets; + if (Group const* gr = !IAmFree() ? master->GetGroup() : GetGroup()) + { + for (Unit* member : BotMgr::GetAllGroupMembers(gr)) + { + if (me->GetMap() != member->FindMap() || !member->IsAlive()) + continue; + for (Unit* attacker : member->getAttackers()) + { + if (attacker->GetClass() == CLASS_ROGUE || attacker->HasInvisibilityAura() || attacker->HasStealthAura()) + { + if (member->GetDistance(attacker) < 15) + { + targets.insert(member); + break; + } + } + } + } + } + for (Unit* attacker : me->getAttackers()) + { + if (attacker->GetClass() == CLASS_ROGUE || attacker->HasInvisibilityAura() || attacker->HasStealthAura()) + { + if (me->GetDistance(attacker) < 15) + { + targets.insert(me); + break; + } + } + } + + if (targets.empty()) + return; + + Unit* target = targets.size() == 1u ? *targets.begin() : Trinity::Containers::SelectRandomContainerElement(targets); + if (doCast(target, GetSpell(FLARE_1))) + return; + } + + void CheckReadiness(uint32 diff) + { + if (!IsSpellReady(READINESS_1, diff) || !me->IsInCombat() || me->IsMounted() || Rand() > 30) + return; + + //mainly used for rapid fire cd reset + bool cast = me->GetVictim() && !IsSpellReady(RAPID_FIRE_1, diff, false); + + if (cast && doCast(me, GetSpell(READINESS_1))) + return; + } + + void ApplyClassSpellCritMultiplierAll(Unit const* victim, float& crit_chance, SpellInfo const* spellInfo, SpellSchoolMask /*schoolMask*/, WeaponAttackType /*attackType*/) const override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + uint8 lvl = me->GetLevel(); + + //Glyph of Explosive Shot: 4% additional critical chance for Explosive Shot + if (lvl >= 60 && (baseId == EXPLOSIVE_SHOT_1 || baseId == EXPLOSIVE_SHOT_PERIODIC_DUMMY_AURA)) + crit_chance += 4.f; + //Point of No Escape: 6% additional critical chance on targets affected by frosty traps + if ((GetSpec() == BOT_SPEC_HUNTER_SURVIVAL) && lvl >= 50) + { + if (victim->GetAuraEffect(SPELL_AURA_MOD_CRIT_CHANCE_FOR_CASTER, SPELLFAMILY_HUNTER, 0x18, 0x0, 0x0, me->GetGUID())) + crit_chance += 6.f; + } + //Sniper Training (part 1): 15% additional critical chance for Kill Shot + if ((GetSpec() == BOT_SPEC_HUNTER_SURVIVAL) && lvl >= 50 && baseId == KILL_SHOT_1) + crit_chance += 15.f; + //Improved Steady Shot (37505): 5% additional critical chance for Steady Shot + if (lvl >= 50 && baseId == STEADY_SHOT_1) + crit_chance += 5.f; + //Glyph of TrueShot Aura (req lvl 40): 10% additional critical chance for Aimed Shot + if (lvl >= 40 && baseId == AIMED_SHOT_1) + crit_chance += 10.f; + //Improved Barrage: 12% additional critical chance for Multi-Shot and Aimed Shot + if ((GetSpec() == BOT_SPEC_HUNTER_MARKSMANSHIP) && lvl >= 40 && (baseId == AIMED_SHOT_1 || baseId == MULTISHOT_1)) + crit_chance += 12.f; + //Survival Instincts: 4% additional critical chance for Arcane Shot, Steady Shot and Explosive Shot + if (lvl >= 15 && (baseId == ARCANE_SHOT_1 || baseId == STEADY_SHOT_1 || baseId == EXPLOSIVE_SHOT_1 || + baseId == EXPLOSIVE_SHOT_PERIODIC_DUMMY_AURA)) + crit_chance += 4.f; + //Savage Strikes: 20% additional critical chance for Raptor Strike, Mongoose Bite and Counterattack + if (lvl >= 10 && (baseId == RAPTOR_STRIKE_1 || baseId == MONGOOSE_BITE_1 || baseId == COUNTERATTACK_1)) + crit_chance += 20.f; + } + + void ApplyClassDamageMultiplierMeleeSpell(int32& damage, SpellNonMeleeDamage& damageinfo, SpellInfo const* spellInfo, WeaponAttackType /*attackType*/, bool iscrit) const override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + + ////do nothing with autoshot + //if (baseId == AUTO_SHOT_1) + // return; + + uint8 lvl = me->GetLevel(); + float fdamage = float(damage); + + //apply bonus damage mods + float pctbonus = 0.0f; + if (iscrit) + { + //!!!Melee spell damage is not yet critical, all reduced by half + //Mortal Shots: 30% crit damage bonus for all ranged abilities + if (lvl >= 15 && baseId != AUTO_SHOT_1) + pctbonus += 0.15f; + //Marked for Death (part 2): 10% crit damage bonus for Aimed Shot, Arcane Shot, Steady Shot, Kill Shot and Chimera Shot + if ((GetSpec() == BOT_SPEC_HUNTER_MARKSMANSHIP) && lvl >= 55 && + (baseId == AIMED_SHOT_1 || baseId == ARCANE_SHOT_1 || baseId == STEADY_SHOT_1 || + baseId == KILL_SHOT_1 || baseId == CHIMERA_SHOT_1)) + pctbonus += 0.05f; + } + + //Improved Tracking: 5% bonus damage versus tracked types (all for bots) + if (lvl >= 10) + pctbonus += 0.05f; + //Focused Fire: 2% bonus damage while pet is active + if (lvl >= 15 && botPet) + pctbonus += 0.02f; + //Ranged Weapon Specialization: 5% bonus damage for ranged attacks + if ((GetSpec() == BOT_SPEC_HUNTER_MARKSMANSHIP) && lvl >= 35) + pctbonus += 0.05f; + //Improved Arcane Shot: 15% bonus damage for Arcane Shot + if (lvl >= 20 && baseId == ARCANE_SHOT_1) + pctbonus += 0.15f; + //Rapid Killing (buff): 20% bonus damage for Aimed Shot, Arcane Shot or Chimera Shot (removed in SpellHitTarget()) + if (lvl >= 20 && (baseId == AIMED_SHOT_1 || baseId == ARCANE_SHOT_1 || baseId == CHIMERA_SHOT_1)) + { + if (AuraEffect const* rapi = me->GetAuraEffect(RAPID_KILLING_BUFF, 0)) + if (rapi->IsAffectingSpell(spellInfo)) + pctbonus += 0.2f; + } + //Barrage: 12% bonus damage for Aimed Shot, Multi-Shot or Volley + if ((GetSpec() == BOT_SPEC_HUNTER_MARKSMANSHIP) && lvl >= 30 && (spellInfo->SpellFamilyFlags[0] & 0x23000)) + pctbonus += 0.12f; + //Marked for Death (part 1): 5% bonus damage for all ranged shots on marked target + if ((GetSpec() == BOT_SPEC_HUNTER_MARKSMANSHIP) && lvl >= 55 && damageinfo.target && + damageinfo.target->GetAuraEffect(SPELL_AURA_RANGED_ATTACK_POWER_ATTACKER_BONUS, SPELLFAMILY_HUNTER, 0x400, 0x0, 0x0/*, me->GetGUID()*/)) + pctbonus += 0.05f; + //T.N.T: 6% bonus damage for Explosive Shot, Explosive Trap, Immolation Trap and Black Arrow + if ((GetSpec() == BOT_SPEC_HUNTER_SURVIVAL) && lvl >= 25 && + (baseId == EXPLOSIVE_SHOT_1 || baseId == EXPLOSIVE_SHOT_PERIODIC_DUMMY_AURA || + baseId == EXPLOSIVE_TRAP_AURA_1 || baseId == IMMOLATION_TRAP_AURA_1 || baseId == BLACK_ARROW_1)) + pctbonus += 0.06f; + //Ferocious Inspiration part 2: 9% bonus damage for Arcane Shot and Steady Shot + if ((GetSpec() == BOT_SPEC_HUNTER_BEASTMASTERY) && + lvl >= 40 && (baseId == ARCANE_SHOT_1 || baseId == STEADY_SHOT_1)) + pctbonus += 0.09f; + //Improved Steady Shot (38392): 10% bonus damage for Steady Shot + if (lvl >= 50 && baseId == STEADY_SHOT_1) + pctbonus += 0.1f; + //Glyph of Steady Shot: 10% bonus damage for Steady Shot if affected by Serpent Sting + if (lvl >= 62 && baseId == STEADY_SHOT_1 && damageinfo.target && + damageinfo.target->GetAuraEffect(SPELL_AURA_MOD_DAMAGE_FROM_CASTER, SPELLFAMILY_HUNTER, 0x4000, 0x0, 0x0/*, me->GetGUID()*/)) + pctbonus += 0.1f; + //The Beast Within part 1: 10% bonus damage for all abilities + if ((GetSpec() == BOT_SPEC_HUNTER_BEASTMASTERY) && lvl >= 50) + pctbonus += 0.1f; + //Sniper Training part 2: 6% bonus damage for Steady Shot, Aimed Shot, Black Arrow and Explosive Shot + if ((GetSpec() == BOT_SPEC_HUNTER_SURVIVAL) && lvl >= 50 && + ((spellInfo->SpellFamilyFlags[0] & 0x20000) || + (spellInfo->SpellFamilyFlags[1] & 0x8000001) || + (spellInfo->SpellFamilyFlags[2] & 0x200))) + { + if (Aura const* snip = me->GetAura(SNIPER_TRAINING_BUFF)) + { + if (snip->GetEffect(0)->IsAffectingSpell(spellInfo) || + snip->GetEffect(1)->IsAffectingSpell(spellInfo)) + pctbonus += 0.06f; + } + } + //Improved Steady Shot part 1: 15% bonus damage for Steady Shot, Aimed Shot, Arcane Arrow and Chimera Shot + if (baseId == AIMED_SHOT_1 || baseId == ARCANE_SHOT_1 || baseId == CHIMERA_SHOT_1) + { + if (AuraEffect const* stea = me->GetAuraEffect(IMPROVED_STEADY_SHOT_BUFF, 0)) + if (stea->IsAffectingSpell(spellInfo)) + pctbonus += 0.15f; + } + + damage = int32(fdamage * (1.0f + pctbonus)); + } + + void ApplyClassDamageMultiplierSpell(int32& damage, SpellNonMeleeDamage& /*damageinfo*/, SpellInfo const* spellInfo, WeaponAttackType /*attackType*/, bool /*iscrit*/) const override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + uint8 lvl = me->GetLevel(); + float fdamage = float(damage); + float flat_mod = 0.f; + + //2) apply bonus damage mods + float pctbonus = 0.0f; + //if (iscrit) + //{ + // //!!!spell damage is not yet critical and will be multiplied by 1.5 + // //so we should put here bonus damage mult /1.5 + // //Lava Flows (part 1): 24% additional crit damage bonus for Lava Burst + // if (lvl >= 50 && spellId == GetSpell(LAVA_BURST_1)) + // pctbonus += 0.16f; + //} + //Trap Mastery part 2: 30% bonus damage for Immolation Trap, Explosive Trap and Black Arrow + if (lvl >= 15 && (baseId == IMMOLATION_TRAP_AURA_1 || baseId == EXPLOSIVE_TRAP_AURA_1 || baseId == BLACK_ARROW_1)) + pctbonus += 0.3f; + //T.N.T: 6% bonus damage for Explosive Shot, Explosive Trap, Immolation Trap and Black Arrow + if ((GetSpec() == BOT_SPEC_HUNTER_SURVIVAL) && lvl >= 25 && + (baseId == EXPLOSIVE_SHOT_1 || baseId == EXPLOSIVE_SHOT_PERIODIC_DUMMY_AURA || + baseId == EXPLOSIVE_TRAP_AURA_1 || baseId == IMMOLATION_TRAP_AURA_1 || baseId == BLACK_ARROW_1)) + pctbonus += 0.06f; + //The Beast Within part 1: 10% bonus damage for all abilities + if ((GetSpec() == BOT_SPEC_HUNTER_BEASTMASTERY) && lvl >= 50) + pctbonus += 0.1f; + + damage = int32(fdamage * (1.0f + pctbonus) + flat_mod); + } + + void ApplyClassSpellCostMods(SpellInfo const* spellInfo, int32& cost) const override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + uint8 lvl = me->GetLevel(); + float fcost = float(cost); + int32 flatbonus = 0; + float pctbonus = 0.0f; + + //percent mods + //100% mods + //Improved Hunter's Mark: -100% mana cost for Hunter's Mark + if (lvl >= 15 && baseId == HUNTERS_MARK_1) + pctbonus += 1.0f; + //Lock and Load: mana cost + if (baseId == ARCANE_SHOT_1 || baseId == EXPLOSIVE_SHOT_1) + { + AuraEffect const* lock = me->GetAuraEffect(LOCK_AND_LOAD_BUFF, 0); + if (lock && lock->IsAffectingSpell(spellInfo)) + pctbonus += 1.0f; + } + + //Improved Mend Pet: -20% mana cost for Mend Pet + //Improved Mend Pet (23560) part 3 + if (lvl >= 25 && baseId == MEND_PET_1) + pctbonus += 0.5f; + //Efficiency: -15% mana cost for Stings and Shots + if ((GetSpec() == BOT_SPEC_HUNTER_MARKSMANSHIP) && lvl >= 25 && + ((spellInfo->SpellFamilyFlags[0] & 0x7FA00) || + (spellInfo->SpellFamilyFlags[1] & 0x88801081) || + (spellInfo->SpellFamilyFlags[2] & 0x1))) + pctbonus += 0.15f; + //Resourcefulness: -60% mana cost for Traps, melee spells and Black Arrow + if ((GetSpec() == BOT_SPEC_HUNTER_SURVIVAL) && lvl >= 35 && + ((spellInfo->SpellFamilyFlags[0] & 0xDE) || + (spellInfo->SpellFamilyFlags[1] & 0x84000))) + pctbonus += 0.6f; + //Glyph of Volley: -20% mana cost for Volley + if (lvl >= 40 && baseId == VOLLEY_1) + pctbonus += 0.2f; + //Master Marksman: -25% mana cost for Steady Shot, Aimed Shot and Chimera Shot + if ((GetSpec() == BOT_SPEC_HUNTER_MARKSMANSHIP) && + lvl >= 45 && (baseId == STEADY_SHOT_1 || baseId == AIMED_SHOT_1 || baseId == CHIMERA_SHOT_1)) + pctbonus += 0.25f; + //Improved Steady Shot part 2: -20% mana cost for Steady Shot, Aimed Shot, Arcane Arrow and Chimera Shot + if (baseId == AIMED_SHOT_1 || baseId == ARCANE_SHOT_1 || baseId == CHIMERA_SHOT_1) + { + if (AuraEffect const* stea = me->GetAuraEffect(IMPROVED_STEADY_SHOT_BUFF, 1)) + if (stea->IsAffectingSpell(spellInfo)) + pctbonus += 0.2f; + } + + //flat mods + //!1 rage = 10 pts! + //Improved Heroic Strike: -3 rage cost for Heroic Strike + //if (lvl >= 10 && baseId == HEROIC_STRIKE_1) + // flatbonus += 30; + + //cost can be < 0 + cost = int32(fcost * (1.0f - pctbonus)) - flatbonus; + } + + void ApplyClassSpellCooldownMods(SpellInfo const* spellInfo, uint32& cooldown) const override + { + //cooldown is in milliseconds + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + uint8 lvl = me->GetLevel(); + int32 timebonus = 0; + float pctbonus = 0.0f; + + //pct mods + //Glyph of Rapid Charge: -7% cooldown for Charge + //if (lvl >= 40 && spellId == GetSpell(CHARGE_1)) + // pctbonus += 0.07f; + + //flat mods + //Survival Tactics: -4 sec cooldown for Disengage + //Glyph of Disengage: -5 sec cooldown for Disengage + if (lvl >= 20 && baseId == DISENGAGE_1) + timebonus += (GetSpec() == BOT_SPEC_HUNTER_SURVIVAL) ? 9000 : 5000; + //Glyph of Feign Death: -5 sec cooldown for Feign Death + //Improved Feign Death (24432): -2 sec cooldown for Feign Death + if (lvl >= 30 && baseId == FEIGN_DEATH_1) + timebonus += 7000; + //Tranquilizing Shot Cooldown reduction (61255): -2 sec cooldown for Tranquilizing Shot + if (lvl >= 60 && baseId == TRANQ_SHOT_1) + timebonus += 2000; + //Glyph of Deterrence: -10 sec cooldown for Deterrence + if (lvl >= 60 && baseId == DETERRENCE_1) + timebonus += 10000; + //Glyph of Chimera Shot: -1 sec cooldown for Chimera Shot + if (lvl >= 60 && baseId == CHIMERA_SHOT_1) + timebonus += 1000; + + cooldown = std::max((float(cooldown) * (1.0f - pctbonus)) - timebonus, 0); + } + + void ApplyClassSpellCategoryCooldownMods(SpellInfo const* spellInfo, uint32& cooldown) const override + { + //cooldown is in milliseconds + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + uint8 lvl = me->GetLevel(); + int32 timebonus = 0; + float pctbonus = 0.0f; + + //Lock and Load: cooldown + if (baseId == ARCANE_SHOT_1 || baseId == EXPLOSIVE_SHOT_1) + { + AuraEffect const* lock = me->GetAuraEffect(LOCK_AND_LOAD_BUFF, 0); + if (lock && lock->IsAffectingSpell(spellInfo)) + pctbonus += 1.0f; + } + + //Rapid Killing part 1: -2 min cooldown for Rapid Fire + if ((GetSpec() == BOT_SPEC_HUNTER_MARKSMANSHIP) && baseId == RAPID_FIRE_1) + timebonus += 120000; + //Glyph of Aimed Shot: -2 sec cooldown for Aimed Shot + if (baseId == AIMED_SHOT_1) + timebonus += 2000; + //Glyph of Multi-Shot: -1 sec cooldown for Multi-Shot + //Improved Multi-Shot (44292): -1 sec cooldown for Multi-Shot + if (baseId == MULTISHOT_1) + timebonus += 2000; + //Trap Cooldown (37481): -4 sec cd for Traps + //Trap Cooldown Reduction: -2 sec cd for Traps + if (spellInfo->SpellFamilyFlags[0] & 0x80) + timebonus += 6000; + //Resourcefulness: -6 sec cd for Traps and Black Arrow + if ((GetSpec() == BOT_SPEC_HUNTER_SURVIVAL) && lvl >= 35 && (spellInfo->SpellFamilyFlags[0] & 0x80)) + timebonus += 6000; + //Catlike Reflexes part 3: -30 sec cd for Kill Command + if ((GetSpec() == BOT_SPEC_HUNTER_BEASTMASTERY) && lvl >= 40 && (spellInfo->SpellFamilyFlags[1] & 0x800)) + timebonus += 30000; + //Glyph of Kill Shot: -6 sec cooldown for Kill Shot + if (lvl >= 40 && baseId == KILL_SHOT_1) + timebonus += 6000; + + cooldown = std::max((float(cooldown) * (1.0f - pctbonus)) - timebonus, 0); + } + + void ApplyClassSpellRadiusMods(SpellInfo const* spellInfo, float& radius) const override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + //uint8 lvl = me->GetLevel(); + float flatbonus = 0.0f; + float pctbonus = 0.0f; + + //pct mods + ////Holy Reach + //if (lvl >= 25 && ((spellInfo->SpellFamilyFlags[0] & 0x18400200) || (spellInfo->SpellFamilyFlags[2] & 0x4))) + // pctbonus += 0.2f; + + //flat mods + //Glyph of Frost Trap + if (baseId == FROST_TRAP_AURA) + flatbonus += 2.f; + //Glyph of the Pack + if (baseId == ASPECT_OF_THE_PACK_1) + flatbonus += 15.f; + + radius = radius * (1.0f + pctbonus) + flatbonus; + } + + void ApplyClassSpellRangeMods(SpellInfo const* spellInfo, float& maxrange) const override + { + //uint32 spellId = spellInfo->Id; + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + uint8 lvl = me->GetLevel(); + float flatbonus = 0.0f; + float pctbonus = 0.0f; + + //pct mods + //Improved Mend Pet (23560) part 1 + if (lvl >= 25 && baseId == MEND_PET_1) + pctbonus += 0.5f; + + //flat mods + //Hawk Eye: +6 yd range for Ranged Abilities + if (lvl >= 10 && + ((spellInfo->SpellFamilyFlags[0] & 0x7FA01) || + (spellInfo->SpellFamilyFlags[1] & 0x88801081) || + (spellInfo->SpellFamilyFlags[2] & 0x401))) + flatbonus += 6.f; + //Glyph of Scatter Shot + if (lvl >= 20 && baseId == SCATTER_SHOT_1) + flatbonus += 3.f; + + maxrange = maxrange * (1.0f + pctbonus) + flatbonus; + } + + void ApplyClassSpellChanceOfSuccessMods(SpellInfo const* spellInfo, float& chance) const override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + uint8 lvl = me->GetLevel(); + + //Improved Aspect of the Hawk: 10% chance + if (lvl >= 10 && (baseId == ASPECT_OF_THE_HAWK_1 || baseId == ASPECT_OF_THE_DRAGONHAWK_1)) + chance += 10.f; + } + + void ApplyClassEffectMods(SpellInfo const* spellInfo, uint8 effIndex, float& value) const override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + float pctbonus = 1.0f; + + //Improved Mend Pet (23560) part 2 + if (baseId == MEND_PET_1 && effIndex == EFFECT_0) + pctbonus *= 1.1f; + + value = value * pctbonus; + } + + void OnClassSpellGo(SpellInfo const* spellInfo) override + { + //uint32 spellId = spellInfo->Id; + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + + //Rapid Killing: use up buff manually + if (baseId == AIMED_SHOT_1 || baseId == ARCANE_SHOT_1 || baseId == CHIMERA_SHOT_1) + { + if (AuraEffect const* rapi = me->GetAuraEffect(RAPID_KILLING_BUFF, 0)) + if (rapi->IsAffectingSpell(spellInfo)) + me->RemoveAura(RAPID_KILLING_BUFF); + } + //Glyph of Arcane Shot + if (baseId == ARCANE_SHOT_1) + { + Unit const* victim = me->GetVictim(); + if (victim && + (victim->GetAuraEffect(SPELL_AURA_MOD_DAMAGE_FROM_CASTER, SPELLFAMILY_HUNTER, 0x4000, 0x0, 0x0, me->GetGUID()) || + victim->GetAuraEffect(SPELL_AURA_PERIODIC_MANA_LEECH, SPELLFAMILY_HUNTER, 0x0, 0x80, 0x0, me->GetGUID()) || + victim->GetAuraEffect(SPELL_AURA_MOD_HIT_CHANCE, SPELLFAMILY_HUNTER, 0x8000, 0x0, 0x0, me->GetGUID()) || + victim->GetAuraEffect(SPELL_AURA_MOD_STUN, SPELLFAMILY_HUNTER, 0x0, 0x1000, 0x0, me->GetGUID()) || + victim->GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_HUNTER, 0x0, 0x100, 0x0, me->GetGUID()))) + { + int32 cost = spellInfo->CalcPowerCost(me, spellInfo->GetSchoolMask()); + CastSpellExtraArgs args(true); + args.AddSpellBP0(cost); + me->CastSpell(me, GLYPH_OF_ARCANE_SHOT_ENERGIZE, args); + } + } + //Lock and Load: consume charge + if (baseId == ARCANE_SHOT_1 || baseId == EXPLOSIVE_SHOT_1) + { + AuraEffect const* lock = me->GetAuraEffect(LOCK_AND_LOAD_BUFF, 0); + if (lock && lock->IsAffectingSpell(spellInfo)) + lock->GetBase()->DropCharge(); + } + if (baseId == READINESS_1) + { + SpellInfo const* cdInfo; + BotSpellMap const& myspells = GetSpellMap(); + for (BotSpellMap::const_iterator itr = myspells.begin(); itr != myspells.end(); ++itr) + { + if (itr->first == spellInfo->Id || itr->first == BESTIAL_WRATH_1 || itr->first == GIFT_OF_NAARU_HUNTER) + continue; + if (itr->second->spellId != 0 && itr->second->cooldown > 0) + { + cdInfo = sSpellMgr->GetSpellInfo(itr->first); + if (cdInfo && cdInfo->SpellFamilyName == SPELLFAMILY_HUNTER && cdInfo->GetRecoveryTime() > 0) + ResetSpellCooldown(itr->first); + } + } + } + //Improved Steady Shot: consume buff + if (baseId == AIMED_SHOT_1 || baseId == ARCANE_SHOT_1 || baseId == CHIMERA_SHOT_1) + { + if (AuraEffect const* stea = me->GetAuraEffect(IMPROVED_STEADY_SHOT_BUFF, 0)) + if (stea->IsAffectingSpell(spellInfo)) + me->RemoveAurasDueToSpell(IMPROVED_STEADY_SHOT_BUFF); + } + + //Aspect helper + switch (baseId) + { + case ASPECT_OF_THE_MONKEY_1: + Aspect = ASPECT_MONKEY; + break; + case ASPECT_OF_THE_HAWK_1: + Aspect = ASPECT_HAWK; + break; + case ASPECT_OF_THE_CHEETAH_1: + Aspect = ASPECT_CHEETAH; + break; + case ASPECT_OF_THE_VIPER_1: + Aspect = ASPECT_VIPER; + break; + case ASPECT_OF_THE_BEAST_1: + Aspect = ASPECT_BEAST; + break; + case ASPECT_OF_THE_PACK_1: + Aspect = ASPECT_PACK; + break; + case ASPECT_OF_THE_WILD_1: + Aspect = ASPECT_WILD; + break; + case ASPECT_OF_THE_DRAGONHAWK_1: + Aspect = ASPECT_DRAGONHAWK; + break; + default: + break; + } + } + + void SpellHitTarget(WorldObject* wtarget, SpellInfo const* spell) override + { + Unit* target = wtarget->ToUnit(); + if (!target) + return; + + if (target == me) + return; + + uint32 baseId = spell->GetFirstRankSpell()->Id; + uint8 lvl = me->GetLevel(); + + if (baseId == HUNTERS_MARK_1) + { + //Hunter's Mark helper + if (AuraEffect* mark = target->GetAuraEffect(spell->Id, 1, me->GetGUID())) + { + //Glyph of Hunter's Mark: +20% effect + //Improved Hunter's Mark: +30% effect + if (lvl >= 15) + mark->ChangeAmount(mark->GetAmount() + mark->GetAmount() / 2); + else if (lvl >= 10) + mark->ChangeAmount(mark->GetAmount() * 13 / 10); + } + } + //Improved Stings part 1: +30% damage + if ((GetSpec() == BOT_SPEC_HUNTER_MARKSMANSHIP) && lvl >= 25 && (baseId == SERPENT_STING_1 || baseId == WYVERN_STING_DOT_AURA_1)) + { + if (AuraEffect* stin = target->GetAuraEffect(spell->Id, 0, me->GetGUID())) + { + stin->ChangeAmount(stin->GetAmount() * 13 / 10); + } + } + //Improved Stings part 2: +30% effect: not working? + //Trap Mastery part 1 + if (lvl >= 15 && (baseId == FROST_TRAP_AURA || baseId == FREEZING_TRAP_AURA_1)) + { + if (Aura* freez = target->GetAura(spell->Id, me->GetGUID())) + { + int32 dur = freez->GetDuration() * 13 / 10; + freez->SetDuration(dur); + freez->SetMaxDuration(dur); + } + } + if (lvl >= 16 && baseId == IMMOLATION_TRAP_AURA_1) + { + if (AuraEffect* immo = target->GetAuraEffect(spell->Id, 0, me->GetGUID())) + { + //Glyph of Immolation Trap: -6 sec duration, +100% effect + immo->ChangeAmount(immo->GetAmount() * 2); + int32 dur = immo->GetBase()->GetDuration() - 6000; + immo->GetBase()->SetDuration(dur); + immo->GetBase()->SetMaxDuration(dur); + } + } + if (lvl >= 15 && baseId == SERPENT_STING_1) + { + if (Aura* sting = target->GetAura(spell->Id, me->GetGUID())) + { + //Glyph of Serpent Sting: +6 sec duration + //Improved Serpent Sting (24467): +3 sec duration + int32 dur = sting->GetDuration() + 6000; + if (lvl >= 60) + dur += 3000; + sting->SetDuration(dur); + sting->SetMaxDuration(dur); + } + } + if (lvl >= 30 && baseId == WING_CLIP_1) + { + //zzzOLD Improved Wing Clip (only on creatures): 30% to root target with Wing Clip + //normal creatures are rooted for 10 sec, elites+ for 6 sec + if (target->GetTypeId() == TYPEID_UNIT) + { + if (urand(1,100) <= 30) + { + uint32 clip = target->ToCreature()->GetCreatureTemplate()->rank == CREATURE_ELITE_NORMAL ? IMPROVED_WING_CLIP_NORMAL : IMPROVED_WING_CLIP_EX; + me->CastSpell(target, clip, true); + } + } + } + if (lvl >= 10 && baseId == CONCUSSIVE_SHOT_1) + { + //Improved Concussion Shot rank 2: 2 sec increased daze duration + if (Aura* concus = target->GetAura(spell->Id, me->GetGUID())) + { + int32 dur = concus->GetDuration() + 2000; + concus->SetDuration(dur); + concus->SetMaxDuration(dur); + } + + //zzzOLD Improved Concussion Shot: chance to stun target for 3 sec + if (urand(1,100) <= 15) + { + me->CastSpell(target, IMPROVED_CONCUSSION, true); + } + } + if ((GetSpec() == BOT_SPEC_HUNTER_MARKSMANSHIP) && lvl >= 50 && baseId == STEADY_SHOT_1) + { + //Improved Steady Shot: 15% chance + if (urand(1,100) <= 15) + { + me->CastSpell(me, IMPROVED_STEADY_SHOT_BUFF, true); + } + } + + OnSpellHitTarget(target, spell); + } + + void SpellHit(WorldObject* wcaster, SpellInfo const* spell) override + { + Unit* caster = wcaster->ToUnit(); + if (!caster) + return; + + uint32 baseId = spell->GetFirstRankSpell()->Id; + uint8 lvl = me->GetLevel(); + + //Feign Death should always clear combat on bots + if (baseId == FEIGN_DEATH_1 && me->GetMap()->IsDungeon()) + me->CombatStop(false, false); + + //Rapid Recuperation (Rapid Killing) hackfix: trigger 2% energize + if (baseId == RAPID_RECUPERATION_ENERGIZE) + { + me->CastSpell(me, RAPID_RECUPERATION_ENERGIZE_PCT_1, true); + } + //Rapid Recuperation (Rapid Fire): match duration + if (baseId == RAPID_RECUPERATION_BUFF && GetSpell(RAPID_FIRE_1)) + { + if (Aura const* rapi = me->GetAura(GetSpell(RAPID_FIRE_1))) + { + if (Aura* recu = me->GetAura(spell->Id)) + { + uint32 dur = rapi->GetDuration(); + recu->SetDuration(dur); + recu->SetMaxDuration(dur); + } + } + } + //Rapid Recuperation (Rapid Fire) + if ((GetSpec() == BOT_SPEC_HUNTER_MARKSMANSHIP) && baseId == RAPID_FIRE_1 && me->GetLevel() >= 45) + { + me->CastSpell(me, RAPID_RECUPERATION_BUFF, true); + } + //Rapid Recuperation (Rapid Killing) + if ((GetSpec() == BOT_SPEC_HUNTER_MARKSMANSHIP) && baseId == RAPID_KILLING_BUFF && me->GetLevel() >= 45) + { + me->CastSpell(me, RAPID_RECUPERATION_BUFF2, true); + } + if (baseId == RAPID_FIRE_1 && lvl >= 26) + { + //Rapid Fire (id 28755): 4 sec increased duration + //Glyph of Rapid Fire: +8% haste + if (AuraEffect* rapi = me->GetAuraEffect(spell->Id, 0, me->GetGUID())) + { + rapi->ChangeAmount(rapi->GetAmount() + 8); + + uint32 dur = rapi->GetBase()->GetDuration() + 4000; + rapi->GetBase()->SetDuration(dur); + rapi->GetBase()->SetMaxDuration(dur); + } + } + if (baseId == QUICK_SHOTS_BUFF) + { + if (AuraEffect* quic = me->GetAuraEffect(QUICK_SHOTS_BUFF, 0)) + { + //base 15% haste + int32 newAmount = quic->GetAmount() + 15; + //Glyph of the Hawk: +6% effect flat + if (lvl >= 15) + newAmount += 6; + + quic->ChangeAmount(newAmount); + } + } + if ((baseId == ASPECT_OF_THE_CHEETAH_1 || baseId == ASPECT_OF_THE_PACK_1) && caster == me && lvl >= 20) + { + //Pathfinding: +8% increased effect + if (AuraEffect* spee = me->GetAuraEffect(spell->Id, 0, me->GetGUID())) + { + spee->ChangeAmount(spee->GetAmount() + 8); + } + } + if ((baseId == ASPECT_OF_THE_MONKEY_1 || baseId == ASPECT_OF_THE_DRAGONHAWK_MONKEY) && lvl >= 15) + { + //Improved Aspect of The Monkey: +6% dodge chance + if (AuraEffect* monk = me->GetAuraEffect(spell->Id, 0, me->GetGUID())) + { + monk->ChangeAmount(monk->GetAmount() + 6); + } + } + //Aspect Mastery + if (lvl >= 20) + { + if (baseId == ASPECT_OF_THE_VIPER_1) + { + if (AuraEffect* vipe = me->GetAuraEffect(spell->Id, 1, me->GetGUID())) + { + //part 1: 10% reduced damage penalty + vipe->ChangeAmount(vipe->GetAmount() + 10); + } + } + if (baseId == ASPECT_OF_THE_MONKEY_1 || baseId == ASPECT_OF_THE_DRAGONHAWK_MONKEY) + { + if (AuraEffect* monk = me->GetAuraEffect(spell->Id, 1, me->GetGUID())) + { + //part 2: 5% reduced damage + monk->ChangeAmount(monk->GetAmount() - 5); + } + } + if (baseId == ASPECT_OF_THE_HAWK_1 || baseId == ASPECT_OF_THE_DRAGONHAWK_1) + { + if (AuraEffect* hawk = me->GetAuraEffect(spell->Id, 0, me->GetGUID())) + { + //part 3: 30% attack power bonus + hawk->ChangeAmount(hawk->GetAmount() * 13 / 10); + } + } + } + + OnSpellHit(caster, spell); + } + + void OnBotDamageDealt(Unit* victim, uint32 damage, CleanDamage const* cleanDamage, DamageEffectType damagetype, SpellInfo const* /*spellInfo*/) override + { + if (botPet && victim != me && victim != botPet && damage > 0 && cleanDamage && cleanDamage->hitOutCome == MELEE_HIT_CRIT && + (damagetype == DIRECT_DAMAGE || damagetype == SPELL_DIRECT_DAMAGE) && me->GetLevel() >= 20) + { + //Go for the Throat: energize pet + me->EnergizeBySpell(botPet, GO_FOR_THE_THROAT_ENERGIZE, 50, POWER_FOCUS); + //Frenzy hack: proc from hunter's crits + if (me->GetLevel() >= 35) + botPet->CastSpell(botPet, FRENZY_BUFF, true); + } + } + + void DamageDealt(Unit* victim, uint32& damage, DamageEffectType damageType) override + { + bot_ai::DamageDealt(victim, damage, damageType); + } + + void DamageTaken(Unit* u, uint32& /*damage*/, DamageEffectType /*damageType*/, SpellInfo const* /*spellInfo*/) override + { + if (!u) + return; + if (!u->IsInCombat() && !me->IsInCombat()) + return; + OnOwnerDamagedBy(u); + } + + void OwnerAttackedBy(Unit* u) override + { + if (!u) + return; + OnOwnerDamagedBy(u); + } + + void SummonBotPet() + { + if (botPet) + UnsummonAll(false); + + if (me->GetLevel() < 10) + return; + + if (myPetType == BOT_PET_INVALID) //disabled + return; + + if (petSummonTimer > GetLastDiff()) + return; + + uint32 entry; + + if (myPetType) + entry = myPetType; + else if (!IAmFree()) + { + if ((master->GetGroup() && master->GetGroup()->isRaidGroup()) || master->GetNpcBotsCount() >= 10) + entry = BOT_PET_WOLF; //raid pet + else if (!IsMeleeClass(master->GetClass())) + entry = urand(BOT_PET_TENACITY_START, BOT_PET_TENACITY_END); + else if (sWorld->IsFFAPvPRealm() || sWorld->IsPvPRealm()) + entry = urand(BOT_PET_CUNNING_START, BOT_PET_CUNNING_END); + else + entry = urand(BOT_PET_HUNTER_START, BOT_PET_HUNTER_END_GENERAL); + } + else + entry = urand(BOT_PET_HUNTER_START, BOT_PET_HUNTER_END_GENERAL); + + //ensurance + if (entry < BOT_PET_HUNTER_START || entry > BOT_PET_HUNTER_END_EXOTIC || + (entry >= BOT_PET_EXOTIC_START && _spec != BOT_SPEC_HUNTER_BEASTMASTERY)) + entry = 0; + + myPetType = entry; + + //try next time + if (!myPetType) + return; + + petSummonTimer = 10000; + ResetSpellCooldown(KINDRED_SPIRITS_PET); + ResetSpellCooldown(SPIRIT_BOND_PET); + + Position pos; + + me->CastSpell(me, CALL_PET_VISUAL, true); + Creature* myPet = me->SummonCreature(myPetType, *me, TEMPSUMMON_CORPSE_DESPAWN); + me->GetNearPoint(myPet, pos.m_positionX, pos.m_positionY, pos.m_positionZ, 0, me->GetOrientation() + M_PI / 2); + myPet->GetMotionMaster()->MovePoint(me->GetMapId(), pos); + myPet->SetCreator(master); + myPet->SetOwnerGUID(me->GetGUID()); + myPet->SetFaction(master->GetFaction()); + myPet->SetControlledByPlayer(!IAmFree()); + myPet->SetPvP(me->IsPvP()); + myPet->SetByteValue(UNIT_FIELD_BYTES_2, 1, master->GetByteValue(UNIT_FIELD_BYTES_2, 1)); + + //fix scale + switch (myPetType) + { + case BOT_PET_RAVAGER: + case BOT_PET_WASP: + case BOT_PET_TEROMOTH: + case BOT_PET_SCORPID: + case BOT_PET_TURTLE: + case BOT_PET_BEAR: + case BOT_PET_WARPSTALKER: + case BOT_PET_COREHOUND: + myPet->SetObjectScale(0.75f); + break; + case BOT_PET_CHIMAERA: + myPet->SetObjectScale(0.67f); + break; + case BOT_PET_RAPTOR: + case BOT_PET_DEVILSAUR: + case BOT_PET_RHINO: + myPet->SetObjectScale(0.5f); + break; + default: + break; + } + + botPet = myPet; + } + + void UnsummonAll(bool savePets = true) override + { + UnsummonPet(savePets); + } + + void SummonedCreatureDies(Creature* /*summon*/, Unit* /*killer*/) override + { + //TC_LOG_ERROR("entities.unit", "SummonedCreatureDies: {}'s {}", me->GetName(), summon->GetName()); + //if (summon == botPet) + // botPet = nullptr; + } + + void SummonedCreatureDespawn(Creature* summon) override + { + //all hunter bot pets despawn at death or manually (gossip, teleport, etc.) + //TC_LOG_ERROR("entities.unit", "SummonedCreatureDespawn: {}'s {}", me->GetName(), summon->GetName()); + if (summon == botPet) + { + petSummonTimer = 10000; + botPet = nullptr; + } + } + + float GetSpellAttackRange(bool longRange) const override + { + return longRange ? CalcSpellMaxRange(AUTO_SHOT_1) : 25.f; + } + + uint32 GetAIMiscValue(uint32 data) const override + { + switch (data) + { + case BOTAI_MISC_PET_TYPE: + return myPetType; + case BOTAI_MISC_PET_AVAILABLE_1: + return BOT_PET_WOLF; + case BOTAI_MISC_PET_AVAILABLE_2: + return BOT_PET_CUNNING_START; + case BOTAI_MISC_PET_AVAILABLE_3: + return BOT_PET_FEROCITY_START; + case BOTAI_MISC_PET_AVAILABLE_4: + return BOT_PET_TENACITY_START; + case BOTAI_MISC_PET_AVAILABLE_5: + return me->GetLevel() >= 80 ? BOT_PET_SILITHID : 0; + case BOTAI_MISC_PET_AVAILABLE_6: + return me->GetLevel() >= 80 ? BOT_PET_CHIMAERA : 0; + case BOTAI_MISC_PET_AVAILABLE_7: + return me->GetLevel() >= 80 ? BOT_PET_SPIRITBEAST : 0; + case BOTAI_MISC_PET_AVAILABLE_8: + return me->GetLevel() >= 80 ? BOT_PET_COREHOUND : 0; + case BOTAI_MISC_PET_AVAILABLE_9: + return me->GetLevel() >= 80 ? BOT_PET_DEVILSAUR : 0; + case BOTAI_MISC_PET_AVAILABLE_10: + return me->GetLevel() >= 80 ? BOT_PET_RHINO : 0; + case BOTAI_MISC_PET_AVAILABLE_11: + return me->GetLevel() >= 80 ? BOT_PET_WORM : 0; + default: + return 0; + } + } + + void SetAIMiscValue(uint32 data, uint32 value) override + { + switch (data) + { + case BOTAI_MISC_PET_TYPE: + myPetType = value; + UnsummonAll(false); + break; + default: + break; + } + } + + void Reset() override + { + UnsummonAll(false); + + myPetType = 0; + + trapTimer = 0; + stingTimer = 0; + aspectTimer = 0; + flareTimer = 0; + misdirectionTimer = 0; + checkMendTimer = 0; + + petSummonTimer = 5000; + + Aspect = 0; + + DefaultInit(); + } + + void ReduceCD(uint32 diff) override + { + if (trapTimer > diff) trapTimer -= diff; + if (stingTimer > diff) stingTimer -= diff; + if (aspectTimer > diff) aspectTimer -= diff; + if (flareTimer > diff) flareTimer -= diff; + if (misdirectionTimer > diff) misdirectionTimer -= diff; + if (checkMendTimer > diff) checkMendTimer -= diff; + + if (petSummonTimer > diff) petSummonTimer -= diff; + } + + void InitPowers() override + { + me->SetPowerType(POWER_MANA); + + if (botPet && botPet->GetPowerType() != POWER_FOCUS) + botPet->SetByteValue(UNIT_FIELD_BYTES_0, 3, POWER_FOCUS); + } + + void InitSpells() override + { + uint8 lvl = me->GetLevel(); + //bool isBeas = GetSpec() == BOT_SPEC_HUNTER_BEASTMASTERY; + bool isMark = GetSpec() == BOT_SPEC_HUNTER_MARKSMANSHIP; + bool isSurv = GetSpec() == BOT_SPEC_HUNTER_SURVIVAL; + + InitSpellMap(AUTO_SHOT_1); + InitSpellMap(ARCANE_SHOT_1); + InitSpellMap(TRANQ_SHOT_1); + InitSpellMap(STEADY_SHOT_1); + InitSpellMap(KILL_SHOT_1); + InitSpellMap(MULTISHOT_1); + InitSpellMap(VOLLEY_1); + InitSpellMap(CONCUSSIVE_SHOT_1); + InitSpellMap(DISTRACTING_SHOT_1); + InitSpellMap(SERPENT_STING_1); + InitSpellMap(SCORPID_STING_1); + InitSpellMap(VIPER_STING_1); + InitSpellMap(RAPID_FIRE_1); + InitSpellMap(FLARE_1); + InitSpellMap(WING_CLIP_1); + InitSpellMap(RAPTOR_STRIKE_1); + InitSpellMap(MONGOOSE_BITE_1); + InitSpellMap(DISENGAGE_1); + InitSpellMap(IMMOLATION_TRAP_1); + InitSpellMap(FREEZING_TRAP_1); + InitSpellMap(FROST_TRAP_1); + InitSpellMap(EXPLOSIVE_TRAP_1); + InitSpellMap(FREEZING_ARROW_1); + InitSpellMap(HUNTERS_MARK_1); + InitSpellMap(SCARE_BEAST_1); + InitSpellMap(FEIGN_DEATH_1); + InitSpellMap(DETERRENCE_1); + InitSpellMap(MISDIRECTION_1); + InitSpellMap(MEND_PET_1); + + InitSpellMap(ASPECT_OF_THE_MONKEY_1); + InitSpellMap(ASPECT_OF_THE_HAWK_1); + InitSpellMap(ASPECT_OF_THE_CHEETAH_1); + InitSpellMap(ASPECT_OF_THE_VIPER_1); + //InitSpellMap(ASPECT_OF_THE_BEAST_1); + InitSpellMap(ASPECT_OF_THE_PACK_1); + InitSpellMap(ASPECT_OF_THE_WILD_1); + InitSpellMap(ASPECT_OF_THE_DRAGONHAWK_1); + + /*Talent*/lvl >= (isMark ? 20 : 70) ? InitSpellMap(AIMED_SHOT_1) : RemoveSpell(AIMED_SHOT_1); + /*Talent*/lvl >= 30 && isMark ? InitSpellMap(READINESS_1) : RemoveSpell(READINESS_1); + /*Talent*/lvl >= 40 && isMark ? InitSpellMap(TRUESHOT_AURA_1) : RemoveSpell(TRUESHOT_AURA_1); + /*Talent*/lvl >= 50 && isMark ? InitSpellMap(SILENCING_SHOT_1) : RemoveSpell(SILENCING_SHOT_1); + /*Talent*/lvl >= 60 && isMark ? InitSpellMap(CHIMERA_SHOT_1) : RemoveSpell(CHIMERA_SHOT_1); + + /*Talent*/lvl >= (isSurv ? 20 : isMark ? 70 : 99) ? InitSpellMap(SCATTER_SHOT_1) : RemoveSpell(SCATTER_SHOT_1); + /*Talent*/lvl >= 30 && isSurv ? InitSpellMap(COUNTERATTACK_1) : RemoveSpell(COUNTERATTACK_1); + /*Talent*/lvl >= 40 && isSurv ? InitSpellMap(WYVERN_STING_1) : RemoveSpell(WYVERN_STING_1); + /*Talent*/lvl >= 50 && isSurv ? InitSpellMap(BLACK_ARROW_1) : RemoveSpell(BLACK_ARROW_1); + /*Talent*/lvl >= 60 && isSurv ? InitSpellMap(EXPLOSIVE_SHOT_1) : RemoveSpell(EXPLOSIVE_SHOT_1); + } + + void ApplyClassPassives() const override + { + uint8 level = master->GetLevel(); + bool isBeas = GetSpec() == BOT_SPEC_HUNTER_BEASTMASTERY; + bool isMark = GetSpec() == BOT_SPEC_HUNTER_MARKSMANSHIP; + bool isSurv = GetSpec() == BOT_SPEC_HUNTER_SURVIVAL; + + RefreshAura(IMPROVED_MEND_PET, isBeas && level >= 25 ? 1 : 0); + + RefreshAura(RAPID_KILLING, isMark && level >= 20 ? 1 : 0); + RefreshAura(CONCUSSIVE_BARRAGE, isMark && level >= 30 ? 1 : 0); + RefreshAura(PIERCING_SHOTS, isMark && level >= 40 ? 1 : 0); + //RefreshAura(TRUESHOT_AURA, isMark && level >= 40 ? 1 : 0); + RefreshAura(MASTER_MARKSMAN, isMark && level >= 45 ? 1 : 0); + RefreshAura(WILD_QUIVER, isMark && level >= 50 ? 1 : 0); + + RefreshAura(SUREFOOTED, level >= 15 ? 1 : 0); + RefreshAura(ENTRAPMENT, isSurv && level >= 15 ? 1 : 0); + RefreshAura(LOCK_AND_LOAD, isSurv && level >= 25 ? 1 : 0); + RefreshAura(EXPOSE_WEAKNESS, isSurv && level >= 40 ? 1 : 0); + RefreshAura(THRILL_OF_THE_HUNT, isSurv && level >= 40 ? 1 : 0); + RefreshAura(MASTER_TACTICIAN5, isSurv && level >= 50 ? 1 : 0); + RefreshAura(MASTER_TACTICIAN4, isSurv && level >= 49 && level < 50 ? 1 : 0); + RefreshAura(MASTER_TACTICIAN3, isSurv && level >= 48 && level < 49 ? 1 : 0); + RefreshAura(MASTER_TACTICIAN2, isSurv && level >= 47 && level < 48 ? 1 : 0); + RefreshAura(MASTER_TACTICIAN1, isSurv && level >= 46 && level < 47 ? 1 : 0); + RefreshAura(NOXIOUS_STINGS, isSurv && level >= 45 ? 1 : 0); + RefreshAura(SNIPER_TRAINING, isSurv && level >= 50 ? 1 : 0); + + RefreshAura(GLYPH_RAPTOR_STRIKE, level >= 15 ? 1 : 0); + RefreshAura(GLYPH_ASPECT_OF_THE_VIPER, level >= 20 ? 1 : 0); + RefreshAura(GLYPH_FREEZING_TRAP, level >= 20 ? 1 : 0); + RefreshAura(GLYPH_EXPLOSIVE_TRAP, level >= 34 ? 1 : 0); + + RefreshAura(HUNTER_T8_P2, level >= 70 ? 1 : 0); + RefreshAura(HUNTER_T10_P2, level >= 75 ? 1 : 0); + RefreshAura(HUNTER_T10_P4, level >= 80 ? 1 : 0); + } + + bool CanUseManually(uint32 basespell) const override + { + switch (basespell) + { + //case RAPID_FIRE_1: + case FLARE_1: + case MEND_PET_1: + case IMMOLATION_TRAP_1: + case FREEZING_TRAP_1: + case FROST_TRAP_1: + case EXPLOSIVE_TRAP_1: + case VOLLEY_1: + //case ASPECT_OF_THE_MONKEY_1: + //case ASPECT_OF_THE_HAWK_1: + //case ASPECT_OF_THE_CHEETAH_1: + //case ASPECT_OF_THE_VIPER_1: + //case ASPECT_OF_THE_BEAST_1: + case ASPECT_OF_THE_PACK_1: + case ASPECT_OF_THE_WILD_1: + //case ASPECT_OF_THE_DRAGONHAWK_1: + return true; + default: + return false; + } + } + + bool HasAbilitiesSpecifics() const override { return true; } + void FillAbilitiesSpecifics(Player const* player, std::list &specList) override + { + uint32 textId; + switch (Aspect) + { + case ASPECT_MONKEY: textId = BOT_TEXT_MONKEY; break; + case ASPECT_HAWK: textId = BOT_TEXT_HAWK; break; + case ASPECT_CHEETAH: textId = BOT_TEXT_CHEETAH; break; + case ASPECT_VIPER: textId = BOT_TEXT_VIPER; break; + case ASPECT_BEAST: textId = BOT_TEXT_BEAST; break; + case ASPECT_PACK: textId = BOT_TEXT_PACK; break; + case ASPECT_WILD: textId = BOT_TEXT_WILD; break; + case ASPECT_DRAGONHAWK: textId = BOT_TEXT_DRAGONHAWK; break; + default: textId = BOT_TEXT_NOASPECT; break; + } + specList.push_back(LocalizedNpcText(player, BOT_TEXT_ASPECT) + ": " + LocalizedNpcText(player, textId)); + } + + std::vector const* GetDamagingSpellsList() const override + { + return &Hunter_spells_damage; + } + std::vector const* GetCCSpellsList() const override + { + return &Hunter_spells_cc; + } + //std::vector const* GetHealingSpellsList() const override + //{ + // return &Hunter_spells_heal; + //} + std::vector const* GetSupportSpellsList() const override + { + return &Hunter_spells_support; + } + + private: + uint32 trapTimer, stingTimer, aspectTimer, flareTimer, misdirectionTimer, checkMendTimer; + uint8 Aspect; + //Pet + uint32 myPetType; + uint32 petSummonTimer; + + //Scans target for hunter's aspects + //returns applied aspects mask + //used for finding out which aspects target lacks + uint32 _getAspectsMask(std::map& idMap) const + { + uint32 mask = 0; + + uint32 baseId; + bool isAspect; + Unit::AuraApplicationMap const& aurapps = me->GetAppliedAuras(); + for (Unit::AuraApplicationMap::const_iterator itr = aurapps.begin(); itr != aurapps.end(); ++itr) + { + isAspect = true; + baseId = itr->second->GetBase()->GetSpellInfo()->GetFirstRankSpell()->Id; + switch (baseId) + { + //case ASPECT_OF_THE_MONKEY_1: + // mask |= SPECIFIC_ASPECT_MONKEY; + // break; + case ASPECT_OF_THE_HAWK_1: + mask |= SPECIFIC_ASPECT_HAWK; + break; + case ASPECT_OF_THE_CHEETAH_1: + mask |= SPECIFIC_ASPECT_CHEETAH; + break; + //case ASPECT_OF_THE_VIPER_1: + // mask |= SPECIFIC_ASPECT_VIPER; + // break; + //case ASPECT_OF_THE_BEAST_1: + // mask |= SPECIFIC_ASPECT_BEAST; + // break; + case ASPECT_OF_THE_PACK_1: + mask |= SPECIFIC_ASPECT_PACK; + break; + case ASPECT_OF_THE_WILD_1: + mask |= SPECIFIC_ASPECT_WILD; + break; + case ASPECT_OF_THE_DRAGONHAWK_1: + mask |= SPECIFIC_ASPECT_DRAGONHAWK; + break; + default: + isAspect = false; //next aura + break; + } + + if (isAspect) + { + idMap[baseId] = itr->first; + if (itr->second->GetBase()->GetCasterGUID() == me->GetGUID()) + mask |= SPECIFIC_ASPECT_MY_ASPECT; + } + } + + return mask; + } + }; +}; + +void AddSC_hunter_bot() +{ + new hunter_bot(); +} diff --git a/src/server/game/AI/NpcBots/bot_mage_ai.cpp b/src/server/game/AI/NpcBots/bot_mage_ai.cpp new file mode 100644 index 000000000..48ed39695 --- /dev/null +++ b/src/server/game/AI/NpcBots/bot_mage_ai.cpp @@ -0,0 +1,1838 @@ +#include "bot_ai.h" +#include "botmgr.h" +#include "botspell.h" +#include "bottraits.h" +#include "Containers.h" +#include "GameEventMgr.h" +#include "Group.h" +#include "Item.h" +#include "Map.h" +#include "MotionMaster.h" +#include "ObjectAccessor.h" +#include "ObjectMgr.h" +#include "Player.h" +#include "ScriptMgr.h" +#include "Spell.h" +#include "SpellAuraEffects.h" +#include "SpellMgr.h" +#include "TemporarySummon.h" +/* +Mage NpcBot (reworked by Trickerer onlysuffering@gmail.com) +Complete - 92-97% +TODO: slow (pvp), mana shield +*/ + +enum MageBaseSpells +{ + DAMPENMAGIC_1 = 604, + AMPLIFYMAGIC_1 = 1008,//manual use only + ARCANEINTELLECT_1 = 1459, + ARCANEMISSILES_1 = 5143, + ARCANE_BLAST_1 = 30451, + POLYMORPH_1 = 118, + COUNTERSPELL_1 = 2139, + SPELLSTEAL_1 = 30449, + EVOCATION_1 = 12051, + BLINK_1 = 1953, + REMOVE_CURSE_1 = 475, + INVISIBILITY_1 = 66, + SCORCH_1 = 2948, + BLAST_WAVE_1 = 11113, + DRAGON_BREATH_1 = 31661, + FIRE_BLAST_1 = 2136, + PYROBLAST_1 = 11366, + LIVING_BOMB_1 = 44457, + FLAMESTRIKE_1 = 2120, + COMBUSTION_1 = 11129, + FROSTFIRE_BOLT_1 = 44614, + FIREBALL_1 = 133, + FROSTBOLT_1 = 116, + FROST_NOVA_1 = 122, + CONE_OF_COLD_1 = 120, + BLIZZARD_1 = 10, + FROST_ARMOR_1 = 168, + ICE_ARMOR_1 = 7302, + MOLTEN_ARMOR_1 = 30482, + ICE_BARRIER_1 = 11426, + ICE_BLOCK_1 = 45438, + FOCUS_MAGIC_1 = 54646, + PRESENCE_OF_MIND_1 = 12043, + ARCANE_POWER_1 = 12042, + SLOW_FALL_1 = 130, + ICE_LANCE_1 = 30455, + ICY_VEINS_1 = 12472, + COLD_SNAP_1 = 11958, + DEEP_FREEZE_1 = 44572, + FROST_WARD_1 = 6143, + FIRE_WARD_1 = 543, + MIRROR_IMAGE_1 = 55342, + //Special + ARCANE_MISSILES_DAMAGE_1 = 7268, + BLIZZARD_DAMAGE_1 = 42208, + LIVING_BOMB_DAMAGE_1 = 44461, + CONJURE_MANA_GEM_1 = 759, + MANA_GEM_1 = 5405, + RITUAL_OF_REFRESHMENT_1 = 43987, + + SUMMON_WATER_ELEMENTAL_1 = 31687 +}; + +enum MagePassives +{ +//Talents + FROSTBITE1 = 11071, + FROSTBITE2 = 12496, + FROSTBITE3 = 12497, + ARCANE_CONCENTRATION = 12577,//rank 5, clearcast + IGNITE = 12848,//rank 5 + BURNING_DETERMINATION = 54749,//rank 2 + FROST_WARDING = 28332,//rank 2 + IMPROVED_COUNTERSPELL1 = 11255, + IMPROVED_COUNTERSPELL2 = 12598, + ARCANE_MEDITATION = 18464,//rank 3 + TORMENT_THE_WEAK = 55340,//rank 3 + IMPACT = 12358,//rank 3 + IMPROVED_BLIZZARD = 12488,//rank 3 + IMPROVED_SCORCH = 12873,//rank 3 + MOLTEN_SHIELDS = 13043,//rank 2 + MASTER_OF_ELEMENTS = 29076,//rank 3 + SHATTER1 = 11170, + SHATTER2 = 12982, + SHATTER3 = 12983, + ARCANE_POTENCY1 = 31571, + ARCANE_POTENCY2 = 31572, + BLAZING_SPEED = 31642,//rank 2 + WINTERS_CHILL1 = 11180, + WINTERS_CHILL2 = 28592, + WINTERS_CHILL3 = 28593, + ARCANE_EMPOWERMENT = 31583,//rank 3 + INCANTERS_ABSORPTION1 = 44394, + INCANTERS_ABSORPTION2 = 44395, + INCANTERS_ABSORPTION3 = 44396, + MISSILE_BARRAGE = 54490,//rank 5 + PYROMANIAC = 34296,//rank 3 + SHATTERED_BARRIER = 54787,//rank 2 + //ARCTIC_WINDS = 31678,//rank 5 + FINGERS_OF_FROST = 44545,//rank 2 + FIRESTARTER1 = 44442, + FIRESTARTER2 = 44443, + HOT_STREAK = 44448,//rank 3 + BRAIN_FREEZE1 = 44546, + BRAIN_FREEZE2 = 44548, + BRAIN_FREEZE3 = 44549, + + GLYPH_POLYMORPH = 56375, + GLYPG_REMOVE_CURSE = 56364, + GLYPH_ICY_VEINS = 56374, + GLYPH_LIVING_BOMB = 63091, + GLYPH_ICE_LANCE = 56377 +}; +enum MageSpecial +{ + ARCANE_CONCENTRATION_BUFF = 12536, + IMPACT_BUFF = 64343, + FIRESTARTER_BUFF = 54741, + ARCANE_POTENCY_BUFF1 = 57529, + ARCANE_POTENCY_BUFF2 = 57531, + COMBUSTION_BUFF = 28682, + BRAIN_FREEZE_BUFF = 57761, + HOT_STREAK_BUFF = 48108, + FINGERS_OF_FROST_BUFF = 44544, + ARCANE_BLAST_DEBUFF = 36032, + MISSILE_BARRAGE_BUFF = 44401, + IMPROVED_BLIZZARD_CHILL = 12486,//rank 3 + FROSTBITE_TRIGGERED = 12494, + WINTERS_CHILL_TRIGGERED = 12579, + IGNITE_TRIGGERED = 12654 +}; + +static const uint32 Mage_spells_damage_arr[] = +{ ARCANEMISSILES_1, ARCANE_BLAST_1, BLAST_WAVE_1, BLIZZARD_1, CONE_OF_COLD_1, DEEP_FREEZE_1, DRAGON_BREATH_1, FIREBALL_1, +FIRE_BLAST_1, FLAMESTRIKE_1, FROSTBOLT_1, FROSTFIRE_BOLT_1, FROST_NOVA_1, ICE_LANCE_1, LIVING_BOMB_1, PYROBLAST_1, +SCORCH_1 }; + +static const uint32 Mage_spells_cc_arr[] = +{ COUNTERSPELL_1, DRAGON_BREATH_1, DEEP_FREEZE_1, FROST_NOVA_1, POLYMORPH_1 }; + +static const uint32 Mage_spells_support_arr[] = +{ AMPLIFYMAGIC_1, ARCANEINTELLECT_1, BLINK_1, COMBUSTION_1, DAMPENMAGIC_1, EVOCATION_1, FIRE_WARD_1, FROST_WARD_1, +FROST_ARMOR_1, FOCUS_MAGIC_1, ICE_BARRIER_1, ICE_BLOCK_1, ICY_VEINS_1, INVISIBILITY_1, ICE_ARMOR_1, MOLTEN_ARMOR_1, +SLOW_FALL_1, SPELLSTEAL_1, REMOVE_CURSE_1, CONJURE_MANA_GEM_1, RITUAL_OF_REFRESHMENT_1, SUMMON_WATER_ELEMENTAL_1, +COLD_SNAP_1, PRESENCE_OF_MIND_1, ARCANE_POWER_1 }; + +static const std::vector Mage_spells_damage(FROM_ARRAY(Mage_spells_damage_arr)); +static const std::vector Mage_spells_cc(FROM_ARRAY(Mage_spells_cc_arr)); +static const std::vector Mage_spells_support(FROM_ARRAY(Mage_spells_support_arr)); + +class mage_bot : public CreatureScript +{ +public: + mage_bot() : CreatureScript("mage_bot") { } + + CreatureAI* GetAI(Creature* creature) const override + { + return new mage_botAI(creature); + } +/* + bool OnGossipHello(Player* player, Creature* creature) + { + return creature->GetBotAI()->OnGossipHello(player, 0); + } + + bool OnGossipSelect(Player* player, Creature* creature, uint32 sender, uint32 action) + { + if (bot_ai* ai = creature->GetBotAI()) + return ai->OnGossipSelect(player, creature, sender, action); + return true; + } + + bool OnGossipSelectCode(Player* player, Creature* creature, uint32 sender, uint32 action, char const* code) + { + if (bot_ai* ai = creature->GetBotAI()) + return ai->OnGossipSelectCode(player, creature, sender, action, code); + return true; + } +*/ + struct mage_botAI : public bot_ai + { + mage_botAI(Creature* creature) : bot_ai(creature) + { + _botclass = BOT_CLASS_MAGE; + + InitUnitFlags(); + } + + bool doCast(Unit* victim, uint32 spellId) + { + if (CheckBotCast(victim, spellId) != SPELL_CAST_OK) + return false; + return bot_ai::doCast(victim, spellId); + } + + void JustEnteredCombat(Unit* u) override { canFrostWard = false; canFireWard = false; bot_ai::JustEnteredCombat(u); } + void KilledUnit(Unit* u) override { bot_ai::KilledUnit(u); } + void EnterEvadeMode(EvadeReason why = EVADE_REASON_OTHER) override { bot_ai::EnterEvadeMode(why); } + void MoveInLineOfSight(Unit* u) override { bot_ai::MoveInLineOfSight(u); } + void JustDied(Unit* u) override { UnsummonAll(false); bot_ai::JustDied(u); } + + void StartAttack(Unit* u, bool force = false) + { + if (!bot_ai::StartAttack(u, force)) + return; + GetInPosition(force, u); + } + + void Counter(uint32 diff) + { + //skip if evocation, blizzard + if (IsChanneling() || Rand() > 30) + return; + + if (IsSpellReady(COUNTERSPELL_1, diff, false)) + { + if (Unit* target = FindCastingTarget(CalcSpellMaxRange(COUNTERSPELL_1), 0, COUNTERSPELL_1)) + { + me->InterruptNonMeleeSpells(false); + if (doCast(target, GetSpell(COUNTERSPELL_1))) + return; + } + } + if (IsSpellReady(DEEP_FREEZE_1, diff) && me->HasAuraType(SPELL_AURA_ABILITY_IGNORE_AURASTATE)) + { + if (Unit* target = FindCastingTarget(CalcSpellMaxRange(DEEP_FREEZE_1), 0, DEEP_FREEZE_1)) + { + me->InterruptNonMeleeSpells(false); + if (doCast(target, GetSpell(DEEP_FREEZE_1))) + return; + } + } + if (IsSpellReady(FIRE_BLAST_1, diff) && me->HasAura(IMPACT_BUFF)) + { + if (Unit* target = FindCastingTarget(CalcSpellMaxRange(FIRE_BLAST_1), 0, FIRE_BLAST_1)) + { + me->InterruptNonMeleeSpells(false); + if (doCast(target, GetSpell(FIRE_BLAST_1))) + return; + } + } + if (!IsCasting() && IsSpellReady(POLYMORPH_1, diff)) + { + if (Unit* target = FindCastingTarget(CalcSpellMaxRange(POLYMORPH_1), 0, POLYMORPH_1, 75)) + { + if (doCast(target, GetSpell(POLYMORPH_1))) + return; + } + } + } + + void CheckSpellSteal(uint32 diff) + { + if (!IsSpellReady(SPELLSTEAL_1, diff) || IsCasting() || Rand() > 15) + return; + + Unit* target = FindHostileDispelTarget(CalcSpellMaxRange(SPELLSTEAL_1), true); + if (target && doCast(target, GetSpell(SPELLSTEAL_1))) + return; + } + + void DoNonCombatActions(uint32 diff) + { + if (GC_Timer > diff || me->IsMounted() || Feasting() || Rand() > 25) + return; + + //slow fall + if (GetSpell(SLOW_FALL_1) && !IAmFree()) + { + Player* fPlayer = nullptr; + Group const* gr = master->GetGroup(); + if (gr) + { + for (GroupReference const* ref = gr->GetFirstMember(); ref != nullptr; ref = ref->next()) + { + Player* pl = ref->GetSource(); + if (pl && pl->IsAlive() && pl->FindMap() == me->GetMap() && pl->GetDistance(me) < 30 && + pl->IsFalling() && pl->m_movementInfo.fallTime > 1000 && + !pl->HasAuraType(SPELL_AURA_FEATHER_FALL)) + { + fPlayer = pl; + break; + } + } + } + else if (master->IsAlive() && master->GetDistance(me) < 30 && master->IsFalling() && + master->m_movementInfo.fallTime > 1000 && !master->HasAuraType(SPELL_AURA_FEATHER_FALL)) + fPlayer = master; + + if (fPlayer && doCast(fPlayer, GetSpell(SLOW_FALL_1))) + return; + } + + //ARMOR + uint32 MOLTENARMOR = HasRole(BOT_ROLE_DPS) ? GetSpell(MOLTEN_ARMOR_1) : GetSpell(ICE_ARMOR_1); + uint32 ICEARMOR = GetSpell(ICE_ARMOR_1) ? GetSpell(ICE_ARMOR_1) : GetSpell(FROST_ARMOR_1); + uint32 ARMOR = !MOLTENARMOR ? ICEARMOR : (me->GetMap()->IsDungeon() || !ICEARMOR) ? MOLTENARMOR : ICEARMOR; + if (ARMOR && !me->HasAura(ARMOR)) + { + if (doCast(me, ARMOR)) + return; + } + + if (GetSpell(CONJURE_MANA_GEM_1)) + { + if (manaGemCharges == 0 && + doCast(me, GetSpell(CONJURE_MANA_GEM_1))) + return; + } + if (GetSpell(DAMPENMAGIC_1)) + { + if (!me->HasAuraTypeWithFamilyFlags(SPELL_AURA_MOD_DAMAGE_TAKEN, SPELLFAMILY_MAGE, 0x2000) + /*!HasAuraName(me, DAMPENMAGIC_1)*/ && + doCast(me, GetSpell(DAMPENMAGIC_1))) + return; + } + } + + bool BuffTarget(Unit* target, uint32 /*diff*/) override + { + if (me->IsInCombat() && !master->GetMap()->IsRaid()) return false; + + if (GetSpell(ARCANEINTELLECT_1) && target->GetMaxPower(POWER_MANA) > 1 && + !target->HasAuraTypeWithFamilyFlags(SPELL_AURA_MOD_STAT, SPELLFAMILY_MAGE, 0x400) + /*!HasAuraName(target, ARCANEINTELLECT_1)*/) + { + if (doCast(target, GetSpell(ARCANEINTELLECT_1))) + return true; + } + + return false; + } + + void UpdateAI(uint32 diff) override + { + if (!GlobalUpdate(diff)) + return; + + DoVehicleActions(diff); + if (!CanBotAttackOnVehicle()) + return; + + CheckPots(diff); + + CheckPoly(diff); + CheckBlink(diff); + CheckIceBlock(diff); + + CheckRacials(diff); + + CheckShield(diff); + CureGroup(GetSpell(REMOVE_CURSE_1), diff); + CheckWard(diff); + + CheckFocusMagic(diff); + BuffAndHealGroup(diff); + + if (!me->IsInCombat()) + DoNonCombatActions(diff); + + //pet + if ((!botPet || !botPet->IsAlive()) && + IsSpellReady(SUMMON_WATER_ELEMENTAL_1, diff) && !IsCasting() && (IAmFree() || master->IsInCombat())) + if (doCast(me, GetSpell(SUMMON_WATER_ELEMENTAL_1))) + return; + + if (ProcessImmediateNonAttackTarget()) + return; + + if (!CheckAttackTarget()) + return; + + CheckPolymorph(diff);//this should go AFTER getting target + + Counter(diff); + CheckSpellSteal(diff); + CheckColdSnap(diff); + + if (IsCasting()) + return; + + if (me->HasInvisibilityAura()) + return; + + CheckUsableItems(diff); + + DoNormalAttack(diff); + } + + void DoNormalAttack(uint32 diff) + { + Unit* mytar = opponent ? opponent : disttarget ? disttarget : nullptr; + if (!mytar) + return; + + StartAttack(mytar, IsMelee()); + + CheckAttackState(); + if (!me->IsAlive() || !mytar->IsAlive()) + return; + + MoveBehind(mytar); + + if (!HasRole(BOT_ROLE_DPS)) + return; + + Unit::AttackerSet const& b_attackers = me->getAttackers(); + + float dist = me->GetDistance(mytar); + + //COMBUSTION (no GCD) + if (IsSpellReady(COMBUSTION_1, diff, false) && GetManaPCT(me) > 20 && + (mytar->GetMaxHealth() > master->GetMaxHealth() * 4 || + master->getAttackers().size() > 1 || b_attackers.size() > 1) && + Rand() < 45 && + !me->GetAuraEffect(SPELL_AURA_ADD_PCT_MODIFIER, SPELLFAMILY_MAGE, 0x0, 0x04000000, 0x0) + /*!HasAuraName(me, COMBUSTION_1)*/) + { + if (doCast(me, GetSpell(COMBUSTION_1))) + return; + } + //ICY VEINS (no GCD) + if (IsSpellReady(ICY_VEINS_1, diff, false) && me->IsInCombat() && GetManaPCT(me) > 20 && + (mytar->GetMaxHealth() > master->GetMaxHealth() * 2 || + (mytar->GetTypeId() == TYPEID_UNIT && mytar->ToCreature()->GetCreatureTemplate()->rank != CREATURE_ELITE_NORMAL)) && + Rand() < 45) + { + if (doCast(me, GetSpell(ICY_VEINS_1))) + return; + } + //ARCANE POWER (no GCD, not with PoM) + if (IsSpellReady(ARCANE_POWER_1, diff, false) && me->IsInCombat() && GetManaPCT(me) > 50 && + (mytar->GetMaxHealth() > master->GetMaxHealth() * 2 || + (mytar->GetTypeId() == TYPEID_UNIT && mytar->ToCreature()->GetCreatureTemplate()->rank != CREATURE_ELITE_NORMAL)) && + Rand() < 75 && !me->GetAuraEffect(SPELL_AURA_ADD_PCT_MODIFIER, SPELLFAMILY_MAGE, 0x0, 0x20, 0x0)) + { + if (doCast(me, GetSpell(ARCANE_POWER_1))) + return; + } + //PRESENCE OF MIND (no GCD, not with AP) + if (IsSpellReady(PRESENCE_OF_MIND_1, diff, false) && me->IsInCombat() && GetManaPCT(me) > 10 && Rand() < 35 && + !me->GetAuraEffect(SPELL_AURA_ADD_PCT_MODIFIER, SPELLFAMILY_MAGE, 0x0, 0x80000, 0x0)) + { + if (doCast(me, GetSpell(PRESENCE_OF_MIND_1))) + return; + } + //DAMAGE + //Cheap check + if (GC_Timer > diff) //!ensure none spells below ignore GCD! + return; + //NOVAS + if ((IsSpellReady(FROST_NOVA_1, diff) || IsSpellReady(BLAST_WAVE_1, diff)) && Rand() < 85) + { + std::list targets; + GetNearbyTargetsList(targets, 8.5f, 1); //both are radius 10 yd + if (!targets.empty()) + { + bool oneOnOne = (*targets.begin()) == mytar; + //Frost Nova + if (IsSpellReady(FROST_NOVA_1, diff) && (targets.size() > 1 || oneOnOne)) + { + if (doCast(me, GetSpell(FROST_NOVA_1))) + { + GetInPosition(true, mytar); + return; + } + } + //Blast Wave + else if (IsSpellReady(BLAST_WAVE_1, diff) && (targets.size() > 1 || oneOnOne)) + { + if (doCast(me, GetSpell(BLAST_WAVE_1))) + return; + } + } + } + //MIRROR IMAGE + if (IsSpellReady(MIRROR_IMAGE_1, diff) && + (mytar->GetTypeId() == TYPEID_PLAYER || + (mytar->GetTypeId() == TYPEID_UNIT && mytar->ToCreature()->GetCreatureTemplate()->rank != CREATURE_ELITE_NORMAL)) && + Rand() < 25) + { + if (doCast(me, GetSpell(MIRROR_IMAGE_1))) + return; + } + //CONES + if (/*fbCasted && */(IsSpellReady(CONE_OF_COLD_1, diff) || IsSpellReady(DRAGON_BREATH_1, diff)) && Rand() < 65) + { + std::list targets; + GetNearbyTargetsInConeList(targets, 8); //both are radius 10 yd + if (!targets.empty()) + { + //Cone of Cold + if (IsSpellReady(CONE_OF_COLD_1, diff)) + { + if (doCast(me, GetSpell(CONE_OF_COLD_1))) + return; + } + //Dragon's Breath + else if (IsSpellReady(DRAGON_BREATH_1, diff)) + { + if (doCast(me, GetSpell(DRAGON_BREATH_1))) + return; + } + } + } + + auto [can_do_frost, can_do_fire, can_do_arcane] = CanAffectVictimBools(mytar, SPELL_SCHOOL_FROST, SPELL_SCHOOL_FIRE, SPELL_SCHOOL_ARCANE); + + //spell reflections: Ice Lance instant / Frostbolt Rank 1 + if (IsSpellReady(ICE_LANCE_1, diff) && can_do_frost && dist < CalcSpellMaxRange(ICE_LANCE_1) && CanRemoveReflectSpells(mytar, ICE_LANCE_1) && + doCast(mytar, ICE_LANCE_1)) + return; + else if (IsSpellReady(FROSTBOLT_1, diff) && can_do_frost && dist < CalcSpellMaxRange(FROSTBOLT_1) && CanRemoveReflectSpells(mytar, FROSTBOLT_1) && + doCast(mytar, FROSTBOLT_1)) + return; + + //Pyroblast TODO: PoM + if (IsSpellReady(PYROBLAST_1, diff) && can_do_fire && dist < CalcSpellMaxRange(PYROBLAST_1) && + ((mytar->IsPolymorphed() && (b_attackers.size() < 2 || (*b_attackers.begin()) == mytar)) || + me->HasAura(HOT_STREAK_BUFF) || (me->HasAura(PRESENCE_OF_MIND_1) && (GetSpec() != BOT_SPEC_MAGE_ARCANE || !GetSpell(ARCANE_BLAST_1))))) + { + if (doCast(mytar, GetSpell(PYROBLAST_1))) + return; + } + //Scorch + if (IsSpellReady(SCORCH_1, diff) && can_do_fire && GetSpec() == BOT_SPEC_MAGE_FIRE && dist < CalcSpellMaxRange(SCORCH_1) && me->GetLevel() >= 25 && + !mytar->GetAuraEffect(SPELL_AURA_MOD_ATTACKER_SPELL_CRIT_CHANCE, SPELLFAMILY_MAGE, 0x0, 0x2000, 0x0)) + { + if (doCast(mytar, GetSpell(SCORCH_1))) + return; + } + //Living Bomb + if ((!mytar->IsControlledByPlayer() || fbCasted) && IsSpellReady(LIVING_BOMB_1, diff) && can_do_fire && dist < CalcSpellMaxRange(LIVING_BOMB_1) && + mytar->GetHealth() > me->GetHealth() / 2 * mytar->getAttackers().size() && + Rand() < 115 && !mytar->GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_MAGE, 0x0, 0x20000, 0x0, me->GetGUID())) + { + if (doCast(mytar, GetSpell(LIVING_BOMB_1))) + return; + } + //Fire Blast (do not waste mana in raids) + if (IsSpellReady(FIRE_BLAST_1, diff) && can_do_fire && dist < CalcSpellMaxRange(FIRE_BLAST_1) && + mytar->GetHealth() < me->GetMaxHealth()*4 && (fbCasted || mytar->GetHealth() < me->GetMaxHealth() / 4) && + Rand() < (30 + 40*fbCasted + 80*(!mytar->IsFrozen() && !mytar->HasUnitState(UNIT_STATE_STUNNED) && me->HasAura(IMPACT_BUFF)))) + { + if (doCast(mytar, GetSpell(FIRE_BLAST_1))) + return; + } + //Deep Freeze (damage only) + if (fbCasted && IsSpellReady(DEEP_FREEZE_1, diff) && can_do_frost && dist < CalcSpellMaxRange(DEEP_FREEZE_1) && Rand() < 30 && + IsImmunedToMySpellEffect(mytar, sSpellMgr->GetSpellInfo(DEEP_FREEZE_1), EFFECT_0) && (mytar->IsFrozen() || me->HasAuraType(SPELL_AURA_ABILITY_IGNORE_AURASTATE))) + { + if (doCast(mytar, GetSpell(DEEP_FREEZE_1))) + return; + } + //Flamestrike (instant cast only) + if (/*fbCasted && */IsSpellReady(FLAMESTRIKE_1, diff) && can_do_fire && dist < CalcSpellMaxRange(FLAMESTRIKE_1) && Rand() < 80 && + me->HasAura(FIRESTARTER_BUFF)) + { + if (doCast(mytar, GetSpell(FLAMESTRIKE_1))) + return; + } + //Blizzard + if (IsSpellReady(BLIZZARD_1, diff) && !JumpingOrFalling() && Rand() < 50) + { + if (Unit* blizztarget = FindAOETarget(CalcSpellMaxRange(BLIZZARD_1))) + { + if (doCast(blizztarget, GetSpell(BLIZZARD_1))) + return; + } + + SetSpellCooldown(BLIZZARD_1, 1500); //fail + } + //Ice Lance (no cd, only GCD) + if (fbCasted && (!me->GetMap()->IsDungeon() || mytar->IsControlledByPlayer()) && + IsSpellReady(ICE_LANCE_1, diff) && can_do_frost && dist < CalcSpellMaxRange(ICE_LANCE_1) && + (mytar->IsFrozen() || me->HasAuraType(SPELL_AURA_ABILITY_IGNORE_AURASTATE))) + { + if (doCast(mytar, GetSpell(ICE_LANCE_1))) + return; + } + //Fireball or Frostfire Bolt (instant cast or combustion use up) + if (/*fbCasted && */IsSpellReady(FROSTFIREBOLT, diff) && (can_do_frost | can_do_fire) && dist < CalcSpellMaxRange(FROSTFIREBOLT) && Rand() < 150 && + ((((CCed(mytar, true) || b_attackers.empty()) && me->HasAura(COMBUSTION_BUFF)) || me->HasAura(BRAIN_FREEZE_BUFF)) || + !GetSpell(FROSTBOLT_1))) //level 1-3 + { + if (doCast(mytar, GetSpell(FROSTFIREBOLT))) + return; + } + //Main rotation + //Arcane Missiles (arcane spec only) + if (IsSpellReady(ARCANEMISSILES_1, diff) && can_do_arcane && GetSpec() == BOT_SPEC_MAGE_ARCANE && dist < CalcSpellMaxRange(ARCANEMISSILES_1) && + (me->GetLevel() < 45 || + ((!GetSpell(ARCANE_BLAST_1) || arcaneBlastStack >= 3 || sSpellMgr->GetSpellInfo(ARCANE_BLAST_1)->CalcPowerCost(me, SPELL_SCHOOL_MASK_ARCANE) > int(me->GetPower(POWER_MANA))) && + me->GetAuraEffect(SPELL_AURA_ADD_FLAT_MODIFIER, SPELLFAMILY_MAGE, 0x0, 0x2, 0x0)))) + { + if (doCast(mytar, GetSpell(ARCANEMISSILES_1))) + return; + } + if (IsSpellReady(ARCANE_BLAST_1, diff) && can_do_arcane && GetSpec() == BOT_SPEC_MAGE_ARCANE && dist < CalcSpellMaxRange(ARCANE_BLAST_1) && + (arcaneBlastStack < 4 || !me->GetAuraEffect(SPELL_AURA_ADD_FLAT_MODIFIER, SPELLFAMILY_MAGE, 0x0, 0x2, 0x0))) + { + if (doCast(mytar, GetSpell(ARCANE_BLAST_1))) + return; + } + if (GetSpec() != BOT_SPEC_MAGE_ARCANE || !GetSpell(ARCANE_BLAST_1)) + { + if (IsSpellReady(FROSTFIREBOLT, diff) && (can_do_frost | can_do_fire) && (GetSpec() == BOT_SPEC_MAGE_FIRE || + (GetSpec() == BOT_SPEC_MAGE_FROST && (FROSTFIREBOLT == FROSTFIRE_BOLT_1 || !GetSpell(FROSTBOLT_1)))) && + dist < CalcSpellMaxRange(FROSTFIREBOLT)) + { + if (doCast(mytar, GetSpell(FROSTFIREBOLT))) + return; + } + + if (IsSpellReady(FROSTBOLT_1, diff) && can_do_frost && GetSpec() != BOT_SPEC_MAGE_FIRE && dist < CalcSpellMaxRange(FROSTBOLT_1)) + { + if (doCast(mytar, GetSpell(FROSTBOLT_1))) + return; + } + if (IsSpellReady(FIREBALL_1, diff) && can_do_fire && GetSpec() == BOT_SPEC_DEFAULT && dist < CalcSpellMaxRange(FIREBALL_1)) + { + if (doCast(mytar, GetSpell(FIREBALL_1))) + return; + } + } + + if (Spell const* shot = me->GetCurrentSpell(CURRENT_AUTOREPEAT_SPELL)) + { + if (shot->GetSpellInfo()->Id == SHOOT_WAND && shot->m_targets.GetUnitTarget() != mytar) + me->InterruptSpell(CURRENT_AUTOREPEAT_SPELL); + } + else if (IsSpellReady(SHOOT_WAND, diff) && !me->isMoving() && me->GetDistance(mytar) < 30 && GetEquips(BOT_SLOT_RANGED) && + doCast(mytar, SHOOT_WAND)) + return; + } + + void CheckPoly(uint32 diff) + { + if (polyCheckTimer <= diff) + { + poly = FindAffectedTarget(GetSpell(POLYMORPH_1), me->GetGUID()); + polyCheckTimer = 2000; + } + } + + void CheckPolymorph(uint32 diff) + { + if (poly == false && IsSpellReady(POLYMORPH_1, diff) && !IsCasting()) + { + if (Unit* target = FindPolyTarget(CalcSpellMaxRange(POLYMORPH_1))) + { + if (doCast(target, GetSpell(POLYMORPH_1))) + return; + } + } + } + + void CheckPots(uint32 diff) + { + if (me->IsMounted() || IsCasting()) + return; + + if (IsPotionReady()) + { + if (GetHealthPCT(me) < 50) + DrinkPotion(false); + } + if (Rand() < 35) + { + if (IsSpellReady(EVOCATION_1, diff) && GetManaPCT(me) < 15 && uint8(me->getAttackers().size()) < (shielded ? 3 : 1)) + { + if (doCast(me, GetSpell(EVOCATION_1))) + return; + } + if (manaGemCharges > 0 && GetManaPCT(me) < 50 && IsSpellReady(MANA_GEM_1, diff, false)) + { + if (doCast(me, GetSpell(MANA_GEM_1))) + return; + } + if (IsPotionReady() && GetManaPCT(me) < 40) + DrinkPotion(true); + } + } + + void CheckBlink(uint32 diff) + { + if (!me->IsAlive()) + return; + if (me->GetVehicle()) + return; + if (HasBotCommandState(BOT_COMMAND_STAY) || me->IsMounted()) + return; + if (!IsSpellReady(BLINK_1, diff) || IsCasting() || Rand() > 70) + return; + + bool cast = false; + Unit* u = nullptr; + if (!IAmFree()) + { + if (!me->IsInCombat() && me->GetExactDist2d(master) > std::max(master->GetBotMgr()->GetBotFollowDist(), 35) && + me->HasInArc(float(M_PI)*0.67f, master)) + { + cast = true; + } + } + if (!cast && me->IsInCombat() && !me->getAttackers().empty() && HasRole(BOT_ROLE_RANGED)) + { + cast = me->HasAuraWithMechanic((1<SelectNearestTarget(7); + cast = (u && u->GetVictim() == me && u->IsWithinLOSInMap(me, LINEOFSIGHT_ALL_CHECKS, VMAP::ModelIgnoreFlags::M2)); + } + if (!cast) + { + u = (*me->getAttackers().begin()); + cast = (u && (!CCed(u, true) || me->getAttackers().size() > 1) && u->GetDistance(me) < 5.f && + u->IsWithinLOSInMap(me, LINEOFSIGHT_ALL_CHECKS, VMAP::ModelIgnoreFlags::M2)); + } + } + if (!cast && IsWanderer() && (me->HasUnitMovementFlag(MOVEMENTFLAG_FORWARD) || me->HasUnitState(UNIT_STATE_ROOT))) + { + u = nullptr; + InstanceTemplate const* instt = sObjectMgr->GetInstanceTemplate(me->GetMap()->GetId()); + bool map_allows_mount = (!me->GetMap()->IsDungeon() || me->GetMap()->IsBattlegroundOrArena()) && (!instt || instt->AllowMount); + if (!me->GetVictim() ? + (me->IsInCombat() || !map_allows_mount || !IsOutdoors() || IsFlagCarrier(me)) : + !me->IsWithinDist(me->GetVictim(), 15.0f + GetSpellAttackRange(true))) + { + Position forwardPos = me->GetFirstCollisionPosition(30.0f, 0.0f); + cast = me->GetExactDist2d(forwardPos) > 15.0f; + } + } + if (cast) + { + if (u) + { + //turn away from target + me->AttackStop(); + me->SetOrientation(me->GetAbsoluteAngle(u) + float(M_PI) * frand(0.85f, 1.15f)); + } + if (doCast(me, GetSpell(BLINK_1))) + return; + } + } + + void CheckFocusMagic(uint32 diff) + { + if (fmCheckTimer > diff || GC_Timer > diff || IAmFree() || me->GetLevel() < 20 || IsCasting() || Rand() > 50) + return; + + uint32 FOCUSMAGIC = GetSpell(FOCUS_MAGIC_1); + if (!FOCUSMAGIC) + return; + + if (FindAffectedTarget(FOCUSMAGIC, me->GetGUID(), 70, 3)) + { + fmCheckTimer = 15000; + return; + } + + std::set targets; + if (Group const* gr = master->GetGroup()) + { + std::vector members = BotMgr::GetAllGroupMembers(gr); + for (uint8 i = 0; i < 3 && !targets.empty(); ++i) + { + for (Unit* member : members) + { + if (!(i == 0 ? member->IsPlayer() : member->IsNPCBot()) || me->GetMap() != member->FindMap() || + !member->IsAlive() || member->GetPowerType() != POWER_MANA || me->GetExactDist(member) > 30 || + member->HasAura(FOCUSMAGIC)) + continue; + if (i > 0) + { + Creature const* bot = member->ToCreature(); + if (bot->GetBotAI()->HasRole(BOT_ROLE_TANK) || + bot->GetBotClass() == BOT_CLASS_BM || bot->GetBotClass() == BOT_CLASS_HUNTER || + bot->GetBotClass() == BOT_CLASS_SPELLBREAKER || bot->GetBotClass() == BOT_CLASS_DARK_RANGER || + bot->GetBotClass() == BOT_CLASS_SEA_WITCH) + continue; + if (i < 2 && bot->GetBotAI()->HasRole(BOT_ROLE_DPS)) + continue; + } + targets.insert(member); + } + } + } + else + { + if (master->GetPowerType() == POWER_MANA && me->GetExactDist(master) < 30 && !master->HasAura(FOCUSMAGIC)) + targets.insert(master); + } + + if (!targets.empty()) + { + Unit* target = targets.size() == 1u ? *targets.begin() : Trinity::Containers::SelectRandomContainerElement(targets); + if (doCast(target, FOCUSMAGIC)) + { + fmCheckTimer = 30000; + return; + } + } + + fmCheckTimer = 5000; //fail + } + + void CheckIceBlock(uint32 diff) + { + if (!me->IsAlive() || GC_Timer > diff || me->GetVehicle() || !GetSpell(ICE_BLOCK_1) || Rand() > 60 || IsTank() || IsFlagCarrier(me)) + return; + + if (iceblockCheckTimer <= diff) + { + if (me->getAttackers().empty() && (!me->IsInCombat() || (GetManaPCT(me) > 45 && GetHealthPCT(me) > 80))) + { + me->RemoveAurasDueToSpell(GetSpell(ICE_BLOCK_1)); + return; + } + iceblockCheckTimer = std::numeric_limits::max(); + } + + if (!IsSpellReady(ICE_BLOCK_1, diff)) + return; + + if (me->IsInCombat() && !me->getAttackers().empty() && + (CCed(me, true) || me->getAttackers().size() > 2 || GetHealthPCT(me) < 40)) + { + if (doCast(me, GetSpell(ICE_BLOCK_1))) + return; + } + } + + void CheckColdSnap(uint32 diff) + { + if (!IsSpellReady(COLD_SNAP_1, diff) || !me->IsInCombat() || me->IsMounted() || Rand() > 50) + return; + + //TODO: recheck priorities + uint32 needFactor = 0; + uint32 cooldown; + cooldown = GetSpellCooldown(FROST_NOVA_1); + needFactor += !cooldown ? 0 : 3 * cooldown / 220; //0-100 x3 + cooldown = GetSpellCooldown(ICE_BLOCK_1); + needFactor += !cooldown ? 0 : 3 * cooldown / 2400; //0-100 x3 + cooldown = shielded ? 0 : GetSpellCooldown(ICE_BARRIER_1); + needFactor += !cooldown ? 0 : 3 * cooldown / 240; //0-100 x3 + cooldown = GetSpellCooldown(FROST_WARD_1); + needFactor += !cooldown ? 0 : 2 * cooldown / 300; //0-100 x2 + cooldown = GetSpellCooldown(ICY_VEINS_1); + needFactor += !cooldown ? 0 : 2 * cooldown / 1500; //0-100 x2 + cooldown = (botPet && botPet->IsAlive()) ? 0 : GetSpellCooldown(SUMMON_WATER_ELEMENTAL_1); + needFactor += !cooldown ? 0 : 1 * cooldown / 1500; //0-100 + cooldown = GetSpellCooldown(DEEP_FREEZE_1); + needFactor += !cooldown ? 0 : 1 * cooldown / 240; //0-100 + cooldown = GetSpellCooldown(CONE_OF_COLD_1); + needFactor += !cooldown ? 0 : 1 * cooldown / 80; //0-100 + //0-1600 + + if (needFactor >= 700 && doCast(me, GetSpell(COLD_SNAP_1))) + return; + } + + void CheckShield(uint32 diff) + { + //TODO: Mana Shield + if (!GetSpell(ICE_BARRIER_1)) + return; + + if (shieldCheckTimer <= diff) + { + shieldCheckTimer = 1500; + shielded = me->GetTotalAuraModifierByMiscValue(SPELL_AURA_SCHOOL_ABSORB, 127) > 0; + shielded = shielded ? shielded : me->HasAura(GetSpell(ICE_BARRIER_1)); + } + + if (shielded || !IsSpellReady(ICE_BARRIER_1, diff) || IsCasting()) + return; + + if ((me->IsInCombat() && me->GetMap()->Instanceable()) || + !me->getAttackers().empty() || GetHealthPCT(me) < 90) + { + if (doCast(me, GetSpell(ICE_BARRIER_1))) + return; + } + } + + void CheckWard(uint32 diff) + { + if ((!me->IsInCombat() && !me->HasAuraType(SPELL_AURA_PERIODIC_DAMAGE)) || + !IsSpellReady(FROST_WARD_1, diff) || IsCasting()) + return; + + uint32 FROSTWARD = canFrostWard ? GetSpell(FROST_WARD_1) : 0; + uint32 FIREWARD = canFireWard ? GetSpell(FIRE_WARD_1) : 0; + + if (FIREWARD && doCast(me, FIREWARD)) + return; + + if (FROSTWARD && doCast(me, FROSTWARD)) + return; + } + + void ApplyClassSpellCritMultiplierAll(Unit const* /*victim*/, float& crit_chance, SpellInfo const* spellInfo, SpellSchoolMask /*schoolMask*/, WeaponAttackType /*attackType*/) const override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + uint8 lvl = me->GetLevel(); + + //Shatter + //SHATTER IS HANDLED IN Unit::isSpCrit() + + //Arcane Potency: 15%/30% additional crit chance for All spells + if (me->HasAura(ARCANE_POTENCY_BUFF2)) + crit_chance += 30.f; + else if (me->HasAura(ARCANE_POTENCY_BUFF1)) + crit_chance += 15.f; + + //Combustion: 10% additional critical chance for fire spells per stack + if (SPELL_SCHOOL_MASK_FIRE & spellInfo->GetSchoolMask()) + if (Aura* combustion = me->GetAura(COMBUSTION_BUFF)) + crit_chance += float(combustion->GetStackAmount() * 10); + + //Incineration: 6% additional crit chance for Fire Blast, Scorch, Arcane Blast and Cone of Cold + if (lvl >= 10 && + (baseId == FIRE_BLAST_1 || baseId == SCORCH_1 || + baseId == ARCANE_BLAST_1 || baseId == CONE_OF_COLD_1)) + crit_chance += 6.f; + //World In Flames: 6% additional critical chance for Flamestrike, Pyroblast, Blast Wave, Dragon's Breath, Living Bomb, Blizzard and Arcane Explosion + if (lvl >= 15 && + (baseId == FLAMESTRIKE_1 || baseId == PYROBLAST_1 || + baseId == BLAST_WAVE_1 || baseId == DRAGON_BREATH_1 || + baseId == BLIZZARD_DAMAGE_1/* || spellId == ARCANEXPLOSION*/ || + baseId == LIVING_BOMB_1 || baseId == LIVING_BOMB_DAMAGE_1)) + crit_chance += 6.f; + //Improved Scorch part 1: 3% additional critical chance for Scorch, Fireball and Frostfire Bolt + if (lvl >= 20 && (baseId == SCORCH_1 || baseId == FIREBALL_1 || baseId == FROSTFIRE_BOLT_1)) + crit_chance += 3.f; + //Critical Mass: 6% additional critical chance for Fire spells + if ((GetSpec() == BOT_SPEC_MAGE_FIRE) && lvl >= 30 && (SPELL_SCHOOL_MASK_FIRE & spellInfo->GetSchoolMask())) + crit_chance += 6.f; + //Winter's chill part 1: 3% additional crit chance for Frostbolt + if ((GetSpec() == BOT_SPEC_MAGE_FROST) && lvl >= 35 && baseId == FROSTBOLT_1) + crit_chance += 3.f; + + //Glyph of Frostfire Bolt part 2: 2% additional critical chance for Frostfire Bolt + if (/*lvl >= 75 && */baseId == FROSTFIRE_BOLT_1) + crit_chance += 2.f; + } + + void ApplyClassDamageMultiplierSpell(int32& damage, SpellNonMeleeDamage& damageinfo, SpellInfo const* spellInfo, WeaponAttackType /*attackType*/, bool iscrit) const override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + uint8 lvl = me->GetLevel(); + float fdamage = float(damage); + + //apply bonus damage mods + float pctbonus = 0.0f; + if (iscrit) + { + //!!!spell damage is not yet critical and will be multiplied by 1.5 + //so we should put here bonus damage mult /1.5 + //Burnout: 50% additional crit damage bonus for All spells + //well it's gonna be a little too much eh? skipped + //Ice Shards: 50% additional crit damage bonus for Frost spells + if (lvl >= 15 && (SPELL_SCHOOL_MASK_FROST & spellInfo->GetSchoolMask())) + pctbonus += 0.334f; + //Spell Power: 50% additional crit damage bonus for All spells + if ((GetSpec() == BOT_SPEC_MAGE_ARCANE) && lvl >= 55) + pctbonus += 0.334f; + //Combustion: 50% additional crit damage bonus for Fire spells + if ((SPELL_SCHOOL_MASK_FIRE & spellInfo->GetSchoolMask()) && me->HasAura(COMBUSTION_1)) + pctbonus += 0.334f; + } + + //Spell Impact: 6% bonus damage for Arcne Explosion, Arcane Blast, Scorch, Fireball, Ice Lance and Cone of Cold + if (lvl >= 20 && + (/*baseId == ARCANE_EXPLOSION_1 || */baseId == SCORCH_1 || + baseId == ARCANE_BLAST_1 || baseId == FIREBALL_1 || + baseId == ICE_LANCE_1 || baseId == CONE_OF_COLD_1)) + pctbonus += 0.06f; + //Piercing Ice: 6% bonus damage for Frost spells + if (lvl >= 20 && (SPELL_SCHOOL_MASK_FROST & spellInfo->GetSchoolMask())) + pctbonus += 0.06f; + //Playing with Fire part 1: 3% bonus damage for all spells + if ((GetSpec() == BOT_SPEC_MAGE_FIRE) && lvl >= 30) + pctbonus += 0.03f; + //Improved Cone of Cold: 35% bonus damage for Cone of Cold + if ((GetSpec() == BOT_SPEC_MAGE_FROST) && lvl >= 30 && baseId == CONE_OF_COLD_1) + pctbonus += 0.35f; + //Arcane Instability part 1: 3% bonus damage for all spells + if ((GetSpec() == BOT_SPEC_MAGE_ARCANE) && lvl >= 35) + pctbonus += 0.03f; + //Fire Power: 10% bonus damage for Fire spells + if ((GetSpec() == BOT_SPEC_MAGE_FIRE) && lvl >= 35 && (SPELL_SCHOOL_MASK_FIRE & spellInfo->GetSchoolMask())) + pctbonus += 0.1f; + //Arcane Empowerment part 1,2: 45% / 9% bonus damage (from spellpower) for Arcane Missiles / Arcane Blast + if (GetSpec() == BOT_SPEC_MAGE_ARCANE && lvl >= 40) + { + if (baseId == ARCANE_MISSILES_DAMAGE_1) + fdamage += me->SpellBaseDamageBonusDone(SPELL_SCHOOL_MASK_MAGIC) * 0.45f * me->CalculateDefaultCoefficient(spellInfo, SPELL_DIRECT_DAMAGE) * me->CalculateSpellpowerCoefficientLevelPenalty(spellInfo); + else if (baseId == ARCANE_BLAST_1) + fdamage += me->SpellBaseDamageBonusDone(SPELL_SCHOOL_MASK_MAGIC) * 0.09f * me->CalculateDefaultCoefficient(spellInfo, SPELL_DIRECT_DAMAGE) * me->CalculateSpellpowerCoefficientLevelPenalty(spellInfo); + } + //Arcane Power: +20% bonus damage + if (AuraEffect const* pow = me->GetAuraEffect(SPELL_AURA_ADD_PCT_MODIFIER, SPELLFAMILY_MAGE, 0x0, 0x80000, 0x0)) + if (pow->IsAffectingSpell(spellInfo)) + pctbonus += 0.2f; + //Molten Fury: 12% bonus damage for All spells against target with less than 35% hp + if ((GetSpec() == BOT_SPEC_MAGE_FIRE) && + lvl >= 40 && damageinfo.target->HasAuraState(AURA_STATE_HEALTHLESS_35_PERCENT)) + pctbonus += 0.12f; + //Arctic Winds part 1: 5% bonus damage for Frost spells + if ((GetSpec() == BOT_SPEC_MAGE_FROST) && lvl >= 40 && (SPELL_SCHOOL_MASK_FROST & spellInfo->GetSchoolMask())) + pctbonus += 0.05f; + //Empowered Fire part 1: 15% bonus damage (from spellpower) for Fireball, Frostfire Bolt and Pyroblast + if ((GetSpec() == BOT_SPEC_MAGE_FIRE) && + lvl >= 45 && (baseId == FIREBALL_1 || baseId == FROSTFIRE_BOLT_1 || baseId == PYROBLAST_1)) + fdamage += me->SpellBaseDamageBonusDone(SPELL_SCHOOL_MASK_MAGIC) * 0.15f * me->CalculateDefaultCoefficient(spellInfo, SPELL_DIRECT_DAMAGE) * me->CalculateSpellpowerCoefficientLevelPenalty(spellInfo); + //Empowered Frostbolt part 1: 10% of spellpower to Frostbolt damage + if ((GetSpec() == BOT_SPEC_MAGE_FROST) && lvl >= 45 && baseId == FROSTBOLT_1) + fdamage += me->SpellBaseDamageBonusDone(SPELL_SCHOOL_MASK_MAGIC) * 0.1f * me->CalculateDefaultCoefficient(spellInfo, SPELL_DIRECT_DAMAGE) * me->CalculateSpellpowerCoefficientLevelPenalty(spellInfo); + //Chilled to the Bone part 1: 5% bonus damage for Frostbolt, Frostfire Bolt and Ice Lance + if ((GetSpec() == BOT_SPEC_MAGE_FROST) && + lvl >= 55 && (baseId == FROSTBOLT_1 || baseId == FROSTFIRE_BOLT_1 || baseId == ICE_LANCE_1)) + pctbonus += 0.05f; + + //Glyph of Frostfire Bolt part 1: 2% bonus damage for Frostfire Bolt + if (/*lvl >= 75 && */baseId == FROSTFIRE_BOLT_1) + pctbonus += 0.02f; + + damage = int32(fdamage * (1.0f + pctbonus)); + } + + void ApplyClassSpellCostMods(SpellInfo const* spellInfo, int32& cost) const override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + uint8 lvl = me->GetLevel(); + float fcost = float(cost); + int32 flatbonus = 0; + float pctbonus = 0.0f; + + //100% mods + //Firestarter part 2: -100% mana cost for Flamestrike + if (baseId == FLAMESTRIKE_1) + if (me->HasAura(FIRESTARTER_BUFF)) + pctbonus += 1.0f; + //Brain Freeze buff: -100% mana cost for Fireball and Frostfire Bolt while active + //we can check spellFamilyFlags or just use ids, going easy way here + if (baseId == FROSTFIRE_BOLT_1 || baseId == FIREBALL_1) + if (me->HasAura(BRAIN_FREEZE_BUFF)) + pctbonus += 1.0f; + //Clearcasting: -100% mana cost for damaging spells + if (AuraEffect const* eff = me->GetAuraEffect(ARCANE_CONCENTRATION_BUFF, 0, me->GetGUID())) + if (eff->IsAffectingSpell(spellInfo)) + pctbonus += 1.0f; + //Missile Barrage: -100% mana cost for Arcane Missiles + if (baseId == ARCANEMISSILES_1) + if (me->GetAuraEffect(SPELL_AURA_ADD_FLAT_MODIFIER, SPELLFAMILY_MAGE, 0x0, 0x2, 0x0)) + pctbonus += 1.0f; + + //pct mods + //Precision part 1: -3% mana cost for All spells + if (lvl >= 15) + pctbonus += 0.03f; + //Frost Channeling: -10% mana cost for all spells + if (lvl >= 25) + pctbonus += 0.1f; + //Improved Blink part 1: -50% mana cost for Blink + if ((GetSpec() == BOT_SPEC_MAGE_ARCANE) && lvl >= 30 && baseId == BLINK_1) + pctbonus += 0.5f; + + //Arcane Blast: +175% mana cost for Arcane Blast (per stack) + if (baseId == ARCANE_BLAST_1) + if (AuraEffect const* bla = me->GetAuraEffect(SPELL_AURA_MOD_DAMAGE_PERCENT_DONE, SPELLFAMILY_MAGE, 0x0, 0x0, 0xC)) + pctbonus += -1.75f * bla->GetBase()->GetStackAmount(); + //Arcane Power: +20% mana cost + if (AuraEffect const* pow = me->GetAuraEffect(SPELL_AURA_ADD_PCT_MODIFIER, SPELLFAMILY_MAGE, 0x0, 0x80000, 0x0)) + if (pow->IsAffectingSpell(spellInfo)) + pctbonus += -0.2f; + + //Glyph of Arcane Intellect: -50% mana cost for Arcane Intellect/Brilliance + if (lvl >= 15 && baseId == ARCANEINTELLECT_1) + pctbonus += 0.5f; + //Glyph of Blast Wave part 1: -15% mana cost for Blast Wave + if (lvl >= 70 && baseId == BLAST_WAVE_1) + pctbonus += 0.15f; + + //cost can be < 0 + cost = int32(fcost * (1.0f - pctbonus)) - flatbonus; + } + + void ApplyClassSpellCastTimeMods(SpellInfo const* spellInfo, int32& casttime) const override + { + //casttime is in milliseconds + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + uint8 lvl = me->GetLevel(); + int32 timebonus = 0; + //float pctbonus = 0.0f; + + //100% mods + //Firestarter part 1: -100% cast time for Flamestrike + if (baseId == FLAMESTRIKE_1) + if (me->HasAura(FIRESTARTER_BUFF)) + timebonus += casttime; + //Brain Freeze: -100% cast time for Fireball and Frostfire Bolt + //we can check spellFamilyFlags or just use ids, going easy way here + if (baseId == FROSTFIRE_BOLT_1 || baseId == FIREBALL_1) + if (me->HasAura(BRAIN_FREEZE_BUFF)) + timebonus += casttime; + //Hot Streak: -100% cast time for Pyroblast + if (baseId == PYROBLAST_1) + if (me->HasAura(HOT_STREAK_BUFF)) + timebonus += casttime; + //Presence of Mind: -100% cast time + if (AuraEffect const* eff = me->GetAuraEffect(SPELL_AURA_ADD_PCT_MODIFIER, SPELLFAMILY_MAGE, 0x0, 0x20, 0x0)) + if (eff->IsAffectingSpell(spellInfo)) + timebonus += casttime; + + //flat mods + //Improved Fireball: -0.5 sec cast time for Fireball (Frostfire too for bot) + if (lvl >= 10 && (baseId == FIREBALL_1 || baseId == FROSTFIRE_BOLT_1)) + timebonus += 500; + //Improved Frostbolt: -0.5 sec cast time for Frostbolt + if (lvl >= 10 && baseId == FROSTBOLT_1) + timebonus += 500; + //Empowered Frostbolt part 2: -0.2 sec cast time for Frostbolt + if (lvl >= 45 && baseId == FROSTBOLT_1) + timebonus += 200; + + //Missile Barrage: -2.5 sec channeling time, -0.5 sec for every tick + if (baseId == ARCANEMISSILES_1 && me->GetAuraEffect(SPELL_AURA_ADD_FLAT_MODIFIER, SPELLFAMILY_MAGE, 0x0, 0x2, 0x0)) + timebonus += casttime / 2; + + casttime = std::max(casttime - timebonus, 0); + } + + void ApplyClassSpellCooldownMods(SpellInfo const* spellInfo, uint32& cooldown) const override + { + //cooldown is in milliseconds + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + uint8 lvl = me->GetLevel(); + int32 timebonus = 0; + float pctbonus = 0.0f; + + //pct mods + //Ice Floes: -20% cooldown for Frost Nova, Cone of Cold, Ice Block and Icy Veins + if (lvl >= 10 && + (baseId == FROST_NOVA_1 || baseId == CONE_OF_COLD_1 || baseId == ICE_BLOCK_1 || baseId == ICY_VEINS_1)) + pctbonus += 0.2f; + //Cold as Ice: -20% cooldown for Ice Barrier, Cold Snap and Summon Water Elemental + if ((GetSpec() == BOT_SPEC_MAGE_FROST) && lvl >= 35 && + (baseId == ICE_BARRIER_1 || baseId == COLD_SNAP_1 || baseId == SUMMON_WATER_ELEMENTAL_1)) + pctbonus += 0.2f; + + //flat mods + //Improved Fire Blast: -2 sec cooldown for Fire Blast + if (lvl >= 10 && baseId == FIRE_BLAST_1) + timebonus += 2000; + //Arcane Flows part 2: -2 min cooldown for Evocation + if ((GetSpec() == BOT_SPEC_MAGE_ARCANE) && lvl >= 45 && baseId == EVOCATION_1) + timebonus += 120000; + //Glyph of Water Elemental: -30 sec cooldown for Summon Water Elemental + if (lvl >= 50 && baseId == SUMMON_WATER_ELEMENTAL_1) + timebonus += 30000; + + ////Pyroblast (special): ensure no double pyroblast casts + //if (baseId == PYROBLAST_1) + // timebonus -= 3000; + + cooldown = std::max((float(cooldown) * (1.0f - pctbonus)) - timebonus, 0); + } + + void ApplyClassSpellRadiusMods(SpellInfo const* spellInfo, float& radius) const override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + uint8 lvl = me->GetLevel(); + float flatbonus = 0.0f; + float pctbonus = 0.0f; + + //pct mods + //Increased Area (AhnQ set bonus?) 23549 + if (lvl >= 60 && (spellInfo->SpellFamilyFlags[0] & 0x1084)) + pctbonus += 0.25f; + //Arctic Reach + if (lvl >= 25 && (spellInfo->SpellFamilyFlags[0] & 0x240)) + pctbonus += 0.2f; + + //flat mods + //Glyph of Blink + if (lvl >= 20 && baseId == BLINK_1) + flatbonus += 5.f; + + radius = radius * (1.0f + pctbonus) + flatbonus; + } + + void ApplyClassSpellRangeMods(SpellInfo const* spellInfo, float& maxrange) const override + { + //uint32 spellId = spellInfo->Id; + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + uint8 lvl = me->GetLevel(); + float flatbonus = 0.0f; + float pctbonus = 0.0f; + + //pct mods + //Arctic Reach: +20% range for Frost Spells + if (lvl >= 25 && ((spellInfo->SpellFamilyFlags[0] & 0x200A0) || (spellInfo->SpellFamilyFlags[1] & 0x100000))) + pctbonus += 0.2f; + + //flat mods + //Magic Attunement: +6 yd range for Arcane Spells + if (lvl >= 20 && ((spellInfo->SpellFamilyFlags[0] & 0xA1006C00) || (spellInfo->SpellFamilyFlags[1] & 0x8010))) + flatbonus += 6.f; + //Flame Throwing: +6 yd range for Fire Spells + if (lvl >= 20 && ((spellInfo->SpellFamilyFlags[0] & 0x400017) || (spellInfo->SpellFamilyFlags[1] & 0x20000))) + flatbonus += 6.f; + + //Glyph of Deep Freeze + if (lvl >= 60 && baseId == DEEP_FREEZE_1) + flatbonus += 10.f; + + //Mage Fire Blast Range Bonus (33066): +6 yd range for Fire Blast + if (lvl >= 60 && baseId == FIRE_BLAST_1) + flatbonus += 6.f; + + maxrange = maxrange * (1.0f + pctbonus) + flatbonus; + } + + void OnClassSpellGo(SpellInfo const* spellInfo) override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + + //DEBUG + //if (!IAmFree()) + //{ + // std::ostringstream msg; + // msg << "OnClassSpellGo: " << spellInfo->SpellName[0] << " (" << spellId << ")!"; + // BotWhisper(msg.str().c_str()); + //} + + if (baseId == SUMMON_WATER_ELEMENTAL_1) + SummonBotPet(); + + //Mana gem conjure and use + if (baseId == CONJURE_MANA_GEM_1) + { + //ItemTemplate const* gem = sObjectMgr->GetItemTemplate(spellInfo->_effects[0].ItemType); + //ASSERT(gem); + //manaGemCharges = uint8(abs(gem->Spells[1].SpellCharges)); //at index 1 + + //Do not bother with this crap + manaGemCharges = 3; + } + if (baseId == MANA_GEM_1) + { + //spell cd is 1 min, item cd is 2 min, correct here + SetSpellCooldown(MANA_GEM_1, 120000); + manaGemCharges -= 1; + } + + //special cases + //Pyroblast (special): ensure no double pyroblast casts + if (baseId == PYROBLAST_1) + SetSpellCooldown(PYROBLAST_1, 3000); + + if (baseId == ICE_BLOCK_1) + { + //Glyph of Ice Block: reset Frost Nova cd + ResetSpellCooldown(FROST_NOVA_1); + iceblockCheckTimer = 4000; + } + + //check for minor rotation thingy (skip common triggered on-hit spells + /*if (spellId != FROSTBITE_TRIGGERED && spellId != WINTERS_CHILL_TRIGGERED && spellId != IGNITE_TRIGGERED && + spellId != ARCANE_CONCENTRATION_BUFF && spellId != ARCANE_POTENCY_BUFF1 && spellId != ARCANE_POTENCY_BUFF2 && + spellId != FIRESTARTER_BUFF && spellId != BRAIN_FREEZE_BUFF && spellId != HOT_STREAK_BUFF)*/ + fbCasted = (baseId == SCORCH_1 || baseId == FROSTBOLT_1 || baseId == FIREBALL_1 || baseId == FROSTFIRE_BOLT_1); + + //Handle clearcasting + if (AuraEffect const* eff = me->GetAuraEffect(ARCANE_CONCENTRATION_BUFF, 0, me->GetGUID())) + { + if (eff->IsAffectingSpell(spellInfo)) + { + //if (int32 cost = spellInfo->CalcPowerCost(me, spellInfo->GetSchoolMask())) + { + //me->ModifyPower(POWER_MANA, cost > 0 ? cost : 0); + me->RemoveAurasDueToSpell(ARCANE_CONCENTRATION_BUFF); + } + //arcane potency has the same affection (damaging spells only) + me->RemoveAurasDueToSpell(ARCANE_POTENCY_BUFF1); + me->RemoveAurasDueToSpell(ARCANE_POTENCY_BUFF2); + } + } + //Handle Presence of Mind + bool consumed_Pom = false; + if (AuraEffect const* eff = me->GetAuraEffect(SPELL_AURA_ADD_PCT_MODIFIER, SPELLFAMILY_MAGE, 0x0, 0x20, 0x0)) + { + if (eff->IsAffectingSpell(spellInfo)) + { + me->RemoveAurasDueToSpell(PRESENCE_OF_MIND_1); + consumed_Pom = true; + } + } + if (!consumed_Pom) + { + //Firestarter + if (baseId == FLAMESTRIKE_1) + me->RemoveAurasDueToSpell(FIRESTARTER_BUFF); + //Brain Freeze (Fireball!) + if (baseId == FROSTFIRE_BOLT_1 || baseId == FIREBALL_1) + me->RemoveAurasDueToSpell(BRAIN_FREEZE_BUFF); + //Hot Streak + if (baseId == PYROBLAST_1) + me->RemoveAurasDueToSpell(HOT_STREAK_BUFF); + } + //Handle Cold Snap + if (baseId == COLD_SNAP_1) + { + SpellInfo const* cdInfo; + BotSpellMap const& myspells = GetSpellMap(); + for (BotSpellMap::const_iterator itr = myspells.begin(); itr != myspells.end(); ++itr) + { + if (itr->first == baseId) + continue; + if (itr->second->spellId != 0 && itr->second->cooldown > 0) + { + cdInfo = sSpellMgr->GetSpellInfo(itr->first); + if (cdInfo && cdInfo->SpellFamilyName == SPELLFAMILY_MAGE && cdInfo->GetRecoveryTime() > 0 && + (cdInfo->GetSchoolMask() & SPELL_SCHOOL_MASK_FROST)) + ResetSpellCooldown(itr->first); + } + } + } + + //Missile Barrage + if (baseId == ARCANEMISSILES_1) + me->RemoveAurasDueToSpell(MISSILE_BARRAGE_BUFF); + } + + void SpellHitTarget(WorldObject* wtarget, SpellInfo const* spell) override + { + Unit* target = wtarget->ToUnit(); + if (!target) + return; + + if (aftercastTargetGuid != ObjectGuid::Empty) + { + //only players for now + if (!aftercastTargetGuid.IsPlayer()) + { + aftercastTargetGuid = ObjectGuid::Empty; + return; + } + + Player* pTarget = ObjectAccessor::GetPlayer(*me, aftercastTargetGuid); + aftercastTargetGuid = ObjectGuid::Empty; + + if (!pTarget/* || me->GetDistance(pTarget) > 15*/) + return; + + //handle effects + for (uint8 i = 0; i != MAX_SPELL_EFFECTS; ++i) + { + switch (spell->_effects[i].Effect) + { + case SPELL_EFFECT_CREATE_ITEM: + case SPELL_EFFECT_CREATE_ITEM_2: + { + uint32 newitemid = spell->_effects[i].ItemType; + if (newitemid) + { + ItemPosCountVec dest; + ItemTemplate const* pProto = sObjectMgr->GetItemTemplate(newitemid); + if (!pProto) + return; + uint32 count = pProto->GetMaxStackSize(); + uint32 no_space = 0; + InventoryResult msg = pTarget->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, newitemid, count, &no_space); + if (msg != EQUIP_ERR_OK) + { + if (msg == EQUIP_ERR_INVENTORY_FULL || msg == EQUIP_ERR_CANT_CARRY_MORE_OF_THIS) + count -= no_space; + else + { + // if not created by another reason from full inventory or unique items amount limitation + pTarget->SendEquipError(msg, nullptr, nullptr, newitemid); + continue; + } + } + if (count) + { + Item* pItem = pTarget->StoreNewItem(dest, newitemid, true, 0); + if (!pItem) + { + pTarget->SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, nullptr, nullptr); + continue; + } + + pTarget->SendNewItem(pItem, count, true, false, true); + } + } + break; + } + default: + break; + } + } + + return; + } + + uint32 spellId = spell->Id; + uint32 baseId = spell->GetFirstRankSpell()->Id; + uint8 lvl = me->GetLevel(); + + if (baseId == ARCANEINTELLECT_1) + { + if (Aura* arc = target->GetAura(spellId, me->GetGUID())) + { + uint32 dur = HOUR * IN_MILLISECONDS; + arc->SetDuration(dur); + arc->SetMaxDuration(dur); + } + } + + //Spells with chill effect + //chill amount by spell family mask + //00100000 00000000 00000000 chilled (blizzard) + //00000200 00000000 00000000 conecold + //00000020 00000000 00000000 frbolt + //00000000 00001000 00000008 ffbolt + //00100220 00001000 00000000 permafrst + //00100220 00001000 00000000 cttbn + if (spell->SpellFamilyName == SPELLFAMILY_MAGE && + ((spell->SpellFamilyFlags[0] & 0x100220) || (spell->SpellFamilyFlags[1] & 0x1000))) + { + //frostbolt, cone of cold, blizzard chill, frostfire bolt + Aura* chill = target->GetAura(spellId, me->GetGUID()); + if (chill) + { + //Permafrost: chill effects duration + 3 sec + if (lvl >= 15) + { + uint32 dur = chill->GetDuration() + 3000; + chill->SetDuration(dur); + chill->SetMaxDuration(dur); + } + //chill effect is at index 0 + AuraEffect* chillEff = chill->GetEffect(0); + if (chillEff) + { + int32 amount = chillEff->GetAmount(); + if (lvl >= 15) + amount -= 10; //permafrost + if ((GetSpec() == BOT_SPEC_MAGE_FROST) && lvl >= 55) + amount -= 10; //chilled to the bone + chillEff->ChangeAmount(amount); + } + } + } + + //Glyph of Ice Barrier: 30% increased effect + if (baseId == ICE_BARRIER_1) + { + shielded = true; + if (lvl >= 46) + { + AuraEffect* barr = me->GetAuraEffect(spellId, 0); + if (barr) + barr->ChangeAmount(barr->GetAmount() * 1.3f); + } + } + + //Custom things + if (baseId == POLYMORPH_1) + { + poly = true; + polyCheckTimer = 2000; + } + + //Winter Veil addition + if (sGameEventMgr->IsActiveEvent(GAME_EVENT_WINTER_VEIL)) + { + if (SPELL_SCHOOL_MASK_FROST & spell->GetSchoolMask()) + me->AddAura(44755, target); //snowflakes + + //if (baseId == FROSTBOLT_1 && urand(1,100) <= 10) + // me->CastSpell(target, 25686, true); //10% super snowball + } + + OnSpellHitTarget(target, spell); + } + + void SpellHit(WorldObject* wcaster, SpellInfo const* spell) override + { + Unit* caster = wcaster->ToUnit(); + if (!caster) + return; + + uint32 baseId = spell->GetFirstRankSpell()->Id; + + if (baseId == ARCANE_BLAST_DEBUFF) + if (Aura* blas = me->GetAura(spell->Id)) + arcaneBlastStack = blas->GetStackAmount(); + + //Ward helper + if (spell->HasEffect(SPELL_EFFECT_SCHOOL_DAMAGE) || spell->HasAura(SPELL_AURA_PERIODIC_DAMAGE)) + { + if (!canFrostWard && (spell->GetSchoolMask() & SPELL_SCHOOL_MASK_FROST)) + canFrostWard = true; + if (!canFireWard && (spell->GetSchoolMask() & SPELL_SCHOOL_MASK_FIRE)) + canFireWard = true; + } + + OnSpellHit(caster, spell); + } + + void DamageDealt(Unit* victim, uint32& damage, DamageEffectType damageType) override + { + bot_ai::DamageDealt(victim, damage, damageType); + } + + void DamageTaken(Unit* u, uint32& /*damage*/, DamageEffectType /*damageType*/, SpellInfo const* /*spellInfo*/) override + { + if (!u) + return; + if (!u->IsInCombat() && !me->IsInCombat()) + return; + OnOwnerDamagedBy(u); + } + + void OwnerAttackedBy(Unit* u) override + { + if (!u) + return; + OnOwnerDamagedBy(u); + } + + void SummonBotPet() + { + if (botPet) + UnsummonAll(false); + + uint32 entry = BOT_PET_WATER_ELEMENTAL; + + Position pos; + + //glyphed: permanent + Creature* myPet = me->SummonCreature(entry, *me, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 3s); + me->GetNearPoint(myPet, pos.m_positionX, pos.m_positionY, pos.m_positionZ, 2, me->GetOrientation()); + myPet->GetMotionMaster()->MovePoint(me->GetMapId(), pos); + myPet->SetCreator(master); + myPet->SetOwnerGUID(me->GetGUID()); + myPet->SetFaction(master->GetFaction()); + myPet->SetControlledByPlayer(!IAmFree()); + myPet->SetPvP(me->IsPvP()); + myPet->SetByteValue(UNIT_FIELD_BYTES_2, 1, master->GetByteValue(UNIT_FIELD_BYTES_2, 1)); + myPet->SetUInt32Value(UNIT_CREATED_BY_SPELL, SUMMON_WATER_ELEMENTAL_1); + + botPet = myPet; + } + + void UnsummonAll(bool savePets = true) override + { + UnsummonPet(savePets); + } + + void SummonedCreatureDies(Creature* /*summon*/, Unit* /*killer*/) override + { + } + + void SummonedCreatureDespawn(Creature* summon) override + { + //TC_LOG_ERROR("entities.unit", "SummonedCreatureDespawn: {}'s {}", me->GetName(), summon->GetName()); + if (summon == botPet) + botPet = nullptr; + } + + uint32 GetAIMiscValue(uint32 data) const override + { + switch (data) + { + case BOTAI_MISC_PET_TYPE: + return BOT_PET_WATER_ELEMENTAL; + default: + return 0; + } + } + + float GetSpellAttackRange(bool longRange) const override + { + return longRange ? CalcSpellMaxRange(FROSTBOLT_1) : 20.f; + } + + void Reset() override + { + UnsummonAll(false); + + polyCheckTimer = 0; + fmCheckTimer = 0; + iceblockCheckTimer = 0; + shieldCheckTimer = 0; + arcaneBlastStack = 0; + manaGemCharges = 0; + + poly = false; + shielded = false; + fbCasted = false; + + DefaultInit(); + } + + void ReduceCD(uint32 diff) override + { + if (polyCheckTimer > diff) polyCheckTimer -= diff; + if (fmCheckTimer > diff) fmCheckTimer -= diff; + if (iceblockCheckTimer > diff) iceblockCheckTimer -= diff; + if (shieldCheckTimer > diff) shieldCheckTimer -= diff; + } + + void InitPowers() override + { + me->SetPowerType(POWER_MANA); + } + + void InitSpells() override + { + uint8 lvl = me->GetLevel(); + //bool isArca = GetSpec() == BOT_SPEC_MAGE_ARCANE; + bool isFire = GetSpec() == BOT_SPEC_MAGE_FIRE; + bool isFros = GetSpec() == BOT_SPEC_MAGE_FROST; + + InitSpellMap(DAMPENMAGIC_1); + InitSpellMap(AMPLIFYMAGIC_1); + InitSpellMap(ARCANEINTELLECT_1); + InitSpellMap(ARCANEMISSILES_1); + InitSpellMap(ARCANE_BLAST_1); + InitSpellMap(POLYMORPH_1); + InitSpellMap(COUNTERSPELL_1); + InitSpellMap(SPELLSTEAL_1); + InitSpellMap(EVOCATION_1); + InitSpellMap(BLINK_1); + InitSpellMap(REMOVE_CURSE_1); + InitSpellMap(INVISIBILITY_1); + InitSpellMap(SCORCH_1); + InitSpellMap(FIRE_BLAST_1); + InitSpellMap(FLAMESTRIKE_1); + InitSpellMap(DAMPENMAGIC_1); + InitSpellMap(FROSTBOLT_1); + InitSpellMap(FROST_NOVA_1); + InitSpellMap(CONE_OF_COLD_1); + InitSpellMap(BLIZZARD_1); + InitSpellMap(FROST_ARMOR_1); + InitSpellMap(ICE_ARMOR_1); + InitSpellMap(MOLTEN_ARMOR_1); + InitSpellMap(ICE_BLOCK_1); + /*Special*/InitSpellMap(BLIZZARD_DAMAGE_1); //important + /*Special*/InitSpellMap(LIVING_BOMB_DAMAGE_1); //important + InitSpellMap(SLOW_FALL_1); + InitSpellMap(ICE_LANCE_1); + InitSpellMap(FROST_WARD_1); + InitSpellMap(FIRE_WARD_1); + InitSpellMap(MIRROR_IMAGE_1); + + /*Special*/InitSpellMap(CONJURE_MANA_GEM_1); + /*Special*/InitSpellMap(MANA_GEM_1); + + InitSpellMap(RITUAL_OF_REFRESHMENT_1); //not casted + + /*Talent*/lvl >= 20 ? InitSpellMap(FOCUS_MAGIC_1) : RemoveSpell(FOCUS_MAGIC_1); + /*Talent*/lvl >= 30 ? InitSpellMap(PRESENCE_OF_MIND_1) : RemoveSpell(PRESENCE_OF_MIND_1); + /*Talent*/lvl >= 40 ? InitSpellMap(ARCANE_POWER_1) : RemoveSpell(ARCANE_POWER_1); + + /*Talent*/lvl >= 20 ? InitSpellMap(PYROBLAST_1) : RemoveSpell(PYROBLAST_1); + /*Talent*/lvl >= 30 && isFire ? InitSpellMap(BLAST_WAVE_1) : RemoveSpell(BLAST_WAVE_1); + /*Talent*/lvl >= 40 && isFire ? InitSpellMap(DRAGON_BREATH_1) : RemoveSpell(DRAGON_BREATH_1); + /*Talent*/lvl >= 50 && isFire ? InitSpellMap(COMBUSTION_1) : RemoveSpell(COMBUSTION_1); + /*Talent*/lvl >= 60 && isFire ? InitSpellMap(LIVING_BOMB_1) : RemoveSpell(LIVING_BOMB_1); + + /*Talent*/lvl >= 20 ? InitSpellMap(ICY_VEINS_1) : RemoveSpell(ICY_VEINS_1); + /*Talent*/lvl >= 30 && isFros ? InitSpellMap(COLD_SNAP_1) : RemoveSpell(COLD_SNAP_1); + /*Talent*/lvl >= 40 && isFros ? InitSpellMap(ICE_BARRIER_1) : RemoveSpell(ICE_BARRIER_1); + /*Talent*/lvl >= 50 && isFros ? InitSpellMap(SUMMON_WATER_ELEMENTAL_1) : RemoveSpell(SUMMON_WATER_ELEMENTAL_1); + /*Talent*/lvl >= 60 && isFros ? InitSpellMap(DEEP_FREEZE_1) : RemoveSpell(DEEP_FREEZE_1); + + InitSpellMap(FROSTFIRE_BOLT_1); + InitSpellMap(FIREBALL_1); + FROSTFIREBOLT = GetSpell(FROSTFIRE_BOLT_1) ? FROSTFIRE_BOLT_1 : FIREBALL_1; + } + + void ApplyClassPassives() const override + { + uint8 level = master->GetLevel(); + bool isArca = GetSpec() == BOT_SPEC_MAGE_ARCANE; + bool isFire = GetSpec() == BOT_SPEC_MAGE_FIRE; + bool isFros = GetSpec() == BOT_SPEC_MAGE_FROST; + + RefreshAura(ARCANE_CONCENTRATION, level >= 15 ? 1 : 0); + RefreshAura(ARCANE_MEDITATION, level >= 25 ? 1 : 0); //mana regen 1 + RefreshAura(TORMENT_THE_WEAK, level >= 25 ? 1 : 0); + RefreshAura(IMPROVED_COUNTERSPELL2, isArca && level >= 26 ? 1 : 0); + RefreshAura(IMPROVED_COUNTERSPELL1, isArca && level >= 25 && level < 26 ? 1 : 0); + RefreshAura(ARCANE_POTENCY2, isArca && level >= 36 ? 1 : 0); + RefreshAura(ARCANE_POTENCY1, isArca && level >= 35 && level < 36 ? 1 : 0); + RefreshAura(ARCANE_EMPOWERMENT, isArca && level >= 40 ? 1 : 0); + RefreshAura(INCANTERS_ABSORPTION3, isArca && level >= 42 ? 1 : 0); + RefreshAura(INCANTERS_ABSORPTION2, isArca && level >= 41 && level < 42 ? 1 : 0); + RefreshAura(INCANTERS_ABSORPTION1, isArca && level >= 40 && level < 41 ? 1 : 0); + RefreshAura(MISSILE_BARRAGE, isArca && level >= 45 ? 1 : 0); + + RefreshAura(IGNITE, level >= 15 ? 1 : 0); + RefreshAura(BURNING_DETERMINATION, level >= 15 ? 1 : 0); + RefreshAura(IMPACT, level >= 20 ? 1 : 0); + RefreshAura(IMPROVED_SCORCH, level >= 25 ? 1 : 0); + RefreshAura(MOLTEN_SHIELDS, level >= 25 ? 1 : 0); + RefreshAura(MASTER_OF_ELEMENTS, level >= 25 ? 1 : 0); + RefreshAura(BLAZING_SPEED, isFire && level >= 35 ? 1 : 0); + RefreshAura(PYROMANIAC, isFire && level >= 40 ? 1 : 0); //mana regen 2 + RefreshAura(FIRESTARTER2, isFire && level >= 51 ? 1 : 0); + RefreshAura(FIRESTARTER1, isFire && level >= 50 && level < 51 ? 1 : 0); + RefreshAura(HOT_STREAK, isFire && level >= 50 ? 1 : 0); + + RefreshAura(FROSTBITE3, level >= 12 ? 1 : 0); + RefreshAura(FROSTBITE2, level >= 11 && level < 12 ? 1 : 0); + RefreshAura(FROSTBITE1, level >= 10 && level < 11 ? 1 : 0); + RefreshAura(FROST_WARDING, level >= 15 ? 1 : 0); + RefreshAura(IMPROVED_BLIZZARD, level >= 20 ? 1 : 0); + RefreshAura(SHATTER3, level >= 27 ? 1 : 0); + RefreshAura(SHATTER2, level >= 26 && level < 27 ? 1 : 0); + RefreshAura(SHATTER1, level >= 25 && level < 26 ? 1 : 0); + RefreshAura(WINTERS_CHILL3, isFros && level >= 37 ? 1 : 0); + RefreshAura(WINTERS_CHILL2, isFros && level >= 36 && level < 37 ? 1 : 0); + RefreshAura(WINTERS_CHILL1, isFros && level >= 35 && level < 36 ? 1 : 0); + RefreshAura(SHATTERED_BARRIER, isFros && level >= 45 ? 1 : 0); + //RefreshAura(ARCTIC_WINDS, isFros && level >= 45 ? 1 : 0); //only miss chance + RefreshAura(FINGERS_OF_FROST, isFros && level >= 45 ? 1 : 0); + RefreshAura(BRAIN_FREEZE3, isFros && level >= 53 ? 1 : 0); + RefreshAura(BRAIN_FREEZE2, isFros && level >= 51 && level < 52 ? 1 : 0); + RefreshAura(BRAIN_FREEZE1, isFros && level >= 50 && level < 51 ? 1 : 0); + + RefreshAura(GLYPH_POLYMORPH, level >= 15 ? 1 : 0); + RefreshAura(GLYPG_REMOVE_CURSE, level >= 18 ? 1 : 0); + RefreshAura(GLYPH_ICY_VEINS, level >= 20 ? 1 : 0); + RefreshAura(GLYPH_LIVING_BOMB, level >= 60 ? 1 : 0); + RefreshAura(GLYPH_ICE_LANCE, level >= 66 ? 1 : 0); + } + + bool CanUseManually(uint32 basespell) const override + { + switch (basespell) + { + case DAMPENMAGIC_1: + case AMPLIFYMAGIC_1: + case ARCANEINTELLECT_1: + case EVOCATION_1: + case REMOVE_CURSE_1: + case FOCUS_MAGIC_1: + case PRESENCE_OF_MIND_1: + case ARCANE_POWER_1: + case ICE_ARMOR_1: + case ICE_BARRIER_1: + case COMBUSTION_1: + case ICY_VEINS_1: + case BLAST_WAVE_1: + case FLAMESTRIKE_1: + case FROST_NOVA_1: + case BLIZZARD_1: + case ICE_BLOCK_1: + case COLD_SNAP_1: + case INVISIBILITY_1: + case SLOW_FALL_1: + case CONJURE_MANA_GEM_1: + case SUMMON_WATER_ELEMENTAL_1: + case MIRROR_IMAGE_1: + return true; + case FROST_ARMOR_1: + return !GetSpell(ICE_ARMOR_1); + //case MANA_GEM_1: + // return manaGemCharges > 0; + default: + return false; + } + } + + std::vector const* GetDamagingSpellsList() const override + { + return &Mage_spells_damage; + } + std::vector const* GetCCSpellsList() const override + { + return &Mage_spells_cc; + } + //std::vector const* GetHealingSpellsList() const override + //{ + // return &Mage_spells_heal; + //} + std::vector const* GetSupportSpellsList() const override + { + return &Mage_spells_support; + } + + private: + //Spells +/*frst*/uint32 FROSTFIREBOLT; + //Timers +/*exc.*/uint32 polyCheckTimer, fmCheckTimer, iceblockCheckTimer, shieldCheckTimer; + //Counters +/*exc.*/uint8 arcaneBlastStack; +/*exc.*/uint8 manaGemCharges; + //Check +/*exc.*/bool poly, shielded, fbCasted; +/*exc.*/bool canFrostWard, canFireWard; + }; +}; + +void AddSC_mage_bot() +{ + new mage_bot(); +} diff --git a/src/server/game/AI/NpcBots/bot_necromancer_ai.cpp b/src/server/game/AI/NpcBots/bot_necromancer_ai.cpp new file mode 100644 index 000000000..6c367965c --- /dev/null +++ b/src/server/game/AI/NpcBots/bot_necromancer_ai.cpp @@ -0,0 +1,755 @@ +#include "bot_ai.h" +#include "bot_GridNotifiers.h" +#include "botmgr.h" +#include "botspell.h" +#include "bottext.h" +#include "bottraits.h" +#include "CellImpl.h" +#include "Containers.h" +#include "GridNotifiers.h" +#include "GridNotifiersImpl.h" +#include "Group.h" +//#include "MotionMaster.h" +#include "Player.h" +#include "ScriptMgr.h" +#include "Spell.h" +#include "SpellAuras.h" +#include "SpellMgr.h" +#include "TemporarySummon.h" +//#include "World.h" +/* +Necromancer NpcBot (by Trickerer onlysuffering@gmail.com) +Description: +Necromancer (Warcraft III / Disablo II tribute) +Abilities: +1) Shadow Bolt: main attack, single target, no mana cost +Complete - 90% +TODO: +maximum skeletons for botparty +fine-tune corpse explosion conditions +*/ + +enum NecromancerBaseSpells +{ + MAIN_ATTACK_1 = SPELL_SHADOW_BOLT2, + RAISE_DEAD_1 = SPELL_RAISE_DEAD, + UNHOLY_FRENZY_1 = SPELL_UNHOLY_FRENZY, + CRIPPLE_1 = SPELL_CRIPPLE, + + CORPSE_EXPLOSION_1 = SPELL_CORPSE_EXPLOSION, + //ATTRACT_1 = SPELL_BLOOD_CURSE +}; +enum NecromancerSpecial +{ + MH_ATTACK_ANIM = SPELL_ATTACK_MELEE_1H, + + RAISE_DEAD_COST = 50 * 5, // 75 * 5, Reduced to match playstyle (1-2,3?) necromancers + UNHOLY_FRENZY_COST = 50 * 5, + CRIPPLE_COST = 175 * 5, + CORPSE_EXPLOSION_COST = 100 * 5, + //ATTRACT_COST = 200 * 5, + + //get 80% mana back if casting on a skeleton + UNHOLY_FRENZY_REFUND = UNHOLY_FRENZY_COST / 10 * 8, + + MAX_MINIONS = 12, + + SPELL_SPAWN_ANIM = 25035, + SPELL_BLOODY_EXPLOSION = 36599, + + MODEL_BLOODY_BONES = 25538, + + CORPSE_EXPLOSION_DAMAGE = 50444, //DK spell + + CE_DAMAGE_PCT_BASE = 35, + CE_DAMAGE_PCT_PER_LEVEL = 1, + + CE_MIN_TARGETS = 3 +}; + +static const uint32 Necromancer_spells_damage_arr[] = +{ /*MAIN_ATTACK_1, */CORPSE_EXPLOSION_1/*, ATTRACT_1*/ }; + +static const uint32 Necromancer_spells_support_arr[] = +{ RAISE_DEAD_1, UNHOLY_FRENZY_1, CRIPPLE_1/*, ATTRACT_1*/ }; + +static const std::vector Necromancer_spells_damage(FROM_ARRAY(Necromancer_spells_damage_arr)); +static const std::vector Necromancer_spells_support(FROM_ARRAY(Necromancer_spells_support_arr)); + +class necromancer_bot : public CreatureScript +{ +public: + necromancer_bot() : CreatureScript("necromancer_bot") { } + + CreatureAI* GetAI(Creature* creature) const override + { + return new necromancer_botAI(creature); + } + + struct necromancer_botAI : public bot_ai + { + necromancer_botAI(Creature* creature) : bot_ai(creature) + { + _botclass = BOT_CLASS_NECROMANCER; + + InitUnitFlags(); + + //necromancer immunities + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_POSSESS, true); + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_CHARM, true); + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_SILENCE, true); + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_SHAPESHIFT, true); + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_TRANSFORM, true); + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_BLOCK_SPELL_FAMILY, true); + me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_POLYMORPH, true); + me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_INTERRUPT, true); + me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_SILENCE, true); + } + + bool doCast(Unit* victim, uint32 spellId) + { + if (CheckBotCast(victim, spellId) != SPELL_CAST_OK) + return false; + return bot_ai::doCast(victim, spellId); + } + + void StartAttack(Unit* u, bool force = false) + { + if (!bot_ai::StartAttack(u, force)) + return; + GetInPosition(force, u); + } + + void JustEnteredCombat(Unit* u) override { bot_ai::JustEnteredCombat(u); } + void AttackStart(Unit*) override { } + void KilledUnit(Unit* u) override { bot_ai::KilledUnit(u); } + void EnterEvadeMode(EvadeReason why = EVADE_REASON_OTHER) override { bot_ai::EnterEvadeMode(why); } + void MoveInLineOfSight(Unit* u) override { bot_ai::MoveInLineOfSight(u); } + void JustDied(Unit* u) override { UnsummonAll(false); bot_ai::JustDied(u); } + void DoNonCombatActions(uint32 /*diff*/) { } + + void CheckCorpseExplosion(uint32 diff) + { + if (!IsSpellReady(CORPSE_EXPLOSION_1, diff) || _corpseExplosionCheckTimer > diff || + me->GetLevel() < 40 || me->GetPower(POWER_MANA) < CORPSE_EXPLOSION_COST || Rand() > 80) + return; + + _corpseExplosionCheckTimer = 500; + + SpellInfo const* ceinfo = AssertBotSpellInfoOverride(GetSpell(CORPSE_EXPLOSION_1)); + float ceradius = ceinfo->GetEffect(EFFECT_0).RadiusEntry->Radius; + ApplyBotSpellRadiusMods(ceinfo, ceradius); + + //1. Corpse near current target + if ((IAmFree() || !master->GetGroup() || master->GetGroup()->GetMembersCount() <= 3) && + me->GetVictim() && me->GetVictim()->GetHealth() <= me->GetMaxHealth() * 3) + { + auto corpse_pred = [this, mindist = ceradius](Creature const* c) mutable { + if (_isUsableCorpse(c) && c->GetDistance(me->GetVictim()) < mindist) + { + mindist = c->GetDistance(me->GetVictim()); + return true; + } + return false; + }; + Creature* creature = nullptr; + Trinity::CreatureLastSearcher searcher(me, creature, corpse_pred); + Cell::VisitAllObjects(me, searcher, ceinfo->RangeEntry->RangeMax[0]); + + if (creature) + { + if (doCast(creature, GetSpell(CORPSE_EXPLOSION_1))) + { + me->CastSpell(me, MH_ATTACK_ANIM, true); + return; + } + } + } + + //2. Find a corpse with enough idiots around it (this one in n^2 so open for reviews) + { + auto corpse_pred = [this, ceradius = ceradius, maxmob = std::size_t(CE_MIN_TARGETS-1)](Creature const* c) mutable { + if (_isUsableCorpse(c)) + { + std::list units; + NearbyHostileUnitCheck check(me, ceradius, this, 0, c); + Trinity::UnitListSearcher searcher(c, units, check); + Cell::VisitAllObjects(c, searcher, ceradius); + if (units.size() > maxmob) + { + maxmob = units.size(); + return true; + } + return false; + } + return false; + }; + std::list corpses; + Trinity::CreatureListSearcher searcher(me, corpses, corpse_pred); + Cell::VisitAllObjects(me, searcher, ceinfo->RangeEntry->RangeMax[0]); + + if (Creature* corpse = corpses.empty() ? nullptr : corpses.size() == 1 ? corpses.front() : + Trinity::Containers::SelectRandomContainerElement(corpses)) + { + if (doCast(corpse, GetSpell(CORPSE_EXPLOSION_1))) + { + me->CastSpell(me, MH_ATTACK_ANIM, true); + return; + } + } + } + } + + void CheckRaiseDead(uint32 diff) + { + if (!IsSpellReady(RAISE_DEAD_1, diff) || _raiseDeadCheckTimer > diff || _minions.size() > MAX_MINIONS - 2 || + me->GetPower(POWER_MANA) < RAISE_DEAD_COST || Rand() > 50) + return; + + _raiseDeadCheckTimer = 500; + + auto corpse_pred = [this, mindist = 25.f](Creature const* c) mutable { + if (_isUsableCorpse(c) && c->GetDistance(me) < mindist) + { + mindist = c->GetDistance(me); + return true; + } + return false; + }; + Creature* creature = nullptr; + Trinity::CreatureLastSearcher searcher(me, creature, corpse_pred); + Cell::VisitAllObjects(me, searcher, 25.f); + + if (creature) + { + if (doCast(creature, GetSpell(RAISE_DEAD_1))) + return; + } + } + + void CheckUnholyFrenzy(uint32 diff) + { + if (!IsSpellReady(UNHOLY_FRENZY_1, diff) || me->GetLevel() < 30 || me->GetPower(POWER_MANA) < UNHOLY_FRENZY_COST || Rand() > 35) + return; + + static const auto frenzy_pred_player = [](Player const* pl, Unit const* nec) -> bool { + return (pl->GetVictim() && pl->IsInCombat() && IsMeleeClass(pl->GetClass()) && nec->GetDistance(pl) < 30 && + pl->GetDistance(pl->GetVictim()) < 15 && pl->getAttackers().empty() && !CCed(pl, true) && + !pl->HasAuraType(SPELL_AURA_PERIODIC_DAMAGE) && pl->GetHealth() >= nec->GetMaxHealth()); + }; + + static const auto frenzy_pred_bot = [](Creature const* bot, Unit const* nec) -> bool { + return (IsMeleeClass(bot->GetBotClass()) && bot->GetVictim() && !bot->GetBotAI()->IsTank(bot) && + bot->GetBotAI()->HasRole(BOT_ROLE_DPS) && !bot->GetBotAI()->HasRole(BOT_ROLE_RANGED) && + nec->GetDistance(bot) < 30 && bot->GetDistance(bot->GetVictim()) < 15 && + bot->getAttackers().empty() && !CCed(bot, true) && + !bot->HasAuraType(SPELL_AURA_PERIODIC_DAMAGE) && bot->GetHealth() >= nec->GetMaxHealth()); + }; + + Unit* target = nullptr; + + //master + if (!IsTank(master) && frenzy_pred_player(master, me)) + target = master; + //minions + else if (HasRole(BOT_ROLE_DPS) && !_minions.empty()) + { + for (Unit* minion : _minions) + { + if (minion->GetVictim() && GetHealthPCT(minion) > 80 && me->GetDistance(minion) < 30 && !CCed(minion, true) && + !minion->HasAuraType(SPELL_AURA_PERIODIC_DAMAGE)) + { + target = minion; + break; + } + } + } + + if (!target) + { + std::set targets; + if (Group const* gr = !IAmFree() ? master->GetGroup() : GetGroup()) + { + std::vector members = BotMgr::GetAllGroupMembers(gr); + for (uint8 i = 0; i < 2 && !targets.empty(); ++i) + { + for (Unit* member : members) + { + if (!(i == 0 ? member->IsPlayer() : member->IsNPCBot()) || me->GetMap() != member->FindMap() || + member->GetGUID() == master->GetGUID()) + continue; + if (member->IsPlayer() ? + (!IsTank(member) && frenzy_pred_player(member->ToPlayer(), me)) : + frenzy_pred_bot(member->ToCreature(), me)) + targets.insert(member); + } + } + } + if (!targets.empty()) + target = targets.size() == 1u ? *targets.begin() : Trinity::Containers::SelectRandomContainerElement(targets); + } + + if (target && doCast(target, GetSpell(UNHOLY_FRENZY_1))) + { + if (target->GetTypeId() == TYPEID_PLAYER) + ReportSpellCast(UNHOLY_FRENZY_1, LocalizedNpcText(target->ToPlayer(), BOT_TEXT__ON_YOU), target->ToPlayer()); + return; + } + + SetSpellCooldown(UNHOLY_FRENZY_1, 1000); //fail + } + + void UpdateAI(uint32 diff) override + { + if (!GlobalUpdate(diff)) + return; + + //Interrupt corpse-usage spells if no longer usable + if (Spell const* spell = me->GetCurrentSpell(CURRENT_GENERIC_SPELL)) + { + if ((spell->GetSpellInfo()->GetFirstRankSpell()->Id == RAISE_DEAD_1 || + spell->GetSpellInfo()->GetFirstRankSpell()->Id == CORPSE_EXPLOSION_1)) + { + Unit const* target = ObjectAccessor::GetUnit(*me, spell->m_targets.GetObjectTargetGUID()); + if (target && target->GetDisplayId() != target->GetNativeDisplayId()) + me->InterruptSpell(CURRENT_GENERIC_SPELL); + } + } + + DoVehicleActions(diff); + if (!CanBotAttackOnVehicle()) + return; + + if (IsPotionReady()) + { + if (me->GetPower(POWER_MANA) < CORPSE_EXPLOSION_COST) + DrinkPotion(true); + else if (GetHealthPCT(me) < 50) + DrinkPotion(false); + } + + if (ProcessImmediateNonAttackTarget()) + return; + + if (!CheckAttackTarget()) + return; + + if (IsCasting()) + return; + + CheckCorpseExplosion(diff); + CheckRaiseDead(diff); + CheckUnholyFrenzy(diff); + + CheckUsableItems(diff); + + Attack(diff); + } + + void Attack(uint32 diff) + { + Unit* mytar = opponent ? opponent : disttarget ? disttarget : nullptr; + if (!mytar) + return; + + StartAttack(mytar, IsMelee()); + + CheckAttackState(); + if (!me->IsAlive() || !mytar->IsAlive()) + return; + + MoveBehind(mytar); + + if (!HasRole(BOT_ROLE_DPS)) + return; + + if (GC_Timer > diff) + return; + + if (!CanAffectVictimAny(mytar, SPELL_SCHOOL_SHADOW, SPELL_SCHOOL_ARCANE)) + return; + + //Cripple + if (IsSpellReady(CRIPPLE_1, diff) && me->GetDistance(mytar) < 30 && + me->GetLevel() >= 50 && me->GetPower(POWER_MANA) >= CRIPPLE_COST && + mytar->GetMaxNegativeAuraModifier(SPELL_AURA_MOD_MELEE_HASTE) >= 0 && + (mytar->GetTypeId() == TYPEID_PLAYER || mytar->GetHealth() > me->GetMaxHealth() * 3)) + { + if (doCast(mytar, GetSpell(CRIPPLE_1))) + return; + } + + if (IsSpellReady(MAIN_ATTACK_1, diff) && me->GetDistance(mytar) < 30) + { + if (doCast(mytar, GetSpell(MAIN_ATTACK_1))) + return; + } + } + + //void ApplyClassDamageMultiplierSpell(int32& damage, SpellNonMeleeDamage& /*damageinfo*/, SpellInfo const* spellInfo, WeaponAttackType /*attackType*/, bool iscrit) const override + //{ + // uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + // //uint8 lvl = me->GetLevel(); + // float fdamage = float(damage); + + // //apply bonus damage mods + // float pctbonus = 1.0f; + // if (iscrit) + // pctbonus *= 1.333f; + + // if (baseId == MAIN_ATTACK_1) + // fdamage += me->SpellBaseDamageBonusDone(SPELL_SCHOOL_MASK_MAGIC) * (spellInfo->_effects[0].BonusMultiplier - 1.f) * me->CalculateDefaultCoefficient(spellInfo, SPELL_DIRECT_DAMAGE) * me->CalculateSpellpowerCoefficientLevelPenalty(spellInfo); + + // damage = int32(fdamage * pctbonus); + //} + + void ApplyClassSpellRadiusMods(SpellInfo const* spellInfo, float& radius) const override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + uint8 lvl = me->GetLevel(); + float pctbonus = 1.0f; + + //flat mods + //Corpse Explosion radii per level + if (lvl > 40 && (baseId == CORPSE_EXPLOSION_1 || baseId == CORPSE_EXPLOSION_DAMAGE)) + radius += 0.25f * (lvl - 40); + + radius = radius * pctbonus; + } + + void ApplyClassEffectMods(SpellInfo const* spellInfo, uint8 effIndex, float& value) const override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //uint8 lvl = me->GetLevel(); + float pctbonus = 1.0f; + + //Set damage for Unholy Frenzy: 45 sec, 15 ticks, total damage is 125% if Necromancer's max health + if (baseId == UNHOLY_FRENZY_1 && effIndex == EFFECT_1) + value += (me->GetMaxHealth() * 1.25f) / std::max(1, spellInfo->GetMaxTicks()); + + value = value * pctbonus; + } + + void ApplyClassThreatMods(SpellInfo const* spellInfo, float& threat) const override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + float pctbonus = 1.0f; + + if (baseId == CORPSE_EXPLOSION_DAMAGE) + threat = 0.f; + + threat = threat * pctbonus; + } + + void OnClassSpellGo(SpellInfo const* spellInfo) override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + + if (baseId == MAIN_ATTACK_1 || baseId == RAISE_DEAD_1 || baseId == UNHOLY_FRENZY_1 || + baseId == CRIPPLE_1 || baseId == CORPSE_EXPLOSION_1/* || baseId == ATTRACT_1*/) + GC_Timer = uint32(me->GetAttackTime(BASE_ATTACK) * me->m_modAttackSpeedPct[BASE_ATTACK]); + + if (baseId == MAIN_ATTACK_1 || baseId == RAISE_DEAD_1 || baseId == UNHOLY_FRENZY_1 || + baseId == CRIPPLE_1/* || baseId == CORPSE_EXPLOSION_1*//* || baseId == ATTRACT_1*/) + me->CastSpell(me, MH_ATTACK_ANIM, true); + } + + void SpellHitTarget(WorldObject* wtarget, SpellInfo const* spell) override + { + Unit* target = wtarget->ToUnit(); + if (!target) + return; + + uint32 baseId = spell->GetFirstRankSpell()->Id; + + if (baseId != MAIN_ATTACK_1) + { + if (baseId == CORPSE_EXPLOSION_1) + { + ASSERT(!IsInBotParty(target)); + target->CastSpell(target, CORPSE_EXPLOSION_VISUAL, true); + target->CastSpell(target, SPELL_BLOODY_EXPLOSION, true); + target->SetDisplayId(MODEL_BLOODY_BONES); + + //Corpse Explosion damage: progress with level 35 to 75% (base level 40) + int32 fdamage = CalculatePct(target->GetMaxHealth(), + CE_DAMAGE_PCT_BASE + CE_DAMAGE_PCT_PER_LEVEL * (uint32(me->GetLevel()) - spell->BaseLevel)); + + CastSpellExtraArgs args(TRIGGERED_FULL_MASK); + args.AddSpellBP0(fdamage); + me->CastSpell(target, CORPSE_EXPLOSION_DAMAGE, args); + } + + if (baseId == RAISE_DEAD_1) + { + ASSERT(!IsInBotParty(target)); + //Two skeletons + for (uint8 i = 0; i < 2; ++i) + SummonBotPet(target); + //visuals + if (!target->IsPet() && !target->IsVehicle() && !target->ToCreature()->isWorldBoss() && !target->ToCreature()->IsDungeonBoss()) + { + target->CastSpell(target, SPELL_BLOODY_EXPLOSION, true); + target->SetDisplayId(MODEL_BLOODY_BONES); + } + } + + if (baseId == UNHOLY_FRENZY_1) + { + if (target->GetEntry() == BOT_PET_NECROSKELETON && _minions.find(target->ToCreature()) != _minions.end()) + { + //get 80% mana back if casting on a skeleton + me->EnergizeBySpell(me, UNHOLY_FRENZY_1, UNHOLY_FRENZY_REFUND, POWER_MANA); + } + } + + if (baseId == CRIPPLE_1) + { + if (target->GetTypeId() == TYPEID_PLAYER || target->GetLevel() > 80) + { + if (Aura* crip = target->GetAura(spell->Id, me->GetGUID())) + { + int32 dur = std::min(crip->GetMaxDuration(), 10000); + crip->SetDuration(dur); + crip->SetMaxDuration(dur); + } + } + } + } + + OnSpellHitTarget(target, spell); + } + + void SpellHit(WorldObject* wcaster, SpellInfo const* spell) override + { + Unit* caster = wcaster->ToUnit(); + if (!caster) + return; + + OnSpellHit(caster, spell); + } + + void DamageDealt(Unit* victim, uint32& damage, DamageEffectType damageType) override + { + bot_ai::DamageDealt(victim, damage, damageType); + } + + void DamageTaken(Unit* u, uint32& /*damage*/, DamageEffectType /*damageType*/, SpellInfo const* /*spellInfo*/) override + { + if (!u) + return; + if (!u->IsInCombat() && !me->IsInCombat()) + return; + OnOwnerDamagedBy(u); + } + + void OwnerAttackedBy(Unit* u) override + { + if (!u) + return; + OnOwnerDamagedBy(u); + } + + uint8 GetPetPositionNumber(Creature const* summon) const override + { + uint8 i = 0; + for (Summons::const_iterator citr = _minions.begin(); citr != _minions.end(); ++citr) + { + if ((*citr)->GetGUID() == summon->GetGUID()) + return i; + ++i; + } + return 0; + } + + void SummonBotPet(Unit* from) + { + if (_minions.size() >= MAX_MINIONS) + { + Unit* u = nullptr; + //try 1: by minimal level + uint8 minlevel = me->GetLevel(); + for (Summons::const_iterator itr = _minions.begin(); itr != _minions.end(); ++itr) + { + if ((*itr)->GetLevel() < minlevel) + { + minlevel = (*itr)->GetLevel(); + u = *itr; + } + } + //try 2: by minimal duration + if (!u) + { + uint32 minduration = 0; + for (Summons::const_iterator itr = _minions.begin(); itr != _minions.end(); ++itr) + { + if ((*itr)->GetAI()->GetData(BOTPETAI_MISC_DURATION) > minduration) + { + minduration = (*itr)->GetAI()->GetData(BOTPETAI_MISC_DURATION); + u = *itr; + } + } + } + + if (!u) + return; + } + + Position pos = from->GetPosition(); + + Creature* myPet = me->SummonCreature(BOT_PET_NECROSKELETON, pos, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 1s); + myPet->SetCreator(master); + myPet->SetOwnerGUID(me->GetGUID()); + myPet->SetFaction(master->GetFaction()); + myPet->SetControlledByPlayer(!IAmFree()); + myPet->SetPvP(me->IsPvP()); + myPet->SetByteValue(UNIT_FIELD_BYTES_2, 1, master->GetByteValue(UNIT_FIELD_BYTES_2, 1)); + myPet->SetUInt32Value(UNIT_CREATED_BY_SPELL, RAISE_DEAD_1); + + //dark minion immunities + myPet->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_SHAPESHIFT, true); + myPet->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_TRANSFORM, true); + myPet->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_FEAR, true); + myPet->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_POLYMORPH, true); + myPet->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_FEAR, true); + myPet->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_HORROR, true); + myPet->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_SLEEP, true); + //heal + myPet->ApplySpellImmune(0, IMMUNITY_EFFECT, SPELL_EFFECT_HEAL, true); + myPet->ApplySpellImmune(0, IMMUNITY_EFFECT, SPELL_EFFECT_HEAL_PCT, true); + myPet->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_PERIODIC_HEAL, true); + + myPet->CastSpell(myPet, SPELL_SPAWN_ANIM, true); + if (Aura* stun = myPet->AddAura(SUMMONING_DISORIENTATION, myPet)) + { + stun->SetDuration(1500); + stun->SetMaxDuration(1500); + } + + myPet->GetAI()->SetData(BOTPETAI_MISC_MAXLEVEL, me->GetLevel()); + _minions.insert(myPet); + } + + void UnsummonAll(bool savePets = true) override + { + UnsummonCreatures(_minions, savePets); + } + + void SummonedCreatureDies(Creature* /*summon*/, Unit* /*killer*/) override + { + } + + void SummonedCreatureDespawn(Creature* summon) override + { + if (_minions.find(summon) != _minions.end()) + _minions.erase(summon); + } + + uint32 GetAIMiscValue(uint32 data) const override + { + switch (data) + { + case BOTAI_MISC_PET_TYPE: + return BOT_PET_NECROSKELETON; + default: + return 0; + } + } + + void CheckAttackState() override + { + } + + void OnBotEnterVehicle(Vehicle const* vehicle) override + { + me->Dismount(); + bot_ai::OnBotEnterVehicle(vehicle); + } + + void Reset() override + { + _corpseExplosionCheckTimer = 0; + _raiseDeadCheckTimer = 0; + + UnsummonAll(false); + + DefaultInit(); + } + + void ReduceCD(uint32 diff) override + { + if (_corpseExplosionCheckTimer > diff) _corpseExplosionCheckTimer -= diff; + if (_raiseDeadCheckTimer > diff) _raiseDeadCheckTimer -= diff; + } + + void InitPowers() override + { + me->SetPowerType(POWER_MANA); + } + + void InitSpells() override + { + InitSpellMap(MAIN_ATTACK_1, true, false); + InitSpellMap(RAISE_DEAD_1, true, false); + InitSpellMap(UNHOLY_FRENZY_1, true, false); + InitSpellMap(CRIPPLE_1, true, false); + InitSpellMap(CORPSE_EXPLOSION_1, true, false); + //InitSpellMap(ATTRACT_1, true, false); + } + + void ApplyClassPassives() const override + { + } + + bool CanUseManually(uint32 basespell) const override + { + switch (basespell) + { + case UNHOLY_FRENZY_1: + return me->GetLevel() >= 30; + default: + return false; + } + } + + std::vector const* GetDamagingSpellsList() const override + { + return &Necromancer_spells_damage; + } + //std::vector const* GetCCSpellsList() const override + //{ + // return &Necromancer_spells_cc; + //} + //std::vector const* GetHealingSpellsList() const override + //{ + // return &Necromancer_spells_heal; + //} + std::vector const* GetSupportSpellsList() const override + { + return &Necromancer_spells_support; + } + + private: + bool _isUsableCorpse(Creature const* c) const + { + static const uint32 ViableCreatureTypesMask = + (1 << (CREATURE_TYPE_BEAST-1)) | (1 << (CREATURE_TYPE_DRAGONKIN-1)) | (1 << (CREATURE_TYPE_HUMANOID-1)); + + return c->getDeathState() == DeathState::CORPSE && c->GetDisplayId() == c->GetNativeDisplayId() && + !c->IsVehicle() && !c->isWorldBoss() && !c->IsDungeonBoss() && + ((1 << (c->GetCreatureType()-1)) & ViableCreatureTypesMask) && + !c->IsControlledByPlayer() && !c->IsNPCBot() && c->GetMaxHealth() >= me->GetMaxHealth() / 4; + } + + uint32 _corpseExplosionCheckTimer; + uint32 _raiseDeadCheckTimer; + + typedef std::set Summons; + Summons _minions; + }; +}; + +void AddSC_necromancer_bot() +{ + new necromancer_bot(); +} diff --git a/src/server/game/AI/NpcBots/bot_paladin_ai.cpp b/src/server/game/AI/NpcBots/bot_paladin_ai.cpp new file mode 100644 index 000000000..1852850b8 --- /dev/null +++ b/src/server/game/AI/NpcBots/bot_paladin_ai.cpp @@ -0,0 +1,2550 @@ +#include "bot_ai.h" +#include "botmgr.h" +#include "bottext.h" +#include "bottraits.h" +#include "Containers.h" +#include "Creature.h" +#include "Group.h" +#include "Item.h" +#include "Map.h" +#include "Player.h" +#include "ScriptMgr.h" +#include "SpellAuraEffects.h" +#include "SpellMgr.h" +//#include "WorldSession.h" +/* +Paladin NpcBot (reworked by Trickerer onlysuffering@gmail.com) +Complete - Around 95% +TODO: +*/ + +enum PaladinBaseSpells// all orignals +{ + FLASH_OF_LIGHT_1 = 19750, + HOLY_LIGHT_1 = 635, + LAY_ON_HANDS_1 = 633, + REDEMPTION_1 = 7328, + HAND_OF_FREEDOM_1 = 1044, + SACRED_SHIELD_1 = 53601, + HOLY_SHOCK_1 = 20473, + CLEANSE_1 = 4987, + HAND_OF_PROTECTION_1 = 1022, + HAND_OF_SALVATION_1 = 1038, + HAND_OF_SACRIFICE_1 = 6940, + SEAL_OF_VENGEANCE_1 = 31801, + SEAL_OF_CORRUPTION_1 = 53736, + SEAL_OF_COMMAND_1 = 20375, + SEAL_OF_LIGHT_1 = 20165, + SEAL_OF_RIGHTEOUSNESS_1 = 21084, + SEAL_OF_WISDOM_1 = 20166, + SEAL_OF_JUSTICE_1 = 20164, + DIVINE_SACRIFICE_1 = 64205, + HAND_OF_RECKONING_1 = 62124, + RIGHTEOUS_DEFENSE_1 = 31789, + DIVINE_PLEA_1 = 54428, + REPENTANCE_1 = 20066, + TURN_EVIL_1 = 10326, + CRUSADER_STRIKE_1 = 35395, + JUDGEMENT_OF_LIGHT_1 = 20271, + JUDGEMENT_OF_WISDOM_1 = 53408, + JUDGEMENT_OF_JUSTICE_1 = 53407, + CONSECRATION_1 = 26573, + HAMMER_OF_JUSTICE_1 = 853, + DIVINE_STORM_1 = 53385, + HAMMER_OF_WRATH_1 = 24275, + EXORCISM_1 = 879, + HOLY_WRATH_1 = 2812, + AVENGING_WRATH_1 = 31884, + RIGHTEOUS_FURY_1 = 25780, + HOLY_SHIELD_1 = 20925, + AVENGERS_SHIELD_1 = 31935, + HAMMER_OF_THE_RIGHTEOUS_1 = 53595, + SHIELD_OF_RIGHTEOUSNESS_1 = 53600, + BLESSING_OF_MIGHT_1 = 19740, + BLESSING_OF_WISDOM_1 = 19742, + BLESSING_OF_KINGS_1 = 20217, + BLESSING_OF_SANCTUARY_1 = 20911, + DEVOTION_AURA_1 = 465, + CONCENTRATION_AURA_1 = 19746, + FIRE_RESISTANCE_AURA_1 = 19891, + FROST_RESISTANCE_AURA_1 = 19888, + SHADOW_RESISTANCE_AURA_1 = 19876, + RETRIBUTION_AURA_1 = 7294, + CRUSADER_AURA_1 = 32223, + + DIVINE_INTERVENTION_1 = 19752, + AURA_MASTERY_1 = 31821, + DIVINE_FAVOR_1 = 20216, + DIVINE_ILLUMINATION_1 = 31842, + BEACON_OF_LIGHT_1 = 53563, + + DIVINE_PROTECTION_1 = 498, + DIVINE_SHIELD_1 = 642, + + PURIFY_1 = 1152 +}; +enum PaladinPassives +{ +//Talents + DIVINE_PURPOSE = 31872, + JUDGEMENTS_OF_THE_PURE = 54155, + JUDGEMENTS_OF_THE_WISE = 31878, + SACRED_CLEANSING = 53553,//rank 3 + RECKONING1 = 20177, + RECKONING2 = 20179, + RECKONING3 = 20181, + RECKONING4 = 20180, + RECKONING5 = 20182, + VINDICATION1 = 9452, + VINDICATION2 = 26016, + PURSUIT_OF_JUSTICE = 26023,//rank 2 + ART_OF_WAR = 53488,//rank 2 + IMPROVED_LAY_ON_HANDS = 20235,//rank 2 + FANATICISM = 31881,//rank 3 + RIGHTEOUS_VENGEANCE1 = 53380,//rank 1 + RIGHTEOUS_VENGEANCE2 = 53381,//rank 2 + RIGHTEOUS_VENGEANCE3 = 53382,//rank 3 + VENGEANCE1 = 20049,//rank 1 + VENGEANCE2 = 20056,//rank 2 + VENGEANCE3 = 20057,//rank 3 + SHEATH_OF_LIGHT1 = 53501,//rank 1 + SHEATH_OF_LIGHT2 = 53502,//rank 2 + SHEATH_OF_LIGHT3 = 53503,//rank 3 + ARDENT_DEFENDER = 31852,//rank 3 + ILLUMINATION = 20215,//rank 5 + INFUSION_OF_LIGHT = 53576,//rank 2 + REDOUBT1 = 20127,//rank 3 + REDOUBT2 = 20130,//rank 3 + REDOUBT3 = 20135,//rank 3 + IMPROVED_RIGHTEOUS_FURY = 20470,//rank 3 + SHIELD_OF_THE_TEMPLAR = 53711,//rank 3 + IMPROVED_DEVOTION_AURA = 20140,//rank 3 + IMPROVED_CONCENTRATION_AURA = 20256,//rank 3 + SANCTIFIED_RETRIBUTION = 31869, + SWIFT_RETRIBUTION = 53648,//rank 3 + LIGHTS_GRACE = 31836,//rank 3 + DIVINE_GUARDIAN = 53530,//rank 3 + //COMBAT_EXPERTISE = 31860,//rank 3 + CRUSADE = 31868,//rank 3 + ONE_HANDED_WEAPON_SPECIALIZATION = 20198,//rank 3 + TWO_HANDED_WEAPON_SPECIALIZATION = 20113,//rank 3 + //JUDGEMENTS_OF_THE_JUST = 53696,//rank 2 + GUARDED_BY_THE_LIGHT = 53585,//rank 2 + TOUCHED_BY_THE_LIGHT = 53592,//rank 3 + HEART_OF_THE_CRUSADER = 20337,//rank 3 +//Glyphs + GLYPH_HOLY_LIGHT = 54937, + GLYPH_SALVATION = 63225, +//Innate + JUDGEMENT_ANTI_PARRY_DODGE_PASSIVE = 60091, +//other + RECUCED_HOLY_LIGHT_CAST_TIME = 37189,//not a typo + //CLEANSE_HEAL_PASSIVE = 28787 +}; + +enum PaladinSpecial +{ + NOAURA = 0, + DEVOTIONAURA = 1, + CONCENTRATIONAURA = 2, + FIRERESAURA = 3, + FROSTRESAURA = 4, + SHADOWRESAURA = 5, + RETRIBUTIONAURA = 6, + CRUSADERAURA = 7, + + SPECIFIC_BLESSING_WISDOM = 0x01, + SPECIFIC_BLESSING_KINGS = 0x02, + SPECIFIC_BLESSING_SANCTUARY = 0x04, + SPECIFIC_BLESSING_MIGHT = 0x08, + SPECIFIC_BLESSING_MY_BLESSING = 0x10, + + SPECIFIC_AURA_DEVOTION = 0x01, + SPECIFIC_AURA_CONCENTRATION = 0x02, + SPECIFIC_AURA_FIRE_RES = 0x04, + SPECIFIC_AURA_FROST_RES = 0x08, + SPECIFIC_AURA_SHADOW_RES = 0x10, + SPECIFIC_AURA_RETRIBUTION = 0x20, + SPECIFIC_AURA_CRUSADER = 0x40, + SPECIFIC_AURA_MY_AURA = 0x80, + SPECIFIC_AURA_ALL_AUTOUSE = (SPECIFIC_AURA_DEVOTION | SPECIFIC_AURA_CONCENTRATION | SPECIFIC_AURA_RETRIBUTION | \ + SPECIFIC_AURA_FIRE_RES | SPECIFIC_AURA_FROST_RES | SPECIFIC_AURA_SHADOW_RES), + + FLASH_OF_LIGHT_HEAL_PERIODIC = 66922, + + ENLIGHTENMENT_BUFF = 43837, + INFUSION_OF_LIGHT_BUFF = 54149,//rank 2 + THE_ART_OF_WAR_BUFF = 59578,//rank 2 + //FORBEARANCE_AURA = 25771, + + LIGHTS_GRACE_BUFF = 31834, + + SEAL_OF_JUSTICE_STUN_AURA = 20170, + JUDGEMENTS_OF_THE_JUST_AURA = 68055, //melee attack speed reduce + + //JUDGEMENT_OF_LIGHT_AURA = 20185, + JUDGEMENT_OF_WISDOM_AURA = 20186, + //JUDGEMENT_OF_JUSTICE_AURA = 20184, + + GREATER_BLESSING_OF_MIGHT_1 = 25782, + GREATER_BLESSING_OF_WISDOM_1 = 25894, + GREATER_BLESSING_OF_KINGS_1 = 25898, + GREATER_BLESSING_OF_SANCTUARY_1 = 25899, + BATTLESHOUT_1 = 6673, + + HOLY_SHOCK_HEAL_1 = 25914, + ARDENT_DEFENDER_HEAL = 66235, + JUDGEMENT_OF_COMMAND_DAMAGE = 20467, + SPIRITUAL_ATTUNEMENT_ENERGIZE = 31786, + SACRED_SHIELD_AURA_TRIGGERED = 58597, + + AVENGING_WRATH_MARKER_SPELL = 61987, + IMMUNITY_SHIELD_MARKER_SPELL = 61988, + + IMPROVED_DEVOTION_AURA_SPELL = 63514 +}; + +static const uint32 Paladin_spells_damage_arr[] = +{ AVENGERS_SHIELD_1, CONSECRATION_1, CRUSADER_STRIKE_1, DIVINE_STORM_1, EXORCISM_1, JUDGEMENT_OF_LIGHT_1, +JUDGEMENT_OF_WISDOM_1, JUDGEMENT_OF_JUSTICE_1, HAMMER_OF_THE_RIGHTEOUS_1, HAMMER_OF_WRATH_1, HOLY_SHIELD_1, +HOLY_SHOCK_1, HOLY_WRATH_1, SHIELD_OF_RIGHTEOUSNESS_1, HAND_OF_RECKONING_1 }; + +static const uint32 Paladin_spells_cc_arr[] = +{ HAMMER_OF_JUSTICE_1, HOLY_WRATH_1, REPENTANCE_1, TURN_EVIL_1 }; + +static const uint32 Paladin_spells_heal_arr[] = +{ BEACON_OF_LIGHT_1, FLASH_OF_LIGHT_1, HOLY_LIGHT_1, HOLY_SHOCK_1, LAY_ON_HANDS_1 }; + +static const uint32 Paladin_spells_support_arr[] = +{ /*DEVOTION_AURA_1, CONCENTRATION_AURA_1, FIRE_RESISTANCE_AURA_1, FROST_RESISTANCE_AURA_1, SHADOW_RESISTANCE_AURA_1, +RETRIBUTION_AURA_1, CRUSADER_AURA_1, */AURA_MASTERY_1, AVENGING_WRATH_1, BLESSING_OF_MIGHT_1, BLESSING_OF_WISDOM_1, +BLESSING_OF_KINGS_1, BLESSING_OF_SANCTUARY_1, CLEANSE_1, DIVINE_FAVOR_1, DIVINE_ILLUMINATION_1, DIVINE_INTERVENTION_1, +DIVINE_PLEA_1, DIVINE_PROTECTION_1, DIVINE_SACRIFICE_1, DIVINE_SHIELD_1, HAND_OF_FREEDOM_1, HAND_OF_PROTECTION_1, +HAND_OF_RECKONING_1, HAND_OF_SACRIFICE_1, HAND_OF_SALVATION_1, HOLY_SHIELD_1, PURIFY_1, REDEMPTION_1, +RIGHTEOUS_DEFENSE_1, RIGHTEOUS_FURY_1, SACRED_SHIELD_1, SEAL_OF_RIGHTEOUSNESS_1, SEAL_OF_JUSTICE_1, SEAL_OF_LIGHT_1, +SEAL_OF_WISDOM_1, SEAL_OF_COMMAND_1, SEAL_OF_VENGEANCE_1, SEAL_OF_CORRUPTION_1 }; + +static const std::vector Paladin_spells_damage(FROM_ARRAY(Paladin_spells_damage_arr)); +static const std::vector Paladin_spells_cc(FROM_ARRAY(Paladin_spells_cc_arr)); +static const std::vector Paladin_spells_heal(FROM_ARRAY(Paladin_spells_heal_arr)); +static const std::vector Paladin_spells_support(FROM_ARRAY(Paladin_spells_support_arr)); + +class paladin_bot : public CreatureScript +{ +public: + paladin_bot() : CreatureScript("paladin_bot") { } + + CreatureAI* GetAI(Creature* creature) const override + { + return new paladin_botAI(creature); + } +/* + bool OnGossipHello(Player* player, Creature* creature) + { + return creature->GetBotAI()->OnGossipHello(player, 0); + } + + bool OnGossipSelect(Player* player, Creature* creature, uint32 sender, uint32 action) + { + if (bot_ai* ai = creature->GetBotAI()) + return ai->OnGossipSelect(player, creature, sender, action); + return true; + } + + bool OnGossipSelectCode(Player* player, Creature* creature, uint32 sender, uint32 action, char const* code) + { + if (bot_ai* ai = creature->GetBotAI()) + return ai->OnGossipSelectCode(player, creature, sender, action, code); + return true; + } +*/ + struct paladin_botAI : public bot_ai + { + paladin_botAI(Creature* creature) : bot_ai(creature) + { + _botclass = BOT_CLASS_PALADIN; + + InitUnitFlags(); + } + + bool doCast(Unit* victim, uint32 spellId) + { + if (CheckBotCast(victim, spellId) != SPELL_CAST_OK) + return false; + return bot_ai::doCast(victim, spellId); + } + + void CheckBeacon(uint32 diff) + { + if (checkBeaconTimer > diff || !IsSpellReady(BEACON_OF_LIGHT_1, diff) || + !HasRole(BOT_ROLE_HEAL|BOT_ROLE_RANGED) || IsCasting() || Rand() > 15) + return; + + checkBeaconTimer = urand(2000, 5000); + + if (FindAffectedTarget(GetSpell(BEACON_OF_LIGHT_1), me->GetGUID(), 60, 3)) + return; + + //find tank + //stacks + if (Group const* gr = !IAmFree() ? master->GetGroup() : GetGroup()) + { + std::set tanks; + for (Unit* member : BotMgr::GetAllGroupMembers(gr)) + { + if (me->GetMap() == member->FindMap() && member->IsAlive() && member->IsInCombat() && IsTank(member) && + (!member->getAttackers().empty() || GetHealthPCT(member) < 90) && + !member->GetAuraEffect(SPELL_AURA_PERIODIC_TRIGGER_SPELL, SPELLFAMILY_PALADIN, 0x0, 0x1000000, 0x0, me->GetGUID())) + tanks.insert(member); + } + + if (tanks.empty()) + return; + + Unit* target = tanks.size() == 1 ? *tanks.begin() : Trinity::Containers::SelectRandomContainerElement(tanks); + if (doCast(target, GetSpell(BEACON_OF_LIGHT_1))) + return; + } + } + + void CheckSacrifice(uint32 diff) + { + if (!IsSpellReady(DIVINE_SACRIFICE_1, diff) || IAmFree() || me->IsMounted() || + IsTank() || Feasting() || !CanBlock() || IsCasting() || Rand() > 25 || GetHealthPCT(me) < 60) + return; + + Group const* gr = master->GetGroup(); + if (!gr) + { + if (master->IsAlive() && GetHealthPCT(master) < 75 && me->GetDistance(master) < 30 && !master->getAttackers().empty() && + !master->GetAuraEffect(SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN, SPELLFAMILY_PALADIN, 3837, EFFECT_0)) + { + if (doCast(me, GetSpell(DIVINE_SACRIFICE_1))) + return; + } + } + else + { + uint8 attacked = 0; + for (Unit const* member : BotMgr::GetAllGroupMembers(gr)) + { + if (me->GetMap() == member->FindMap() && member->IsAlive() && + !(member->IsNPCBot() && member->ToCreature()->IsTempBot()) && + me->GetDistance(member) < 30 && !member->getAttackers().empty() && + !member->GetAuraEffect(SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN, SPELLFAMILY_PALADIN, 3837, EFFECT_0)) + { + if (++attacked > 3) + break; + } + } + if (attacked > 3 && doCast(me, GetSpell(DIVINE_SACRIFICE_1))) + return; + } + + SetSpellCooldown(DIVINE_SACRIFICE_1, 1000); //fail + } + + void CheckHandOfSacrifice(uint32 diff) + { + if (!IsSpellReady(HAND_OF_SACRIFICE_1, diff) || IAmFree() || me->IsMounted() || + IsTank() || Feasting() || !CanBlock() || IsCasting() || Rand() > 25 || GetHealthPCT(me) < 50) + return; + + Group const* gr = master->GetGroup(); + if (!gr) + { + if (master->IsAlive() && me->GetDistance(master) < 30 && !master->getAttackers().empty() && + (master->getAttackers().size() > 2 || GetHealthPCT(master) < 50) && + !master->GetAuraEffect(SPELL_AURA_SPLIT_DAMAGE_PCT, SPELLFAMILY_PALADIN, 0x2000, 0x0, 0x0)) + { + if (doCast(master, GetSpell(HAND_OF_SACRIFICE_1))) + return; + } + } + else + { + Unit* u = nullptr; + for (Unit* member : BotMgr::GetAllGroupMembers(gr)) + { + if (me->GetMap() == member->FindMap() && member->IsAlive() && me->GetDistance(member) < 30 && + !(member->IsNPCBot() && member->ToCreature()->IsTempBot()) && + (member->getAttackers().size() > 2 || GetHealthPCT(member) < 50) && + !member->GetAuraEffect(SPELL_AURA_SPLIT_DAMAGE_PCT, SPELLFAMILY_PALADIN, 0x2000, 0x0, 0x0)) + { + u = member; + break; + } + } + + if (u && doCast(u, GetSpell(HAND_OF_SACRIFICE_1))) + return; + } + + SetSpellCooldown(HAND_OF_SACRIFICE_1, 2000); //fail + } + + void ShieldGroup(uint32 diff) + { + if (checkShieldTimer > diff || !IsSpellReady(SACRED_SHIELD_1, diff) || + me->IsMounted() || Feasting() || IsCasting() || Rand() > 50) + return; + + checkShieldTimer = 1500; + + if (IsTank()) + { + if (Rand() > 15) + return; + } + else if (!HasRole(BOT_ROLE_HEAL) && Rand() > 10) + return; + + if (FindAffectedTarget(GetSpell(SACRED_SHIELD_1), me->GetGUID(), 70, 3)) + return; + + Group const* gr = !IAmFree() ? master->GetGroup() : GetGroup(); + Unit* target = nullptr; + if (!gr) + { + Unit* u = master; + if (u->IsAlive() && u->IsInCombat() && IsTank(u) && me->GetDistance(u) < 30 && + !u->GetAuraEffect(SPELL_AURA_DUMMY, SPELLFAMILY_PALADIN, 0x0, 0x80000, 0x0)) + target = u; + + if (!target && !IAmFree()) + { + BotMap const* map = master->GetBotMgr()->GetBotMap(); + for (BotMap::const_iterator itr = map->begin(); itr != map->end(); ++itr) + { + u = itr->second; + if (u != me && IsTank()) + continue; + if (!u || !u->IsInWorld() || me->GetMap() != u->FindMap() || !u->IsAlive() || !u->IsInCombat() || + u->ToCreature()->IsTempBot() || !IsTank(u) || me->GetDistance(u) > 30 || + u->GetAuraEffect(SPELL_AURA_DUMMY, SPELLFAMILY_PALADIN, 0x0, 0x80000, 0x0)) + continue; + + target = u; + break; + } + } + } + else + { + std::set targets; + std::vector members = BotMgr::GetAllGroupMembers(gr); + for (uint8 i = 0; i < 4 && !targets.empty(); ++i) + { + for (Unit* member : members) + { + if (!(!(i & 1) ? member->IsPlayer() : member->IsNPCBot()) || me->GetMap() != member->FindMap() || + !member->IsAlive() || !member->IsInCombat() || me->GetDistance(member) > 30 || + (i < 2 ? !IsTank(member) : member->getAttackers().empty()) || + (member->IsNPCBot() && member->ToCreature()->IsTempBot()) || + member->GetAuraEffect(SPELL_AURA_DUMMY, SPELLFAMILY_PALADIN, 0x0, 0x80000, 0x0)) + continue; + targets.insert(member); + } + } + if (!targets.empty()) + target = targets.size() == 1u ? *targets.begin() : Trinity::Containers::SelectRandomContainerElement(targets); + } + + if (target && doCast(target, GetSpell(SACRED_SHIELD_1))) + return; + } + + void HOPGroup(uint32 diff) + { + if (!IsSpellReady(HAND_OF_PROTECTION_1, diff) || me->IsMounted() || Feasting() || IsCasting() || Rand() > 30) + return; + + Group const* gr = !IAmFree() ? master->GetGroup() : GetGroup(); + if (!gr) + { + if (me->GetMap() == master->FindMap()) + { + if (HOPTarget(master)) + {} + if (!IAmFree() && HOPTarget(me)) + {} + } + } + else + { + std::vector members = BotMgr::GetAllGroupMembers(gr); + for (uint8 i = 0; i < 2; ++i) + { + for (Unit* member : members) + { + if (!(i == 0 ? member->IsPlayer() : member->IsNPCBot()) || me->GetMap() != member->FindMap() || + !member->IsAlive() || !member->IsInCombat() || me->GetDistance(member) > 30 || IsTank(member) || + (member->IsNPCBot() && member->ToCreature()->IsTempBot()) || + member->GetAuraEffect(SPELL_AURA_DUMMY, SPELLFAMILY_PALADIN, 0x0, 0x80000, 0x0)) + continue; + if (HOPTarget(member)) + return; + } + } + } + } + + bool HOPTarget(Unit* target) + { + if ((target->IsPlayer() ? target->GetClass() : target->ToCreature()->GetBotClass()) == BOT_CLASS_PALADIN) + return false; //paladins should use their own damn bubble + if (target->HasAuraTypeWithMiscvalue(SPELL_AURA_SCHOOL_IMMUNITY, 1) || target->HasAuraTypeWithMiscvalue(SPELL_AURA_SCHOOL_IMMUNITY, 127)) + return false; //immune to physical (hop or smth is present) + if (target->HasAuraTypeWithMiscvalue(SPELL_AURA_MECHANIC_IMMUNITY, 25)) + return false; //forbearance + if (target->getAttackers().empty()) + return false; //HOP only saves from physical, these aoe are rare and on bosses they are ultimate anyway + + if (GetHealthPCT(target) < 15 + 5*(uint32)target->getAttackers().size()) + { + me->InterruptNonMeleeSpells(false); + if (doCast(target, GetSpell(HAND_OF_PROTECTION_1))) + { + if (target->IsPlayer()) + ReportSpellCast(HAND_OF_PROTECTION_1, LocalizedNpcText(target->ToPlayer(), BOT_TEXT__ON_YOU), target->ToPlayer()); + + if (!IAmFree() && target->GetGUID() != master->GetGUID()) + ReportSpellCast(HAND_OF_PROTECTION_1, LocalizedNpcText(master, BOT_TEXT__ON_) + target->GetName() + '!', master); + } + return true; + } + + return false; + } + + void HOFGroup(uint32 diff) + { + if (!IsSpellReady(HAND_OF_FREEDOM_1, diff) || me->IsMounted() || Feasting() || IsCasting() || Rand() > 20) + return; + + Group const* gr = !IAmFree() ? master->GetGroup() : GetGroup(); + if (!gr) + { + if (me->GetMap() == master->FindMap()) + { + if (HOFTarget(master)) + {} + if (!IAmFree() && HOFTarget(me)) + {} + } + } + else + { + std::vector members = BotMgr::GetAllGroupMembers(gr); + for (uint8 i = 0; i < 2; ++i) + { + for (Unit* member : members) + { + if (!(i == 0 ? member->IsPlayer() : member->IsNPCBot()) || me->GetMap() != member->FindMap() || + !member->IsAlive() || me->GetDistance(member) > 30 || (member->IsNPCBot() && member->ToCreature()->IsTempBot())) + continue; + if (HOFTarget(member)) + return; + } + } + } + } + + bool HOFTarget(Unit* target) + { + const bool canUnstun = me->GetLevel() >= 35 && GetSpec() == BOT_SPEC_PALADIN_RETRIBUTION; + if (target->HasAuraType(SPELL_AURA_MECHANIC_IMMUNITY)) + { + if (target->HasAuraTypeWithMiscvalue(SPELL_AURA_MECHANIC_IMMUNITY, 11) && + target->HasAuraTypeWithMiscvalue(SPELL_AURA_MECHANIC_IMMUNITY, 7)) + return false; //immune to root and snares + if (canUnstun && target->HasAuraTypeWithMiscvalue(SPELL_AURA_MECHANIC_IMMUNITY, 12)) + return false; //immune to stuns + } + + SpellInfo const* spellInfo; + AuraApplication const* app; + Unit::AuraApplicationMap const& auras = target->GetAppliedAuras(); + for (Unit::AuraApplicationMap::const_iterator i = auras.begin(); i != auras.end(); ++i) + { + app = i->second; + if (!app || app->IsPositive() || app->GetBase()->IsPassive() || app->GetBase()->GetDuration() < 2000) + continue; + spellInfo = app->GetBase()->GetSpellInfo(); + if (spellInfo->Attributes & SPELL_ATTR0_HIDDEN_CLIENTSIDE) continue; + //if (spellInfo->AttributesEx & SPELL_ATTR1_DONT_DISPLAY_IN_AURA_BAR) continue; + if (spellInfo->GetSpellMechanicMaskByEffectMask(app->GetEffectMask()) & + ((1<Dispel; + uint32 spell; + //Hand of Freedom is level 12, Purify is 8, Cleanse is 42 + if (!GetSpell(CLEANSE)) + spell = (dispel == DISPEL_DISEASE || dispel == DISPEL_POISON) ? + GetSpell(PURIFY_1) : GetSpell(HAND_OF_FREEDOM_1); + else + spell = (dispel == DISPEL_MAGIC || dispel == DISPEL_DISEASE || dispel == DISPEL_POISON) ? + GetSpell(CLEANSE_1) : GetSpell(HAND_OF_FREEDOM_1); + + if (doCast(target, spell)) + return true; + } + } + return false; + } + + void HOSGroup(uint32 diff) + { + if (!IsSpellReady(HAND_OF_SALVATION_1, diff) || IsCasting() || Rand() > 40) + return; + + //Glyph of Salvation + if (me->GetLevel() >= 26 && (IAmFree() || IsTank())) + { + if (!me->getAttackers().empty() && GetHealthPCT(me) < std::max(80 - 5 * me->getAttackers().size(), 25)) + { + if (doCast(me, GetSpell(HAND_OF_SALVATION_1))) + {} + } + return; + } + + if (IAmFree()) + return; + + Group const* gr = master->GetGroup(); + if (!gr) + return; + + std::vector members = BotMgr::GetAllGroupMembers(gr); + for (uint8 i = 0; i < 2; ++i) + { + for (Unit* member : members) + { + if (!(i == 0 ? member->IsPlayer() : member->IsNPCBot()) || me->GetMap() != member->FindMap() || + !member->IsInCombat() || IsTank(member) || me->GetDistance(member) > 30 || + (IsTankingClass(i == 0 ? member->GetClass() : member->ToCreature()->GetBotClass()) && !me->GetMap()->IsRaid()) || + (member->IsNPCBot() && member->ToCreature()->IsTempBot()) || + member->HasAuraTypeWithFamilyFlags(SPELL_AURA_PERIODIC_TRIGGER_SPELL_WITH_VALUE, SPELLFAMILY_PALADIN, 0x100)) + continue; + if (HOSTarget(member)) + return; + } + } + } + + bool HOSTarget(Unit* target) + { + for (Unit* attacker : target->getAttackers()) + { + if (attacker->CanHaveThreatList() && attacker->getAttackers().size() >= 3 && target->GetDistance(attacker) < 15) + { + if (doCast(target, GetSpell(HAND_OF_SALVATION_1))) + return true; + break; //do not try more than once on the same target + } + } + return false; + } + + bool HealTarget(Unit* target, uint32 diff) override + { + if (!target || !target->IsAlive() || target->GetShapeshiftForm() == FORM_SPIRITOFREDEMPTION || me->GetDistance(target) > 40) + return false; + uint8 hp = GetHealthPCT(target); + if (hp > GetHealHpPctThreshold()) + return false; + bool pointed = IsPointedHealTarget(target); + if (hp > 90 && !(pointed && me->GetMap()->IsRaid()) && + (!target->IsInCombat() || target->getAttackers().empty() || !IsTank(target) || !me->GetMap()->IsRaid())) + return false; + //try to preserve heal if Divine Plea is active + if (hp > 50 && me->GetAuraEffect(SPELL_AURA_OBS_MOD_POWER, SPELLFAMILY_PALADIN, 0x0, 0x0, 0x1)) + return false; + + int32 hps = GetHPS(target); + int32 xphp = target->GetHealth() + hps * 2.5f; + int32 hppctps = int32(hps * 100.f / float(target->GetMaxHealth())); + int32 xphploss = xphp > int32(target->GetMaxHealth()) ? 0 : abs(int32(xphp - target->GetMaxHealth())); + int32 xppct = hp + hppctps * 2.5f; + if (xppct >= 95 && hp >= 25 && !pointed) + return false; + + //Lay on Hands + if (IsSpellReady(LAY_ON_HANDS_1, diff, false) && (target != me || shieldDelayTimer <= diff) && + (target->IsInCombat() || !target->getAttackers().empty()) && Rand() < 80 && hp <= 20 && xppct <=0 && + !target->HasAuraTypeWithMiscvalue(SPELL_AURA_SCHOOL_IMMUNITY, 127)) + { + me->InterruptNonMeleeSpells(false); + if (doCast(target, GetSpell(LAY_ON_HANDS_1))) + { + if (target->GetTypeId() == TYPEID_PLAYER) + ReportSpellCast(LAY_ON_HANDS_1, LocalizedNpcText(target->ToPlayer(), BOT_TEXT__ON_YOU), target->ToPlayer()); + + if (!IAmFree() && target != master) + { + std::string msg = target == me ? LocalizedNpcText(master, BOT_TEXT__ON_MYSELF) : (LocalizedNpcText(master, BOT_TEXT__ON_) + target->GetName() + '!'); + ReportSpellCast(LAY_ON_HANDS_1, msg, master); + } + return true; + } + } + + //Holy Shock + if (IsSpellReady(HOLY_SHOCK_1, diff, false) && !target->IsCharmed() && !target->isPossessed() && + xphploss > _heals[HOLY_SHOCK_1]) + { + me->InterruptNonMeleeSpells(false); + if (hp < 30 && IsSpellReady(DIVINE_FAVOR_1, diff, false) && !target->getAttackers().empty()) + if (doCast(me, GetSpell(DIVINE_FAVOR_1))) + {} + if (doCast(target, GetSpell(HOLY_SHOCK_1))) + return true; + } + + if (IsCasting()) return false; + + Unit const* u = target->GetVictim(); + bool tanking = u && IsTank(target) && u->ToCreature() && u->ToCreature()->isWorldBoss(); + + if (IsSpellReady(DIVINE_ILLUMINATION_1, diff, false) && GetManaPCT(me) <= 50 && Rand() < 50 + 50*tanking) + if (doCast(me, GetSpell(DIVINE_ILLUMINATION_1))) + {} + + //Holy Light + if (IsSpellReady(HOLY_LIGHT_1, diff) && (xppct > 15 || !GetSpell(FLASH_OF_LIGHT_1)) && + xphploss > _heals[HOLY_LIGHT_1]) + { + //Aura Mastery + if (hp < 60 && _aura == CONCENTRATIONAURA && IsSpellReady(AURA_MASTERY_1, diff, false) && Rand() < 90 && + ((!me->getAttackers().empty() && (*me->getAttackers().begin())->GetTypeId() == TYPEID_PLAYER) || + me->GetMap()->Instanceable() || tanking)) + if (doCast(me, GetSpell(AURA_MASTERY_1))) + {} + if (doCast(target, GetSpell(HOLY_LIGHT_1))) + return true; + } + //Flash of Light + if (IsSpellReady(FLASH_OF_LIGHT_1, diff) && (tanking || xphploss > _heals[FLASH_OF_LIGHT_1])) + { + if (doCast(target, GetSpell(FLASH_OF_LIGHT_1))) + return true; + } + + return false; + } + + void StartAttack(Unit* u, bool force = false) + { + if (!bot_ai::StartAttack(u, force)) + return; + GetInPosition(force, u); + } + + void JustEnteredCombat(Unit* u) override { bot_ai::JustEnteredCombat(u); } + void KilledUnit(Unit* u) override { bot_ai::KilledUnit(u); } + void EnterEvadeMode(EvadeReason why = EVADE_REASON_OTHER) override { bot_ai::EnterEvadeMode(why); } + void MoveInLineOfSight(Unit* u) override { bot_ai::MoveInLineOfSight(u); } + void JustDied(Unit* u) override { bot_ai::JustDied(u); } + + void BreakCC(uint32 diff) override + { + if (me->GetLevel() >= 35 && GetSpec() == BOT_SPEC_PALADIN_RETRIBUTION && IsSpellReady(HAND_OF_FREEDOM_1, diff) && Rand() < 30 && me->HasAuraWithMechanic(1<IsMounted()) + me->RemoveAurasByType(SPELL_AURA_MOUNTED); + if (doCast(me, GetSpell(HAND_OF_FREEDOM_1))) + return; + } + bot_ai::BreakCC(diff); + } + + void UpdateAI(uint32 diff) override + { + if (!GlobalUpdate(diff)) + return; + + DoVehicleActions(diff); + if (!CanBotAttackOnVehicle()) + return; + + if (IsPotionReady()) + { + if (GetManaPCT(me) < 30) + DrinkPotion(true); + else if (GetHealthPCT(me) < 60) + DrinkPotion(false); + } + else if (GetManaPCT(me) < 40 && IsSpellReady(DIVINE_PLEA_1, diff) && Rand() < 30 && + !me->GetAuraEffect(SPELL_AURA_OBS_MOD_POWER, SPELLFAMILY_PALADIN, 0x0, 0x80004000, 0x1)) + { + if (doCast(me, GetSpell(DIVINE_PLEA_1))) + return; + } + + CheckRacials(diff); + + HOPGroup(diff); + CheckBeacon(diff); + + if (me->GetMap()->IsRaid()) + { + CureGroup(GetSpell(CLEANSE), diff); + BuffAndHealGroup(diff); + CheckHandOfSacrifice(diff); + ShieldGroup(diff); + } + else + { + BuffAndHealGroup(diff); + CheckHandOfSacrifice(diff); + ShieldGroup(diff); + CureGroup(GetSpell(CLEANSE), diff); + } + + CheckSacrifice(diff); + HOFGroup(diff); + HOSGroup(diff); + + if (!me->IsInCombat()) + DoNonCombatActions(diff); + + CheckSeal(diff); + CheckAura(diff); + + if (ProcessImmediateNonAttackTarget()) + return; + + if (!CheckAttackTarget()) + return; + + Repentance(diff); + Counter(diff); + TurnEvil(diff); + + CheckDivineIntervention(diff); + if (!me->IsAlive()) + return; + + if (IsCasting()) + return; + + CheckUsableItems(diff); + + DoNormalAttack(diff); + } + + void DoNonCombatActions(uint32 diff) + { + if (GC_Timer > diff || me->IsMounted() || IsCasting()) + return; + + ResurrectGroup(GetSpell(REDEMPTION_1)); + } + + void CheckSeal(uint32 diff) + { + if (checkSealTimer > diff || GC_Timer > diff || me->IsMounted() || + IsCasting() || Feasting() || Rand() > 30) + return; + + checkSealTimer = 10000; + + Unit const* victim = me->GetVictim(); + + uint32 COMMAND = GetSpell(SEAL_OF_COMMAND_1); + uint32 LIGHT = GetSpell(SEAL_OF_LIGHT_1); + uint32 RIGHT = GetSpell(SEAL_OF_RIGHTEOUSNESS_1); + uint32 WISDOM = GetSpell(SEAL_OF_WISDOM_1); + uint32 JUSTICE = GetSpell(SEAL_OF_JUSTICE_1); + uint32 VENGEANCE = (me->GetRaceMask() & RACEMASK_ALLIANCE) ? GetSpell(SEAL_OF_VENGEANCE_1) : GetSpell(SEAL_OF_CORRUPTION_1); + + if (VENGEANCE && victim && + (victim->GetMaxHealth() > me->GetMaxHealth() * (2 + victim->getAttackers().size() / 2) || + victim->GetClass() == CLASS_ROGUE)) + COMMAND = VENGEANCE; + + uint32 SEAL = 0; + + if (IsMelee() && GetManaPCT(me) < 20 && WISDOM) + SEAL = WISDOM; + else if (IsTank()) + { + if (JUSTICE && me->getAttackers().size() > 1) + JUSTICE = 0; + if (JUSTICE && victim) + { + Creature const* cre = victim->ToCreature(); + if (cre && cre->GetCreatureTemplate()->rank != CREATURE_ELITE_NORMAL && + (cre->GetCreatureTemplate()->MechanicImmuneMask & (1<<(MECHANIC_STUN-1)))) + JUSTICE = 0; + } + SEAL = COMMAND ? COMMAND : JUSTICE ? JUSTICE : RIGHT; + } + else if (HasRole(BOT_ROLE_DPS)) + { + SEAL = WISDOM && HasRole(BOT_ROLE_HEAL) ? WISDOM : COMMAND ? COMMAND : RIGHT; + } + else if (HasRole(BOT_ROLE_HEAL)) + SEAL = WISDOM ? WISDOM : LIGHT ? LIGHT : RIGHT; + + if (SEAL && !me->HasAura(SEAL)) + if (doCast(me, SEAL)) + return; + } + + void CheckAura(uint32 diff) + { + if (checkAuraTimer > diff || GC_Timer > diff || IAmFree() || IsCasting() || + /*me->GetExactDist(master) > 40 || me->IsMounted() || Feasting() || */Rand() > 20) + return; + + checkAuraTimer = urand(3000, 6000); + + //7 paladins in group? + uint32 DEVOTION_AURA = GetSpell(DEVOTION_AURA_1); + uint32 CONCENTRATION_AURA = GetSpell(CONCENTRATION_AURA_1); + uint32 FIRE_RESISTANCE_AURA = GetSpell(FIRE_RESISTANCE_AURA_1); + uint32 FROST_RESISTANCE_AURA = GetSpell(FROST_RESISTANCE_AURA_1); + uint32 SHADOW_RESISTANCE_AURA = GetSpell(SHADOW_RESISTANCE_AURA_1); + uint32 RETRIBUTION_AURA = GetSpell(RETRIBUTION_AURA_1); + //uint32 CRUSADER_AURA = GetSpell(CRUSADER_AURA_1); + + bool pureHealer = GetSpec() == BOT_SPEC_PALADIN_HOLY; + bool isProt = GetSpec() == BOT_SPEC_PALADIN_PROTECTION; + + std::map idMap; + uint32 mask = _getAurasMask(idMap); + + //for Aura Mastery allow every pure healer paladin to have their own C aura + //SPECIFIC_AURA_MY_AURA check still works so no spam + if (pureHealer) + mask &= ~SPECIFIC_AURA_CONCENTRATION; + + //if (CRUSADER_AURA && !(mask & SPECIFIC_AURA_CRUSADER) && + // (master->IsMounted() || me->IsMounted())) + //{ + // if (doCast(me, CRUSADER_AURA)) + // return; + //} + + //Has own aura or has all auras + if (mask & SPECIFIC_AURA_MY_AURA) + return; + else if ((mask & SPECIFIC_AURA_ALL_AUTOUSE) == SPECIFIC_AURA_ALL_AUTOUSE) + return; + + //TODO: priority? + if (DEVOTION_AURA && + (!(mask & SPECIFIC_AURA_DEVOTION) || idMap[DEVOTION_AURA_1] < DEVOTION_AURA) && + (!RETRIBUTION_AURA || IsTank(master) || isProt)) + { + if (doCast(me, DEVOTION_AURA)) + return; + } + if (CONCENTRATION_AURA && !(mask & SPECIFIC_AURA_CONCENTRATION) && + (master->GetClass() == BOT_CLASS_MAGE || master->GetClass() == BOT_CLASS_PRIEST || + master->GetClass() == BOT_CLASS_WARLOCK || master->GetClass() == BOT_CLASS_DRUID || + (!IAmFree() && master->GetClass() == BOT_CLASS_PALADIN) || pureHealer)) + { + if (doCast(me, CONCENTRATION_AURA)) + return; + } + if (RETRIBUTION_AURA && + (!(mask & SPECIFIC_AURA_RETRIBUTION) || idMap[RETRIBUTION_AURA_1] < RETRIBUTION_AURA) && + (IsMeleeClass(master->GetClass()) || IsMelee())) + { + if (doCast(me, RETRIBUTION_AURA)) + return; + } + if (FIRE_RESISTANCE_AURA && + (!(mask & SPECIFIC_AURA_FIRE_RES) || idMap[FIRE_RESISTANCE_AURA_1] < FIRE_RESISTANCE_AURA)) + { + if (doCast(me, FIRE_RESISTANCE_AURA)) + return; + } + if (FROST_RESISTANCE_AURA && + (!(mask & SPECIFIC_AURA_FROST_RES) || idMap[FROST_RESISTANCE_AURA_1] < FROST_RESISTANCE_AURA)) + { + if (doCast(me, FROST_RESISTANCE_AURA)) + return; + } + if (SHADOW_RESISTANCE_AURA && + (!(mask & SPECIFIC_AURA_SHADOW_RES) || idMap[SHADOW_RESISTANCE_AURA_1] < SHADOW_RESISTANCE_AURA)) + { + if (doCast(me, SHADOW_RESISTANCE_AURA)) + return; + } + } + + bool BuffTarget(Unit* target, uint32 /*diff*/) override + { + if (me->IsInCombat() && !master->GetMap()->IsRaid()) return false; + + if (target == me) + { + if (uint32 rFury = GetSpell(RIGHTEOUS_FURY_1)) + { + if (IsTank()) + { + if (!me->HasAura(rFury) && doCast(me, rFury)) + return true; + } + else if (me->HasAura(rFury)) + me->RemoveAurasDueToSpell(rFury); + } + } + + uint32 mask = _getBlessingsMask(target); + + //already has my blessing + if (mask & SPECIFIC_BLESSING_MY_BLESSING) + return false; + + uint32 BLESSING_OF_WISDOM = GetSpell(BLESSING_OF_WISDOM_1); + uint32 BLESSING_OF_KINGS = GetSpell(BLESSING_OF_KINGS_1); + uint32 BLESSING_OF_SANCTUARY = GetSpell(BLESSING_OF_SANCTUARY_1); + uint32 BLESSING_OF_MIGHT = GetSpell(BLESSING_OF_MIGHT_1); + + bool wisdom = (mask & SPECIFIC_BLESSING_WISDOM); + bool kings = (mask & SPECIFIC_BLESSING_KINGS); + bool sanctuary = (mask & SPECIFIC_BLESSING_SANCTUARY); + bool might = (mask & SPECIFIC_BLESSING_MIGHT); + + if (IsTank(target)) + { + if (BLESSING_OF_KINGS && !kings && doCast(target, BLESSING_OF_KINGS)) + return true; + else if (BLESSING_OF_SANCTUARY && !sanctuary && doCast(target, BLESSING_OF_SANCTUARY)) + return true; + else if (BLESSING_OF_WISDOM && !wisdom && target->GetMaxPower(POWER_MANA) > 1 && doCast(target, BLESSING_OF_WISDOM)) + return true; + else if (BLESSING_OF_MIGHT && !might && doCast(target, BLESSING_OF_MIGHT)) + return true; + + return false; + } + + uint8 Class = 0; + if (target->GetTypeId() == TYPEID_PLAYER) + Class = target->GetClass(); + else if (Creature* cre = target->ToCreature()) + Class = cre->GetBotAI() ? cre->GetBotAI()->GetBotClass() : cre->GetClass(); + + switch (Class) + { + case BOT_CLASS_BM: + case BOT_CLASS_SPHYNX: + case BOT_CLASS_DREADLORD: + case BOT_CLASS_SPELLBREAKER: + case BOT_CLASS_DARK_RANGER: + case BOT_CLASS_NECROMANCER: + case BOT_CLASS_SEA_WITCH: + case BOT_CLASS_CRYPT_LORD: + if (BLESSING_OF_KINGS && !kings && doCast(target, BLESSING_OF_KINGS)) + return true; + else if (BLESSING_OF_MIGHT && !might && doCast(target, BLESSING_OF_MIGHT)) + return true; + else if (BLESSING_OF_SANCTUARY && !sanctuary && doCast(target, BLESSING_OF_SANCTUARY)) + return true; + break; + case CLASS_PRIEST: + case CLASS_MAGE: + case CLASS_WARLOCK: + if (BLESSING_OF_KINGS && !kings && doCast(target, BLESSING_OF_KINGS)) + return true; + else if (BLESSING_OF_WISDOM && !wisdom && doCast(target, BLESSING_OF_WISDOM)) + return true; + else if (BLESSING_OF_SANCTUARY && !sanctuary && doCast(target, BLESSING_OF_SANCTUARY)) + return true; + break; + case CLASS_DEATH_KNIGHT: + case CLASS_WARRIOR: + case CLASS_PALADIN: + case CLASS_ROGUE: + case CLASS_HUNTER: + case CLASS_SHAMAN: + if (BLESSING_OF_KINGS && !kings && doCast(target, BLESSING_OF_KINGS)) + return true; + else if (BLESSING_OF_MIGHT && !might && doCast(target, BLESSING_OF_MIGHT)) + return true; + else if (BLESSING_OF_SANCTUARY && !sanctuary && doCast(target, BLESSING_OF_SANCTUARY)) + return true; + else if (BLESSING_OF_WISDOM && !wisdom && target->GetPowerType() == POWER_MANA && doCast(target, BLESSING_OF_WISDOM)) + return true; + break; + default: + if (BLESSING_OF_KINGS && !kings && doCast(target, BLESSING_OF_KINGS)) + return true; + else if (BLESSING_OF_WISDOM && !wisdom && target->GetMaxPower(POWER_MANA) > 1 && doCast(target, BLESSING_OF_WISDOM)) + return true; + else if (BLESSING_OF_SANCTUARY && !sanctuary && doCast(target, BLESSING_OF_SANCTUARY)) + return true; + else if (BLESSING_OF_MIGHT && !might && doCast(target, BLESSING_OF_MIGHT)) + return true; + break; + } + return false; + } + + void Repentance(uint32 diff, Unit* target = nullptr) + { + if (target) + { + if (IsSpellReady(REPENTANCE_1, diff) && doCast(target, GetSpell(REPENTANCE_1))) + return; + } + else if (IsSpellReady(REPENTANCE_1, diff)) + { + Unit* u = FindStunTarget(); + if (u && u->GetVictim() != me && doCast(u, GetSpell(REPENTANCE_1))) + return; + } + } + + void Counter(uint32 diff) + { + if (IsCasting()) + return; + if (Rand() > 60) + return; + + Unit* target = IsSpellReady(REPENTANCE_1, diff) ? FindCastingTarget(20, 0, REPENTANCE_1) : nullptr; + if (target) + Repentance(diff, target); //first check repentance + if (!target && IsSpellReady(TURN_EVIL_1, diff)) + { + target = FindCastingTarget(20, 0, TURN_EVIL_1); + if (target && doCast(target, GetSpell(TURN_EVIL_1))) + return; + } + if (!target && IsSpellReady(HOLY_WRATH_1, diff, false) && HasRole(BOT_ROLE_DPS)) + { + target = FindCastingTarget(8, 0, TURN_EVIL_1); //here we check target as with turn evil cuz of same requirements + if (target && doCast(me, GetSpell(HOLY_WRATH_1))) + return; + } + if (!target && IsSpellReady(HAMMER_OF_JUSTICE_1, diff, false)) + { + target = FindCastingTarget(10, 0, HAMMER_OF_JUSTICE_1); + if (target && doCast(target, GetSpell(HAMMER_OF_JUSTICE_1))) + {} + } + } + + void TurnEvil(uint32 diff) + { + if (!IsSpellReady(TURN_EVIL_1, diff) || IsCasting() || Rand() > 50 || + FindAffectedTarget(GetSpell(TURN_EVIL_1), me->GetGUID(), 50)) + return; + Unit* target = FindUndeadCCTarget(20, TURN_EVIL_1); + if (target && + (target != me->GetVictim() || GetHealthPCT(me) < 70 || target->GetVictim() == master) && + doCast(target, GetSpell(TURN_EVIL_1))) + return; + else + { + for (Unit* mtar : { opponent, disttarget }) + { + if (mtar && (mtar->GetCreatureTypeMask() & CREATURE_TYPEMASK_DEMON_OR_UNDEAD) && !CCed(mtar) && + mtar->GetVictim() && !IsTank(mtar->GetVictim()) && mtar->GetVictim() != me && + GetHealthPCT(me) < 90 && + doCast(mtar, GetSpell(TURN_EVIL_1))) + return; + } + } + } + + void CheckDivineIntervention(uint32 diff) + { + if (!IsSpellReady(DIVINE_INTERVENTION_1, diff, !IsCasting()) || IAmFree() || IsTank() || + GetManaPCT(me) > 10 || Rand() > 20) + return; + + std::list players; + + if (master->IsAlive() && !master->getAttackers().empty() && GetHealthPCT(master) < 15 && + !master->HasAuraTypeWithMiscvalue(SPELL_AURA_SCHOOL_IMMUNITY, 127)) + players.push_back(master); + if (Group const* gr = master->GetGroup()) + { + for (GroupReference const* itr = gr->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* pl = itr->GetSource(); + if (!pl || pl == master || !pl->IsInWorld() || me->GetMap() != pl->FindMap() || + !pl->IsAlive() || pl->getAttackers().empty() || GetHealthPCT(pl) > 15 || + pl->HasAuraTypeWithMiscvalue(SPELL_AURA_SCHOOL_IMMUNITY, 127)) + continue; + + players.push_back(pl); + } + } + + if (players.empty()) + return; + + Unit* target = players.size() == 1 ? players.front() : Trinity::Containers::SelectRandomContainerElement(players); + if (doCast(target, GetSpell(DIVINE_INTERVENTION_1))) + return; + } + + void DoNormalAttack(uint32 diff) + { + Unit* mytar = opponent ? opponent : disttarget ? disttarget : nullptr; + if (!mytar) + return; + + StartAttack(mytar, IsMelee()); + + CheckAttackState(); + if (!me->IsAlive() || !mytar->IsAlive()) + return; + + MoveBehind(mytar); + + //Divine Shield + if (IsSpellReady(DIVINE_SHIELD_1, diff) && shieldDelayTimer <= diff && (IAmFree() || !IsTank()) && + Rand() < 80 && !me->getAttackers().empty() && GetHealthPCT(me) < 25) + { + if (doCast(me, GetSpell(DIVINE_SHIELD_1))) + return; + } + + //Holy shield + if (IsSpellReady(HOLY_SHIELD_1, diff) && HasRole(BOT_ROLE_DPS) && CanBlock() && !me->getAttackers().empty() && + !me->HasAuraTypeWithMiscvalue(SPELL_AURA_SCHOOL_IMMUNITY, 127)) + { + if (doCast(me, GetSpell(HOLY_SHIELD_1))) + return; + } + + auto [can_do_holy, can_do_normal] = CanAffectVictimBools(mytar, SPELL_SCHOOL_HOLY, SPELL_SCHOOL_NORMAL); + + float dist = me->GetDistance(mytar); + + //HAMMER OF WRATH + if (IsSpellReady(HAMMER_OF_WRATH_1, diff) && can_do_holy && HasRole(BOT_ROLE_DPS) && Rand() < 80 && + mytar->HasAuraState(AURA_STATE_HEALTHLESS_20_PERCENT) && dist < 30) + { + if (doCast(mytar, GetSpell(HAMMER_OF_WRATH_1))) + return; + } + //HAND OF RECKONING //No GCD + Unit* u = mytar->GetVictim(); + if (IsSpellReady(HAND_OF_RECKONING_1, diff, false) && can_do_holy && u && u != me && Rand() < 50 && dist < 30 && + mytar->GetTypeId() == TYPEID_UNIT && !mytar->IsControlledByPlayer() && + !CCed(mytar) && HasRole(BOT_ROLE_DPS) && !mytar->HasAuraType(SPELL_AURA_MOD_TAUNT) && + (!IsTank(u) || (IsTank() && GetHealthPCT(me) > 67 && + (GetHealthPCT(u) < 30 || (IsOffTank() && !IsOffTank(u) && IsPointedOffTankingTarget(mytar)) || + (!IsOffTank() && IsOffTank(u) && IsPointedTankingTarget(mytar))))) && + ((!IsTankingClass(u->GetClass()) && GetHealthPCT(u) < 80) || IsTank()) && + IsInBotParty(u)) + { + if (doCast(mytar, GetSpell(HAND_OF_RECKONING_1))) + return; + } + //HAND OF RECKONING 2 (distant) + if (IsSpellReady(HAND_OF_RECKONING_1, diff, false) && !IAmFree() && u == me && Rand() < 30 && IsTank() && HasRole(BOT_ROLE_DPS) && + (IsOffTank() || master->GetBotMgr()->GetNpcBotsCountByRole(BOT_ROLE_TANK_OFF) == 0) && + !(me->GetLevel() >= 40 && mytar->GetTypeId() == TYPEID_UNIT && + (mytar->ToCreature()->IsDungeonBoss() || mytar->ToCreature()->isWorldBoss()))) + { + Unit* tUnit = FindDistantTauntTarget(); + if (tUnit) + { + if (doCast(tUnit, GetSpell(HAND_OF_RECKONING_1))) + return; + } + } + //RIGHTEOUS DEFENSE //No GCD + if (IsSpellReady(RIGHTEOUS_DEFENSE_1, diff, false) && !IAmFree() && u && u != me && IsTank() && + me->GetDistance(u) < 40 && mytar->GetTypeId() == TYPEID_UNIT && !mytar->IsControlledByPlayer() && + !IsTankingClass(u->GetClass()) && GetHealthPCT(u) < 80 && + !CCed(mytar) && !mytar->HasAuraType(SPELL_AURA_MOD_TAUNT) && + (!IsTank(u) || (GetHealthPCT(u) < 30 && GetHealthPCT(me) > 67)) && + IsInBotParty(u) && Rand() < 20 + 30 * u->getAttackers().size()) + { + if (doCast(u, GetSpell(RIGHTEOUS_DEFENSE_1))) + return; + } + //RIGHTEOUS DEFENSE 2 (distant) + if (IsSpellReady(RIGHTEOUS_DEFENSE_1, diff, false) && !IAmFree() && u == me && IsTank() && Rand() < 30 && + !(me->GetLevel() >= 40 && mytar->GetTypeId() == TYPEID_UNIT && + (mytar->ToCreature()->IsDungeonBoss() || mytar->ToCreature()->isWorldBoss()))) + { + Unit* tUnit = FindDistantTauntTarget(40, true); + if (tUnit) + { + if (doCast(tUnit, GetSpell(RIGHTEOUS_DEFENSE_1))) + return; + } + } + //Divine Plea + if (IsSpellReady(DIVINE_PLEA_1, diff) && Rand() < 30 && GetManaPCT(me) < (IsTank() ? 90 : 7) && + !me->GetAuraEffect(SPELL_AURA_OBS_MOD_POWER, SPELLFAMILY_PALADIN, 0x0, 0x80004000, 0x1)) + { + if (doCast(me, GetSpell(DIVINE_PLEA_1))) + return; + } + //Avenging Wrath (tank - big threat, dps - big hp, heal - divine plea counter) + if (IsSpellReady(AVENGING_WRATH_1, diff, false) && can_do_holy && avDelayTimer <= diff && + HasRole(BOT_ROLE_HEAL|BOT_ROLE_DPS) && Rand() < 35 && dist < 30 && + IsTank() ? (mytar->GetTypeId() == TYPEID_UNIT && (mytar->ToCreature()->IsDungeonBoss() || mytar->ToCreature()->isWorldBoss())) : + (!HasRole(BOT_ROLE_HEAL) || !HasRole(BOT_ROLE_RANGED)) ? (mytar->GetHealth() > me->GetMaxHealth()/4 * (1 + mytar->getAttackers().size())) : + (me->GetAuraEffect(SPELL_AURA_OBS_MOD_POWER, SPELLFAMILY_PALADIN, 0x0, 0x80004000, 0x1) != nullptr)) + { + if (doCast(me, GetSpell(AVENGING_WRATH_1))) + {} + } + //Avenger's shield + if (IsSpellReady(AVENGERS_SHIELD_1, diff) && can_do_holy && CanBlock() && + HasRole(BOT_ROLE_DPS) && dist < 30 && Rand() < 60) + { + if (doCast(mytar, GetSpell(AVENGERS_SHIELD_1))) + return; + } + //Divine Protection tanks only + if (IsSpellReady(DIVINE_PROTECTION_1, diff, false) && shieldDelayTimer <= diff && IsTank() && Rand() < 80 && + !me->getAttackers().empty() && GetHealthPCT(me) < 67 - 20*me->HasAuraType(SPELL_AURA_PERIODIC_HEAL)) + { + if (doCast(me, GetSpell(DIVINE_PROTECTION_1))) + return; + } + //Exorcism (have cast window or instant) + if (IsSpellReady(EXORCISM_1, diff) && can_do_holy && HasRole(BOT_ROLE_DPS) && dist < 30 && Rand() < 70 && + ((IsTank() && dist > 12) || (HasRole(BOT_ROLE_RANGED) && !HasRole(BOT_ROLE_HEAL)) || + me->GetAuraEffect(SPELL_AURA_ADD_PCT_MODIFIER, SPELLFAMILY_PALADIN, 0x0, 0x0, 0x2))) + { + if (doCast(mytar, GetSpell(EXORCISM_1))) + return; + } + //Hammer of Justice + if (IsSpellReady(HAMMER_OF_JUSTICE_1, diff) && !CCed(mytar) && dist < 10 && Rand() < 20 && + mytar->GetDiminishing(DIMINISHING_STUN) <= DIMINISHING_LEVEL_2 && + !IsImmunedToMySpellEffect(mytar, sSpellMgr->GetSpellInfo(HAMMER_OF_JUSTICE_1), EFFECT_0)) + { + if (doCast(mytar, GetSpell(HAMMER_OF_JUSTICE_1))) + return; + } + //Judgement + if (GetSpellCooldown(JUDGEMENT_OF_LIGHT_1) <= diff && can_do_holy && HasRole(BOT_ROLE_DPS) && Rand() < 120) + { + uint32 JUDGEMENT = 0; + + if (GetSpell(JUDGEMENT_OF_JUSTICE_1) && mytar->HasAuraType(SPELL_AURA_MOD_INCREASE_SPEED) && + dist < CalcSpellMaxRange(JUDGEMENT_OF_JUSTICE_1)) + { + //has joj from someone else + bool canCast = true; + Unit::AuraEffectList const& notSpeedAuras = mytar->GetAuraEffectsByType(SPELL_AURA_USE_NORMAL_MOVEMENT_SPEED); + for (Unit::AuraEffectList::const_iterator itr = notSpeedAuras.begin(); itr != notSpeedAuras.end(); ++itr) + { + if ((*itr)->GetCasterGUID() != me->GetGUID() && (*itr)->GetBase()->GetDuration() > 2000) + { + canCast = false; + break; + } + } + if (canCast) + { + //has sprint or something + Unit::AuraEffectList const& speedAuras = mytar->GetAuraEffectsByType(SPELL_AURA_MOD_INCREASE_SPEED); + for (Unit::AuraEffectList::const_iterator itr = speedAuras.begin(); itr != speedAuras.end(); ++itr) + { + if (!(*itr)->GetBase()->IsPassive() && + (*itr)->GetBase()->GetDuration() > 2000 && + (*itr)->GetAmount() >= 30) + { + JUDGEMENT = JUDGEMENT_OF_JUSTICE_1; + break; + } + } + } + } + if (!JUDGEMENT && GetSpell(JUDGEMENT_OF_WISDOM_1) && dist < CalcSpellMaxRange(JUDGEMENT_OF_WISDOM_1)) + { + //from 35% to 50% mana + AuraEffect const* wisd = mytar->GetAuraEffect(JUDGEMENT_OF_WISDOM_AURA, 0); + //AuraEffect const* wisd = mytar->GetAuraEffect(SPELL_AURA_DUMMY, SPELLFAMILY_PALADIN, 3014, 0); + uint8 myManaPct = GetManaPCT(me); + if ((!wisd && myManaPct < 35) || (wisd && wisd->GetCasterGUID() == me->GetGUID() && myManaPct < 50)) + JUDGEMENT = JUDGEMENT_OF_WISDOM_1; + } + if (!JUDGEMENT && GetSpell(JUDGEMENT_OF_LIGHT_1) && dist < CalcSpellMaxRange(JUDGEMENT_OF_LIGHT_1)) + { + JUDGEMENT = JUDGEMENT_OF_LIGHT_1; + } + + //Conditional spell unavailable, use any other + if (!JUDGEMENT) + { + if (GetSpell(JUDGEMENT_OF_WISDOM_1)) + JUDGEMENT = JUDGEMENT_OF_WISDOM_1; + else if (GetSpell(JUDGEMENT_OF_LIGHT_1)) + JUDGEMENT = JUDGEMENT_OF_LIGHT_1; + else if (GetSpell(JUDGEMENT_OF_JUSTICE_1)) + JUDGEMENT = JUDGEMENT_OF_JUSTICE_1; + } + + if (JUDGEMENT && doCast(mytar, GetSpell(JUDGEMENT))) + return; + } + //Consecration + if (IsSpellReady(CONSECRATION_1, diff) && can_do_holy && HasRole(BOT_ROLE_DPS) && dist < 5 && + !mytar->isMoving() && Rand() < 50) + { + if (doCast(me, GetSpell(CONSECRATION_1))) + return; + } + //Hammer of the Righteous (1h only) + if (IsSpellReady(HAMMER_OF_THE_RIGHTEOUS_1, diff) && can_do_holy && HasRole(BOT_ROLE_DPS) && + dist < 5 && Rand() < 80) + { + Item const* weapMH = GetEquips(BOT_SLOT_MAINHAND); + if (weapMH && + (weapMH->GetTemplate()->InventoryType == INVTYPE_WEAPON || + weapMH->GetTemplate()->InventoryType == INVTYPE_WEAPONMAINHAND) && + doCast(mytar, GetSpell(HAMMER_OF_THE_RIGHTEOUS_1))) + return; + } + //Shield of Righteousness + if (IsSpellReady(SHIELD_OF_RIGHTEOUSNESS_1, diff) && can_do_holy && HasRole(BOT_ROLE_DPS) && CanBlock() && + (IsTank() || IAmFree()) && dist < 5 && Rand() < 90) + { + if (doCast(mytar, GetSpell(SHIELD_OF_RIGHTEOUSNESS_1))) + return; + } + //Crusader Strike + if (IsSpellReady(CRUSADER_STRIKE_1, diff) && can_do_normal && HasRole(BOT_ROLE_DPS) && dist < 5 && Rand() < 90) + { + if (doCast(mytar, GetSpell(CRUSADER_STRIKE_1))) + return; + } + //Divine Storm + if (IsSpellReady(DIVINE_STORM_1, diff) && can_do_normal && HasRole(BOT_ROLE_DPS) && dist < 7 && Rand() < 40) + { + if (doCast(me, GetSpell(DIVINE_STORM_1))) + return; + } + //Holy Wrath + if (IsSpellReady(HOLY_WRATH_1, diff) && HasRole(BOT_ROLE_DPS) && Rand() < 50) + { + if ((mytar->GetCreatureType() == CREATURE_TYPE_UNDEAD || mytar->GetCreatureType() == CREATURE_TYPE_DEMON) && + dist < 8.5f && doCast(me, GetSpell(HOLY_WRATH_1))) + return; + else + { + if (FindUndeadCCTarget(8.5f, HOLY_WRATH_1, false) && + doCast(me, GetSpell(HOLY_WRATH_1))) + return; + } + } + } + + void ApplyClassSpellCritMultiplierAll(Unit const* /*victim*/, float& crit_chance, SpellInfo const* spellInfo, SpellSchoolMask schoolMask, WeaponAttackType /*attackType*/) const override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + uint8 lvl = me->GetLevel(); + + //Sanctified Light: 6% additional critical chance for Holy Light and Holy Shock + if ((GetSpec() == BOT_SPEC_PALADIN_HOLY) && lvl >= 30 && (baseId == HOLY_LIGHT_1 || baseId == HOLY_SHOCK_1)) + crit_chance += 6.f; + //Holy Power: 5% additional critical chance for Holy spells + if ((GetSpec() == BOT_SPEC_PALADIN_HOLY) && lvl >= 35 && (schoolMask & SPELL_SCHOOL_MASK_HOLY)) + crit_chance += 5.f; + //Improved Flash of Light (id: 20251): 6% additional critical chance for Flash of Light + if (lvl >= 70 && baseId == FLASH_OF_LIGHT_1) + crit_chance += 6.f; + //Glyph of Flash of Light: 5% additional critical chance for Flash of Light + if (lvl >= 20 && baseId == FLASH_OF_LIGHT_1) + crit_chance += 5.f; + //Sanctified Wrath: 50% additional critical chance for Hammer of Wrath + if ((GetSpec() == BOT_SPEC_PALADIN_RETRIBUTION) && lvl >= 45 && baseId == HAMMER_OF_WRATH_1) + crit_chance += 50.f; + //Fanaticism: 18% additional critical chance for all Judgements (not shure which check is right) + if ((GetSpec() == BOT_SPEC_PALADIN_RETRIBUTION) && lvl >= 45 && spellInfo->GetCategory() == SPELLCATEGORY_JUDGEMENT) + crit_chance += 18.f; + //Infusion of Light + if (baseId == HOLY_LIGHT_1) + { + if (AuraEffect const* infu = me->GetAuraEffect(INFUSION_OF_LIGHT_BUFF, 0)) + if (infu->IsAffectingSpell(spellInfo)) + crit_chance += 20.f; + } + if (baseId == HOLY_LIGHT_1 || baseId == FLASH_OF_LIGHT_1 || baseId == HOLY_SHOCK_1) + { + if (AuraEffect const* favo = me->GetAuraEffect(DIVINE_FAVOR_1, 0)) + if (favo->IsAffectingSpell(spellInfo)) + crit_chance += 100.f; + } + } + + void ApplyClassDamageMultiplierMeleeSpell(int32& damage, SpellNonMeleeDamage& /*damageinfo*/, SpellInfo const* spellInfo, WeaponAttackType /*attackType*/, bool /*iscrit*/) const override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + uint8 lvl = me->GetLevel(); + float fdamage = float(damage); + + //apply bonus damage mods + float pctbonus = 0.0f; + //if (iscrit) + //{ + //} + //Sanctity of Battle: 15% bonus damage for Exorcism and Crusader Strike + if ((GetSpec() == BOT_SPEC_PALADIN_RETRIBUTION) && lvl >= 25 && baseId == EXORCISM_1) + pctbonus += 0.15f; + //The Art of War (damage part): 10% bonus damage for Judgements, Crusader Strike and Divine Storm + if ((GetSpec() == BOT_SPEC_PALADIN_RETRIBUTION) && lvl >= 40 && + (spellInfo->GetCategory() == SPELLCATEGORY_JUDGEMENT || baseId == CRUSADER_STRIKE_1 || baseId == DIVINE_STORM_1)) + pctbonus += 0.1f; + //Judgements of the Pure (damage part): 25% bonus damage for Judgements and Seals + if ((GetSpec() == BOT_SPEC_PALADIN_HOLY) && lvl >= 50 && + (spellInfo->GetCategory() == SPELLCATEGORY_JUDGEMENT || + spellInfo->GetSpellSpecific() == SPELL_SPECIFIC_SEAL || + baseId == JUDGEMENT_OF_COMMAND_DAMAGE)) + pctbonus += 0.25f; + //Glyph of Exorcism: 20% bonus damage for Exorcism + if (lvl >= 50 && baseId == EXORCISM_1) + pctbonus += 0.2f; + + damage = int32(fdamage * (1.0f + pctbonus)); + } + + void ApplyClassDamageMultiplierSpell(int32& damage, SpellNonMeleeDamage& /*damageinfo*/, SpellInfo const* spellInfo, WeaponAttackType /*attackType*/, bool /*iscrit*/) const override + { + uint32 spellId = spellInfo->Id; + uint8 lvl = me->GetLevel(); + float fdamage = float(damage); + + //apply bonus damage mods + float pctbonus = 0.0f; + //if (iscrit) + //{ + //} + + //Judgements of the Pure (damage part): 25% bonus damage for Judgements and Seals + if ((GetSpec() == BOT_SPEC_PALADIN_HOLY) && lvl >= 50 && + (spellInfo->GetCategory() == SPELLCATEGORY_JUDGEMENT || + spellInfo->GetSpellSpecific() == SPELL_SPECIFIC_SEAL || + spellId == JUDGEMENT_OF_COMMAND_DAMAGE)) + pctbonus += 0.25f; + //Improved Consecration (id: 38422): 10% bonus damage for Consecration + if (lvl >= 20 && spellId == GetSpell(CONSECRATION_1)) + pctbonus += 0.1f; + + damage = int32(fdamage * (1.0f + pctbonus)); + } + + void ApplyClassDamageMultiplierHeal(Unit const* /*victim*/, float& heal, SpellInfo const* spellInfo, DamageEffectType /*damagetype*/, uint32 /*stack*/) const override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + uint8 lvl = me->GetLevel(); + float pctbonus = 0.0f; + float flat_mod = 0.0f; + + //Divine Plea: 50% reduced healing for all spells + if (/*lvl >= 71 && */me->GetAuraEffect(SPELL_AURA_OBS_MOD_POWER, SPELLFAMILY_PALADIN, 0x0, 0x80004000, 0x1)) + pctbonus -= 0.5f; + + //Healing Light: 12% bonus healing for Holy Light, Flash of Light and Holy Shock + if ((GetSpec() == BOT_SPEC_PALADIN_HOLY) && lvl >= 15 && (baseId == HOLY_LIGHT_1 || baseId == FLASH_OF_LIGHT_1 || baseId == HOLY_SHOCK_1)) + pctbonus += 0.12f; + //Glyph of Seal of Light: 5% bonus healing for all spells + if (lvl >= 30 && me->GetAuraEffect(SPELL_AURA_ADD_PCT_MODIFIER, SPELLFAMILY_PALADIN, 0x0, 0x2000000, 0x0)) + pctbonus += 0.05f; + + heal = heal * (1.0f + pctbonus) + flat_mod; + } + + void ApplyClassSpellCostMods(SpellInfo const* spellInfo, int32& cost) const override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + uint8 lvl = me->GetLevel(); + float fcost = float(cost); + int32 flatbonus = 0; + float pctbonus = 0.0f; + + //percent mods + //Benediction: -10% mana cost for Instant spells + if (lvl >= 10 && !spellInfo->CalcCastTime()) + pctbonus += 0.1f; + //Blessed Hands: -30% mana cost for Hand spells + if ((GetSpec() == BOT_SPEC_PALADIN_HOLY) && lvl >= 25 && (spellInfo->SpellFamilyFlags[0] & 0x2110)) + pctbonus += 0.3f; + //Holy Light Cost Reduction (id: 60148): -5% mana cost for Holy Light + if (lvl >= 30 && baseId == HOLY_LIGHT_1) + pctbonus += 0.05f; + //Consecration Discount (id: 37180): -15% mana cost for Consecration + if (lvl >= 30 && baseId == CONSECRATION_1) + pctbonus += 0.15f; + //Glyph of Seal of Wisdom: -5% mana cost for all healing spells (for bot it is all spells) + if (lvl >= 15 && me->GetAuraEffect(SPELL_AURA_ADD_PCT_MODIFIER, SPELLFAMILY_PALADIN, 0x0, 0x4000000, 0x0)) + pctbonus += 0.05f; + //Glyph of Shield of Righteous: -80% mana cost for Shield of Righteous + if (lvl >= 75 && (spellInfo->SpellFamilyFlags[1] & 0x100000)) + pctbonus += 0.8f; + + //flat mods + //Cleanse Cost Reduced (id: 27847): -25 mana cost for Cleanse + if (lvl >= 40 && baseId == CLEANSE_1) + flatbonus += 25; + //Reduced Holy Light Cost (id: 37739): -34 mana cost for Holy Light + if (lvl >= 40 && baseId == HOLY_LIGHT_1) + flatbonus += 34; + + //cost can be < 0 + cost = int32(fcost * (1.0f - pctbonus)) - flatbonus; + } + + void ApplyClassSpellCastTimeMods(SpellInfo const* spellInfo, int32& casttime) const override + { + //casttime is in milliseconds + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + uint8 lvl = me->GetLevel(); + int32 timebonus = 0; + //float pctbonus = 0.0f; + + //100% mods + //Glyph of Turn Evil: -100% cast time for Turn Evil + if (lvl >= 24 && baseId == TURN_EVIL_1) + timebonus += casttime; + if (baseId == FLASH_OF_LIGHT_1 || baseId == EXORCISM_1) + { + //The Art of War + AuraEffect const* arto = me->GetAuraEffect(THE_ART_OF_WAR_BUFF, 0); + //Infusion of Light + AuraEffect const* infu = me->GetAuraEffect(INFUSION_OF_LIGHT_BUFF, 1); + if (arto && arto->IsAffectingSpell(spellInfo)) + timebonus += casttime; + else if (infu && infu->IsAffectingSpell(spellInfo)) + timebonus += casttime; + } + + //flat mods + //Improved Holy Light (id: 24457): -0.1 sec cast time for Holy Light + if (lvl >= 40 && baseId == HOLY_LIGHT_1) + timebonus += 100; + //Recuced Holy Light Cast Time (id: 37189): -0.5 sec cast time for Holy Light (works only for healers) + //Light's Grace: -0.5 sec cast time for Holy Light + if (baseId == HOLY_LIGHT_1) + { + if (AuraEffect const* enli = me->GetAuraEffect(ENLIGHTENMENT_BUFF, 0)) + if (enli->IsAffectingSpell(spellInfo)) + timebonus += 500; + if (AuraEffect const* grac = me->GetAuraEffect(LIGHTS_GRACE_BUFF, 0)) + if (grac->IsAffectingSpell(spellInfo)) + timebonus += 500; + } + + casttime = std::max(casttime - timebonus, 0); + } + + void ApplyClassSpellCooldownMods(SpellInfo const* spellInfo, uint32& cooldown) const override + { + //cooldown is in milliseconds + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + uint8 lvl = me->GetLevel(); + int32 timebonus = 0; + float pctbonus = 0.0f; + + //pct mods + + //flat mods + //Improved Judgements: -2 sec cooldown for judgements + //Judgment Cooldown Reduction (60153): -1 sec cooldown for judgements + //Judgement Cooldown Reduction (61776): -1 sec cooldown for judgements + if (spellInfo->GetCategory() == SPELLCATEGORY_JUDGEMENT) + { + if (lvl >= 70) + timebonus += 4000; + else if (lvl >= 60) + timebonus += 3000; + else if (lvl >= 15) + timebonus += 2000; + } + //Sacred Duty: -60 sec cooldown for Divine Shield and Divine Protection + if ((GetSpec() == BOT_SPEC_PALADIN_PROTECTION) && lvl >= 35 && (baseId == DIVINE_SHIELD_1 || baseId == DIVINE_PROTECTION_1)) + timebonus += 60000; + //Reduced Righteous Defense Cooldown (37181): -2 sec cooldown for Righteous Defense + if (lvl >= 60 && baseId == RIGHTEOUS_DEFENSE_1) + timebonus += 2000; + //Paladin T9 Tank 2P Bonus part 1: -2 sec cooldown for Hand of Reckoning + if (lvl >= 78 && baseId == HAND_OF_RECKONING_1) + timebonus += 2000; + //Glyph of Turn Evil: +8 sec cooldown for Turn Evil + if (lvl >= 24 && baseId == TURN_EVIL_1) + timebonus -= 8000; + + cooldown = std::max((float(cooldown) * (1.0f - pctbonus)) - timebonus, 0); + } + + void ApplyClassSpellCategoryCooldownMods(SpellInfo const* spellInfo, uint32& cooldown) const override + { + //cooldown is in milliseconds + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + uint8 lvl = me->GetLevel(); + int32 timebonus = 0; + float pctbonus = 0.0f; + + //pct mods + //Purifying Power part 2: -33% cooldown for Exorcism and Holy Wrath + if ((GetSpec() == BOT_SPEC_PALADIN_HOLY) && lvl >= 35 && (baseId == EXORCISM_1 || baseId == HOLY_WRATH_1)) + pctbonus += 0.333f; + //Glyph of Avenging Wrath: -50% cooldown for Hammer of Wrath if Avenging Wrath is active + if (lvl >= 70 && baseId == HAMMER_OF_WRATH_1 && + me->GetAuraEffect(SPELL_AURA_MOD_HEALING_DONE_PERCENT, SPELLFAMILY_PALADIN, 0x0, 0x2000, 0x0)) + pctbonus += 0.5f; + + //flat mods + //Improved Judgements: -2 sec cooldown for judgements + //Judgment Cooldown Reduction (60153): -1 sec cooldown for judgements + //Judgement Cooldown Reduction (61776): -1 sec cooldown for judgements + if (spellInfo->GetCategory() == SPELLCATEGORY_JUDGEMENT) + { + if (lvl >= 70) + timebonus += 4000; + else if (lvl >= 60) + timebonus += 3000; + else if (lvl >= 15) + timebonus += 2000; + } + //Guardian's Favor part 1: -120 sec cooldown for Hand of Protection + if (lvl >= 15 && baseId == HAND_OF_PROTECTION_1) + timebonus += 120000; + //Improved Hammer of Justice: -20 sec cooldown for Hammer of Justice + if ((GetSpec() == BOT_SPEC_PALADIN_PROTECTION) && lvl >= 25 && baseId == HAMMER_OF_JUSTICE_1) + timebonus += 20000; + //Judgements of the Just: -10 sec cooldown for Hammer of Justice (tanks only) + if ((GetSpec() == BOT_SPEC_PALADIN_PROTECTION) && lvl >= 55 && baseId == HAMMER_OF_JUSTICE_1) + timebonus += 10000; + //Glyph of Holy Shock: -1 sec cooldown for Holy Shock + if (baseId == HOLY_SHOCK_1) + timebonus += 1000; + //Glyph of Consecration: +2 sec cooldown for Consecration + if (lvl >= 20 && baseId == CONSECRATION_1) + timebonus -= 2000; + //Glyph of Holy Wrath: -15 sec cooldown for Holy Wrath + if (lvl >= 50 && baseId == HOLY_WRATH_1) + timebonus += 15000; + //Improved Lay on Hands (part 2): -4 min cooldown for Lay on Hands + if (lvl >= 20 && baseId == LAY_ON_HANDS_1) + timebonus += 240000; + //Glyph of Lay on Hands: -5 min cooldown for Lay on Hands (only healers) + if (lvl >= 15 && HasRole(BOT_ROLE_HEAL) && baseId == LAY_ON_HANDS_1) + timebonus += 300000; + //Lay Hands (id: 28774): -4 min cooldown for Lay on Hands (only healers) + if (lvl >= 60 && HasRole(BOT_ROLE_HEAL) && baseId == LAY_ON_HANDS_1) + timebonus += 240000; + + cooldown = std::max((float(cooldown) * (1.0f - pctbonus)) - timebonus, 0); + } + + void ApplyClassSpellGlobalCooldownMods(SpellInfo const* /*spellInfo*/, float& cooldown) const override + { + //cooldown is in milliseconds + //uint32 spellId = spellInfo->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + //uint8 lvl = me->GetLevel(); + float timebonus = 0.0f; + float pctbonus = 0.0f; + + ////Unrelenting Assault (part 1, special): -0.5 sec global cooldown for Overpower and Revenge (not for tanks) + //if (lvl >= 50 && !IsTank() && (spellId == GetSpell(OVERPOWER_1) || spellId == GetSpell(REVENGE_1))) + // timebonus += 500.f; + + cooldown = (cooldown * (1.0f - pctbonus)) - timebonus; + } + + void ApplyClassSpellRadiusMods(SpellInfo const* spellInfo, float& radius) const override + { + //uint32 spellId = spellInfo->Id; + //uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + uint8 lvl = me->GetLevel(); + float flatbonus = 0.0f; + float pctbonus = 0.0f; + + //pct mods + ////Holy Reach + //if (lvl >= 25 && ((spellInfo->SpellFamilyFlags[0] & 0x18400200) || (spellInfo->SpellFamilyFlags[2] & 0x4))) + // pctbonus += 0.2f; + + //flat mods + //Increased Aura Radii (23565) + if (lvl >= 40 && (spellInfo->SpellFamilyFlags[0] & 0x4020048)) + flatbonus += 10.f; + + radius = radius * (1.0f + pctbonus) + flatbonus; + } + + void ApplyClassSpellRangeMods(SpellInfo const* spellInfo, float& maxrange) const override + { + //uint32 spellId = spellInfo->Id; + //uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + uint8 lvl = me->GetLevel(); + float flatbonus = 0.0f; + float pctbonus = 0.0f; + + //pct mods + //Booming Voice + //if (lvl >= 10 && ((spellInfo->SpellFamilyFlags[0] & 0x30000) || (spellInfo->SpellFamilyFlags[1] & 0x80))) + // pctbonus += 1.0f; + + //flat mods + //Enlightened Judgements: +30 yd range for Judgement of Light and Judgement of Wisdom (healers) + if ((GetSpec() == BOT_SPEC_PALADIN_HOLY) && lvl >= 55 && (spellInfo->SpellFamilyFlags[0] & 0x800000)) + flatbonus += 30.f; + + maxrange = maxrange * (1.0f + pctbonus) + flatbonus; + } + + void ApplyClassSpellMaxTargetsMods(SpellInfo const* spellInfo, uint32& targets) const override + { + uint32 bonusTargets = 0; + + //Glyph of Hammer of the Righteous: +1 target + if (spellInfo->SpellFamilyFlags[1] & 0x40000) + bonusTargets += 1; + + targets = targets + bonusTargets; + } + + void ApplyClassEffectMods(SpellInfo const* spellInfo, uint8 effIndex, float& value) const override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + uint8 lvl = me->GetLevel(); + float pctbonus = 1.0f; + + //Improved Devotion Aura: 50% increased effect + if (baseId == DEVOTION_AURA_1 && effIndex == EFFECT_0 && GetSpec() == BOT_SPEC_PALADIN_PROTECTION && lvl >= 25) + pctbonus *= 1.5f; + //Improved Devotion Aura: 6% bonus healing + if (baseId == IMPROVED_DEVOTION_AURA_SPELL && effIndex == EFFECT_1 && GetSpec() == BOT_SPEC_PALADIN_PROTECTION && lvl >= 25) + value += 6.f; + + value = value * pctbonus; + } + + void OnClassSpellGo(SpellInfo const* spellInfo) override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + + if (baseId == HOLY_LIGHT_1 || baseId == FLASH_OF_LIGHT_1 || baseId == HOLY_SHOCK_1) + { + if (AuraEffect const* favo = me->GetAuraEffect(DIVINE_FAVOR_1, 0)) + if (favo->IsAffectingSpell(spellInfo)) + me->RemoveAurasDueToSpell(DIVINE_FAVOR_1); + } + + if (baseId == HOLY_LIGHT_1) + { + if (AuraEffect const* enli = me->GetAuraEffect(ENLIGHTENMENT_BUFF, 0)) + if (enli->IsAffectingSpell(spellInfo)) + me->RemoveAurasDueToSpell(ENLIGHTENMENT_BUFF); + if (AuraEffect const* grac = me->GetAuraEffect(LIGHTS_GRACE_BUFF, 0)) + if (grac->IsAffectingSpell(spellInfo)) + me->RemoveAurasDueToSpell(LIGHTS_GRACE_BUFF); + if (AuraEffect const* infu = me->GetAuraEffect(INFUSION_OF_LIGHT_BUFF, 0)) + if (infu->IsAffectingSpell(spellInfo)) + me->RemoveAurasDueToSpell(INFUSION_OF_LIGHT_BUFF); + } + + if (baseId == EXORCISM_1 || baseId == FLASH_OF_LIGHT_1) + { + //Infusion of Light takes priority since AoW affects Exorcism too + AuraEffect const* infu = me->GetAuraEffect(INFUSION_OF_LIGHT_BUFF, 1); + //The Art of War + AuraEffect const* arto = me->GetAuraEffect(THE_ART_OF_WAR_BUFF, 0); + if (arto && arto->IsAffectingSpell(spellInfo)) + me->RemoveAurasDueToSpell(THE_ART_OF_WAR_BUFF); + else if (infu && infu->IsAffectingSpell(spellInfo)) + me->RemoveAurasDueToSpell(INFUSION_OF_LIGHT_BUFF); + } + + if (baseId == DIVINE_SACRIFICE_1) + { + _sacDamage = 0; + } + } + + void SpellHitTarget(WorldObject* wtarget, SpellInfo const* spell) override + { + Unit* target = wtarget->ToUnit(); + if (!target) + return; + + uint32 spellId = spell->Id; + uint32 baseId = spell->GetFirstRankSpell()->Id; + uint8 lvl = me->GetLevel(); + + //Glyph of Beacon of Light: 30 sec increased duration + if (baseId == BEACON_OF_LIGHT_1) + { + Aura* beac = target->GetAura(spellId, me->GetGUID()); + if (beac) + { + uint32 dur = beac->GetDuration() + 30000; + beac->SetDuration(dur); + beac->SetMaxDuration(dur); + } + } + //Judgements of the Just melee attack speed reduction part 1 + if ((GetSpec() == BOT_SPEC_PALADIN_PROTECTION) && lvl >= 55 && spell->GetCategory() == SPELLCATEGORY_JUDGEMENT) + { + me->CastSpell(target, JUDGEMENTS_OF_THE_JUST_AURA, true); + } + //Judgements of the Just melee attack speed reduction part 2 + if ((GetSpec() == BOT_SPEC_PALADIN_PROTECTION) && spellId == JUDGEMENTS_OF_THE_JUST_AURA) + { + AuraEffect* slow = target->GetAuraEffect(JUDGEMENTS_OF_THE_JUST_AURA, 1, me->GetGUID()); + if (slow) + slow->ChangeAmount(slow->GetAmount() - 20); + } + + if ((GetSpec() == BOT_SPEC_PALADIN_PROTECTION) && spellId == SEAL_OF_JUSTICE_STUN_AURA) + { + if (lvl >= 55) + { + //Judgements of the Just: 1 sec increased duration + Aura* stun = target->GetAura(spellId, me->GetGUID()); + if (stun) + { + uint32 dur = stun->GetDuration() + 1000; + stun->SetDuration(dur); + stun->SetMaxDuration(dur); + } + } + } + if (baseId == CONSECRATION_1) + { + if (lvl >= 30) + { + //Glyph of Consecration: 2 sec increased duration + Aura* cons = target->GetAura(spellId, me->GetGUID()); + if (cons) + { + uint32 dur = cons->GetDuration() + 2000; + cons->SetDuration(dur); + cons->SetMaxDuration(dur); + } + } + } + if ((GetSpec() == BOT_SPEC_PALADIN_RETRIBUTION) && baseId == RETRIBUTION_AURA_1) + { + if (lvl >= 30) + { + //Sanctified Retribution: 50% increased effect + AuraEffect* eff = target->GetAuraEffect(spellId, EFFECT_0, me->GetGUID()); + if (eff) + eff->ChangeAmount(eff->GetAmount() * 3 / 2); + } + } + if ((GetSpec() == BOT_SPEC_PALADIN_HOLY) && baseId == CONCENTRATION_AURA_1) + { + if (lvl >= 25) + { + //Improved Concentration Aura: 15% increased effect (flat) + AuraEffect* eff = target->GetAuraEffect(spellId, EFFECT_0, me->GetGUID()); + if (eff) + eff->ChangeAmount(eff->GetAmount() + 15); //base = 35, bonus = 15 + } + } + if (baseId == FLASH_OF_LIGHT_HEAL_PERIODIC) + { + if ((GetSpec() == BOT_SPEC_PALADIN_HOLY) && lvl >= 78 && !HasRole(BOT_ROLE_TANK | BOT_ROLE_DPS)) + { + //Paldin T9 Holy 4P Bonus: 100% increased healing from Infusion of Light (pure healers only) + AuraEffect* eff = target->GetAuraEffect(spellId, EFFECT_0, me->GetGUID()); + if (eff) + eff->ChangeAmount(eff->GetAmount() * 2); + } + } + if (baseId == BLESSING_OF_WISDOM_1) + { + if (lvl >= 25) + { + //Improved Blessing of Wisdom: 20% increased effect + AuraEffect* eff = target->GetAuraEffect(spellId, EFFECT_0, me->GetGUID()); + if (eff) + eff->ChangeAmount(eff->GetAmount() * 6 / 5); + } + } + if (baseId == BLESSING_OF_MIGHT_1) + { + if (lvl >= 15) + { + //Improved Blessing of Might: 25% increased effect + if (Aura* migh = target->GetAura(spellId, me->GetGUID())) + for (uint8 i = 0; i != EFFECT_2; ++i) // 2 effects + if (AuraEffect* eff = migh->GetEffect(i)) + eff->ChangeAmount((eff->GetAmount() * 125) / 100); + } + } + if (baseId == HAND_OF_FREEDOM_1) + { + //Guardian's Favor part 2 (handled separately) + if (Aura* hof = target->GetAura(spellId, me->GetGUID())) + { + uint32 dur = hof->GetDuration() + 4000; + hof->SetDuration(dur); + hof->SetMaxDuration(dur); + } + } + if ((GetSpec() == BOT_SPEC_PALADIN_HOLY) && baseId == HAND_OF_SALVATION_1 && !IsTank(target)) + { + //Blessed Hands (part 2) + if (AuraEffect* hos = target->GetAuraEffect(spellId, 0, me->GetGUID())) + { + hos->ChangeAmount(hos->GetAmount() * 2); + } + } + if ((GetSpec() == BOT_SPEC_PALADIN_HOLY) && baseId == HAND_OF_SACRIFICE_1) + { + //Blessed Hands (part 3) + if (AuraEffect* hos = target->GetAuraEffect(spellId, 0, me->GetGUID())) + { + hos->ChangeAmount(hos->GetAmount() + 10); + } + } + if (baseId == BLESSING_OF_KINGS_1 || baseId == BLESSING_OF_MIGHT_1 || + baseId == BLESSING_OF_WISDOM_1 || baseId == BLESSING_OF_SANCTUARY_1) + { + //Blessings duration 1h + if (Aura* bless = target->GetAura(spellId, me->GetGUID())) + { + uint32 dur = HOUR * IN_MILLISECONDS; + bless->SetDuration(dur); + bless->SetMaxDuration(dur); + } + } + if (baseId == SACRED_SHIELD_AURA_TRIGGERED || baseId == SACRED_SHIELD_1) + { + //Divine Guardian (part 2): 20% increased absorb, +100% duration + Aura* shi = target->GetAura(spellId, me->GetGUID()); + if (shi) + { + uint32 dur = shi->GetDuration() * 2; + shi->SetDuration(dur); + shi->SetMaxDuration(dur); + if (baseId == SACRED_SHIELD_AURA_TRIGGERED) + { + if (AuraEffect* eff = shi->GetEffect(EFFECT_0)) + eff->ChangeAmount(eff->GetAmount() * 6 / 5); + } + } + } + + OnSpellHitTarget(target, spell); + } + + void SpellHit(WorldObject* wcaster, SpellInfo const* spell) override + { + Unit* caster = wcaster->ToUnit(); + if (!caster) + return; + + uint32 baseId = spell->GetFirstRankSpell()->Id; + + //Glyph of Seal of Vengeance + if (baseId == SEAL_OF_VENGEANCE_1 || baseId == SEAL_OF_CORRUPTION_1) + { + AuraEffect* sea = me->GetAuraEffect(spell->Id, 1); + if (sea) + sea->ChangeAmount(sea->GetAmount() + 10); + } + + //Aura Helper + if (caster == me) + { + if (baseId == DEVOTION_AURA_1) + _aura = DEVOTIONAURA; + if (baseId == CONCENTRATION_AURA_1) + _aura = CONCENTRATIONAURA; + if (baseId == FIRE_RESISTANCE_AURA_1) + _aura = FIRERESAURA; + if (baseId == FROST_RESISTANCE_AURA_1) + _aura = FROSTRESAURA; + if (baseId == SHADOW_RESISTANCE_AURA_1) + _aura = SHADOWRESAURA; + if (baseId == RETRIBUTION_AURA_1) + _aura = RETRIBUTIONAURA; + if (baseId == CRUSADER_AURA_1) + _aura = CRUSADERAURA; + } + + //immunity markers + if (baseId == AVENGING_WRATH_MARKER_SPELL) + avDelayTimer = 30000; + if (baseId == IMMUNITY_SHIELD_MARKER_SPELL) + shieldDelayTimer = 30000; + + OnSpellHit(caster, spell); + } + + void DamageDealt(Unit* victim, uint32& damage, DamageEffectType damageType) override + { + bot_ai::DamageDealt(victim, damage, damageType); + } + + void OnBotDamageTaken(Unit* /*attacker*/, uint32 damage, CleanDamage const* /*cleanDamage*/, DamageEffectType /*damagetype*/, SpellInfo const* spellInfo) override + { + // Divine Sacrifice helper - calculate remaining damage amount and find if we can be one-shot'ed + if (damage && _sacDamage < int32(me->GetMaxHealth() / 4)) + { + if (spellInfo && spellInfo->Id == DIVINE_SACRIFICE_1) + _sacDamage -= int32(damage); + else + _sacDamage += int32(damage); + + if (me->GetHealth() - _sacDamage < me->GetMaxHealth() / 5) + { + if (me->GetAuraEffect(SPELL_AURA_SPLIT_DAMAGE_PCT, SPELLFAMILY_PALADIN, 0x0, 0x0, 0x4, me->GetGUID())) + { + _sacDamage = me->GetMaxHealth(); + me->RemoveAurasDueToSpell(DIVINE_SACRIFICE_1, me->GetGUID()); + } + } + } + } + + void DamageTaken(Unit* u, uint32& /*damage*/, DamageEffectType /*damageType*/, SpellInfo const* /*spellInfo*/) override + { + if (!u) + return; + if (!u->IsInCombat() && !me->IsInCombat()) + return; + OnOwnerDamagedBy(u); + } + + //healer may be nullptr + void HealReceived(Unit* healer, uint32& heal) override + { + //Spiritual Attunement (double the effect on bots) + if (heal && (GetSpec() == BOT_SPEC_PALADIN_PROTECTION) && me->GetLevel() >= 40 && healer != me && GetLostHP(me)) + { + if (int32 basepoints = int32(CalculatePct(std::min(heal, GetLostHP(me)), 20))) + { + CastSpellExtraArgs args(true); + args.AddSpellBP0(basepoints); + me->CastSpell(me, SPIRITUAL_ATTUNEMENT_ENERGIZE, args); + } + } + + //bot_ai::HealReceived(healer, heal); + } + + void OwnerAttackedBy(Unit* u) override + { + if (!u) + return; + OnOwnerDamagedBy(u); + } + + float GetSpellAttackRange(bool longRange) const override + { + return longRange ? CalcSpellMaxRange(GetSpell(EXORCISM_1) ? EXORCISM_1 : JUDGEMENT_OF_LIGHT_1) : 10.f; + } + + void Reset() override + { + checkAuraTimer = 0; + checkSealTimer = 0; + checkShieldTimer = 0; + checkBeaconTimer = 0; + avDelayTimer = 0; + shieldDelayTimer = 0; + _aura = NOAURA; + _sacDamage = 0; + + CLEANSE = 0; + + DefaultInit(); + } + + void ReduceCD(uint32 diff) override + { + if (checkAuraTimer > diff) checkAuraTimer -= diff; + if (checkSealTimer > diff) checkSealTimer -= diff; + if (checkShieldTimer > diff) checkShieldTimer -= diff; + if (checkBeaconTimer > diff) checkBeaconTimer -= diff; + if (avDelayTimer > diff) avDelayTimer -= diff; + if (shieldDelayTimer > diff) shieldDelayTimer -= diff; + } + + void InitPowers() override + { + me->SetPowerType(POWER_MANA); + } + + void InitSpells() override + { + uint8 lvl = me->GetLevel(); + bool isHoly = GetSpec() == BOT_SPEC_PALADIN_HOLY; + bool isProt = GetSpec() == BOT_SPEC_PALADIN_PROTECTION; + bool isRetr = GetSpec() == BOT_SPEC_PALADIN_RETRIBUTION; + + InitSpellMap(FLASH_OF_LIGHT_1); + InitSpellMap(HOLY_LIGHT_1); + InitSpellMap(LAY_ON_HANDS_1); + InitSpellMap(SACRED_SHIELD_1); + InitSpellMap(REDEMPTION_1); + InitSpellMap(HAMMER_OF_JUSTICE_1); + InitSpellMap(TURN_EVIL_1); + InitSpellMap(HOLY_WRATH_1); + InitSpellMap(EXORCISM_1); + InitSpellMap(JUDGEMENT_OF_LIGHT_1); + InitSpellMap(JUDGEMENT_OF_WISDOM_1); + InitSpellMap(JUDGEMENT_OF_JUSTICE_1); + InitSpellMap(CONSECRATION_1); + InitSpellMap(HAMMER_OF_WRATH_1); + InitSpellMap(AVENGING_WRATH_1); + InitSpellMap(RIGHTEOUS_FURY_1); + InitSpellMap(SHIELD_OF_RIGHTEOUSNESS_1); + InitSpellMap(BLESSING_OF_MIGHT_1); + InitSpellMap(BLESSING_OF_WISDOM_1); + InitSpellMap(BLESSING_OF_KINGS_1); + InitSpellMap(DEVOTION_AURA_1); + InitSpellMap(CONCENTRATION_AURA_1); + InitSpellMap(FIRE_RESISTANCE_AURA_1); + InitSpellMap(FROST_RESISTANCE_AURA_1); + InitSpellMap(SHADOW_RESISTANCE_AURA_1); + InitSpellMap(RETRIBUTION_AURA_1); + InitSpellMap(CRUSADER_AURA_1); + InitSpellMap(DIVINE_PLEA_1); + InitSpellMap(HAND_OF_PROTECTION_1); + InitSpellMap(HAND_OF_FREEDOM_1); + InitSpellMap(HAND_OF_SALVATION_1); + InitSpellMap(HAND_OF_SACRIFICE_1); + InitSpellMap(HAND_OF_RECKONING_1); + InitSpellMap(RIGHTEOUS_DEFENSE_1); + //InitSpellMap(PURIFY_1); + //InitSpellMap(CLEANSE_1); + InitSpellMap(SEAL_OF_LIGHT_1); + InitSpellMap(SEAL_OF_RIGHTEOUSNESS_1); + InitSpellMap(SEAL_OF_WISDOM_1); + InitSpellMap(SEAL_OF_JUSTICE_1); + InitSpellMap((me->GetRaceMask() & RACEMASK_ALLIANCE) ? SEAL_OF_VENGEANCE_1 : SEAL_OF_CORRUPTION_1); + InitSpellMap(DIVINE_INTERVENTION_1); + InitSpellMap(DIVINE_PROTECTION_1); + InitSpellMap(DIVINE_SHIELD_1); + + /*Talent*/lvl >= (isHoly ? 20 : 70) ? InitSpellMap(AURA_MASTERY_1) : RemoveSpell(AURA_MASTERY_1); + /*Talent*/lvl >= 30 && isHoly ? InitSpellMap(DIVINE_FAVOR_1) : RemoveSpell(DIVINE_FAVOR_1); + /*Talent*/lvl >= 40 && isHoly ? InitSpellMap(HOLY_SHOCK_1) : RemoveSpell(HOLY_SHOCK_1); + /*Talent*/lvl >= 50 && isHoly ? InitSpellMap(DIVINE_ILLUMINATION_1) : RemoveSpell(DIVINE_ILLUMINATION_1); + /*Talent*/lvl >= 60 && isHoly ? InitSpellMap(BEACON_OF_LIGHT_1) : RemoveSpell(BEACON_OF_LIGHT_1); + + /*Talent*/lvl >= (isProt ? 20 : isHoly ? 70 : 99) ? InitSpellMap(DIVINE_SACRIFICE_1) : RemoveSpell(DIVINE_SACRIFICE_1); + /*Talent*/lvl >= 30 && isProt ? InitSpellMap(BLESSING_OF_SANCTUARY_1) : RemoveSpell(BLESSING_OF_SANCTUARY_1); + /*Talent*/lvl >= 40 && isProt ? InitSpellMap(HOLY_SHIELD_1) : RemoveSpell(HOLY_SHIELD_1); + /*Talent*/lvl >= 50 && isProt ? InitSpellMap(AVENGERS_SHIELD_1) : RemoveSpell(AVENGERS_SHIELD_1); + /*Talent*/lvl >= 60 && isProt ? InitSpellMap(HAMMER_OF_THE_RIGHTEOUS_1) : RemoveSpell(HAMMER_OF_THE_RIGHTEOUS_1); + + /*Talent*/lvl >= 20 && isRetr ? InitSpellMap(SEAL_OF_COMMAND_1) : RemoveSpell(SEAL_OF_COMMAND_1); + /*Talent*/lvl >= 40 && isRetr ? InitSpellMap(REPENTANCE_1) : RemoveSpell(REPENTANCE_1); + /*Talent*/lvl >= 50 && isRetr ? InitSpellMap(CRUSADER_STRIKE_1) : RemoveSpell(CRUSADER_STRIKE_1); + /*Talent*/lvl >= 60 && isRetr ? InitSpellMap(DIVINE_STORM_1) : RemoveSpell(DIVINE_STORM_1); + + CLEANSE = InitSpell(me, CLEANSE_1) ? CLEANSE_1 : PURIFY_1; + RemoveSpell(CLEANSE_1); + RemoveSpell(PURIFY_1); + InitSpellMap(CLEANSE); + } + + void ApplyClassPassives() const override + { + uint8 level = master->GetLevel(); + bool isHoly = GetSpec() == BOT_SPEC_PALADIN_HOLY; + bool isProt = GetSpec() == BOT_SPEC_PALADIN_PROTECTION; + bool isRetr = GetSpec() == BOT_SPEC_PALADIN_RETRIBUTION; + + RefreshAura(ILLUMINATION, isHoly && level >= 20 ? 1 : 0); + RefreshAura(IMPROVED_LAY_ON_HANDS, isHoly && level >= 20 ? 1 : 0); + RefreshAura(IMPROVED_CONCENTRATION_AURA, isHoly && level >= 25 ? 1 : 0); + RefreshAura(LIGHTS_GRACE, isHoly && level >= 40 ? 1 : 0); + RefreshAura(SACRED_CLEANSING, isHoly && level >= 45 ? 1 : 0); + RefreshAura(JUDGEMENTS_OF_THE_PURE, isHoly && level >= 50 ? 1 : 0); + RefreshAura(INFUSION_OF_LIGHT, isHoly && level >= 55 ? 1 : 0); + RefreshAura(RECUCED_HOLY_LIGHT_CAST_TIME, isHoly && level >= 60 ? 1 : 0); // + + RefreshAura(IMPROVED_RIGHTEOUS_FURY, isProt && level >= 20 ? 1 : 0); + RefreshAura(IMPROVED_DEVOTION_AURA, isProt && level >= 25 ? 1 : 0); + RefreshAura(DIVINE_GUARDIAN, isProt && level >= 25 ? 1 : 0); + RefreshAura(RECKONING5, isProt && level >= 50 ? 1 : 0); + RefreshAura(RECKONING4, isProt && level >= 45 && level < 50 ? 1 : 0); + RefreshAura(RECKONING3, isProt && level >= 40 && level < 45 ? 1 : 0); + RefreshAura(RECKONING2, isProt && level >= 35 && level < 40 ? 1 : 0); + RefreshAura(RECKONING1, isProt && level >= 30 && level < 35 ? 1 : 0); + RefreshAura(ONE_HANDED_WEAPON_SPECIALIZATION, isProt && level >= 35 ? 1 : 0); + RefreshAura(ARDENT_DEFENDER, isProt && level >= 40 ? 1 : 0); + //RefreshAura(COMBAT_EXPERTISE, isProt && level >= 45 ? 1 : 0); + RefreshAura(REDOUBT3, isProt && level >= 55 ? 1 : 0); + RefreshAura(REDOUBT2, isProt && level >= 50 && level < 55 ? 1 : 0); + RefreshAura(REDOUBT1, isProt && level >= 45 && level < 50 ? 1 : 0); + RefreshAura(GUARDED_BY_THE_LIGHT, isProt && level >= 50 ? 1 : 0); + RefreshAura(TOUCHED_BY_THE_LIGHT, isProt && level >= 50 ? 1 : 0); + RefreshAura(SHIELD_OF_THE_TEMPLAR, isProt && level >= 55 ? 1 : 0); + //RefreshAura(JUDGEMENTS_OF_THE_JUST, isProt && level >= 55 ? 1 : 0); + + RefreshAura(HEART_OF_THE_CRUSADER, isRetr && level >= 15 ? 1 : 0); + RefreshAura(PURSUIT_OF_JUSTICE, isRetr && level >= 20 ? 1 : 0); + RefreshAura(FANATICISM, isRetr && level >= 20 ? 1 : 0); + RefreshAura(VINDICATION2, isRetr && level >= 25 ? 1 : 0); + RefreshAura(VINDICATION1, isRetr && level >= 20 && level < 25 ? 1 : 0); + RefreshAura(CRUSADE, isRetr && level >= 25 ? 1 : 0); + RefreshAura(TWO_HANDED_WEAPON_SPECIALIZATION, isRetr && level >= 30 ? 1 : 0); + RefreshAura(SANCTIFIED_RETRIBUTION, !IAmFree() && isRetr && level >= 30 ? 1 : 0); + RefreshAura(VENGEANCE3, isRetr && level >= 40 ? 1 : 0); + RefreshAura(VENGEANCE2, isRetr && level >= 37 && level < 40 ? 1 : 0); + RefreshAura(VENGEANCE1, isRetr && level >= 35 && level < 37 ? 1 : 0); + RefreshAura(DIVINE_PURPOSE, isRetr && level >= 35 ? 1 : 0); + RefreshAura(JUDGEMENTS_OF_THE_WISE, isRetr && level >= 40 ? 1 : 0); + RefreshAura(ART_OF_WAR, isRetr && level >= 40 ? 1 : 0); + RefreshAura(SWIFT_RETRIBUTION, !IAmFree() && isRetr && level >= 50 ? 1 : 0); + RefreshAura(SHEATH_OF_LIGHT3, isRetr && level >= 60 ? 1 : 0); + RefreshAura(SHEATH_OF_LIGHT2, isRetr && level >= 55 && level < 60 ? 1 : 0); + RefreshAura(SHEATH_OF_LIGHT1, isRetr && level >= 50 && level < 55 ? 1 : 0); + RefreshAura(RIGHTEOUS_VENGEANCE3, isRetr && level >= 60 ? 1 : 0); + RefreshAura(RIGHTEOUS_VENGEANCE2, isRetr && level >= 57 && level < 60 ? 1 : 0); + RefreshAura(RIGHTEOUS_VENGEANCE1, isRetr && level >= 55 && level < 57 ? 1 : 0); + + RefreshAura(GLYPH_HOLY_LIGHT, level >= 15 ? 1 : 0); + RefreshAura(GLYPH_SALVATION, level >= 26 ? 1 : 0); + + RefreshAura(JUDGEMENT_ANTI_PARRY_DODGE_PASSIVE); + + //RefreshAura(CLEANSE_HEAL_PASSIVE, level >= 58 ? 1 : 0); + } + + bool CanUseManually(uint32 basespell) const override + { + switch (basespell) + { + case FLASH_OF_LIGHT_1: + case HOLY_LIGHT_1: + case LAY_ON_HANDS_1: + case HAND_OF_FREEDOM_1: + case SACRED_SHIELD_1: + case CLEANSE_1: + case HAND_OF_PROTECTION_1: + case HAND_OF_SALVATION_1: + case HAND_OF_SACRIFICE_1: + //case SEAL_OF_COMMAND_1: + //case SEAL_OF_LIGHT_1: + //case SEAL_OF_RIGHTEOUSNESS_1: + //case SEAL_OF_WISDOM_1: + //case SEAL_OF_JUSTICE_1: + case DIVINE_PLEA_1: + case AVENGING_WRATH_1: + case BLESSING_OF_MIGHT_1: + case BLESSING_OF_WISDOM_1: + case BLESSING_OF_KINGS_1: + case BLESSING_OF_SANCTUARY_1: + return true; + case HOLY_SHOCK_1: + return HasRole(BOT_ROLE_HEAL); + case DEVOTION_AURA_1: + return _aura != DEVOTIONAURA; + case CONCENTRATION_AURA_1: + return _aura != CONCENTRATIONAURA; + case FIRE_RESISTANCE_AURA_1: + return _aura != FIRERESAURA; + case FROST_RESISTANCE_AURA_1: + return _aura != FROSTRESAURA; + case SHADOW_RESISTANCE_AURA_1: + return _aura != SHADOWRESAURA; + case RETRIBUTION_AURA_1: + return _aura != RETRIBUTIONAURA; + case CRUSADER_AURA_1: + return _aura != CRUSADERAURA; + case PURIFY_1: + return !GetSpell(CLEANSE_1); + default: + return false; + } + } + + bool HasAbilitiesSpecifics() const override { return true; } + void FillAbilitiesSpecifics(Player const* player, std::list &specList) override + { + uint32 textId; + switch (_aura) + { + case DEVOTIONAURA: textId = BOT_TEXT_DEVOTION; break; + case CONCENTRATIONAURA: textId = BOT_TEXT_CONCENTRATION; break; + case FIRERESAURA: textId = BOT_TEXT_FIRERESISTANCE; break; + case FROSTRESAURA: textId = BOT_TEXT_FROSTRESISTANCE; break; + case SHADOWRESAURA: textId = BOT_TEXT_SHADOWRESISTANCE; break; + case RETRIBUTIONAURA: textId = BOT_TEXT_RETRIBUTION; break; + case CRUSADERAURA: textId = BOT_TEXT_CRUSADER; break; + case NOAURA: default: textId = BOT_TEXT_NOAURA; break; + } + specList.push_back(LocalizedNpcText(player, BOT_TEXT_AURA) + ": " + LocalizedNpcText(player, textId)); + } + + std::vector const* GetDamagingSpellsList() const override + { + return &Paladin_spells_damage; + } + std::vector const* GetCCSpellsList() const override + { + return &Paladin_spells_cc; + } + std::vector const* GetHealingSpellsList() const override + { + return &Paladin_spells_heal; + } + std::vector const* GetSupportSpellsList() const override + { + return &Paladin_spells_support; + } + + void InitHeals() override + { + SpellInfo const* spellInfo; + if (InitSpell(me, HOLY_SHOCK_HEAL_1)) + { + spellInfo = sSpellMgr->GetSpellInfo(InitSpell(me, HOLY_SHOCK_HEAL_1)); + _heals[HOLY_SHOCK_1] = me->SpellHealingBonusDone(me, spellInfo, spellInfo->_effects[0].CalcValue(me), HEAL, spellInfo->GetEffect(EFFECT_0), {}); + } + else + _heals[HOLY_SHOCK_1] = 0; + + if (InitSpell(me, HOLY_LIGHT_1)) + { + spellInfo = sSpellMgr->GetSpellInfo(InitSpell(me, HOLY_LIGHT_1)); + _heals[HOLY_LIGHT_1] = me->SpellHealingBonusDone(me, spellInfo, spellInfo->_effects[0].CalcValue(me), HEAL, spellInfo->GetEffect(EFFECT_0), {}); + } + else + _heals[HOLY_LIGHT_1] = 0; + + if (InitSpell(me, FLASH_OF_LIGHT_1)) + { + spellInfo = sSpellMgr->GetSpellInfo(InitSpell(me, FLASH_OF_LIGHT_1)); + _heals[FLASH_OF_LIGHT_1] = me->SpellHealingBonusDone(me, spellInfo, spellInfo->_effects[0].CalcValue(me), HEAL, spellInfo->GetEffect(EFFECT_0), {}); + } + else + _heals[FLASH_OF_LIGHT_1] = 0; + } + + private: + //Spells + uint32 CLEANSE; + //Timers +/*misc*/uint32 checkAuraTimer, checkSealTimer, checkShieldTimer, checkBeaconTimer, avDelayTimer, shieldDelayTimer; + //Special +/*misc*/uint8 _aura; +/*misc*/int32 _sacDamage; + + typedef std::unordered_map HealMap; + HealMap _heals; + + //uint32 _getBlessingsMask(Unit const*) const + //Scans target for auras which are related to paladin's blessings + //(even if aura is just incompatible with one) + //returns applied blessings mask + //used for finding out which blessings target lacks + uint32 _getBlessingsMask(Unit const* target) const + { + uint32 mask = 0; + + bool blessing; + Unit::AuraApplicationMap const& aurapps = target->GetAppliedAuras(); + for (Unit::AuraApplicationMap::const_iterator itr = aurapps.begin(); itr != aurapps.end(); ++itr) + { + blessing = true; + switch (itr->second->GetBase()->GetSpellInfo()->GetFirstRankSpell()->Id) + { + case BLESSING_OF_WISDOM_1: + case GREATER_BLESSING_OF_WISDOM_1: + mask |= SPECIFIC_BLESSING_WISDOM; + break; + case BLESSING_OF_KINGS_1: + case GREATER_BLESSING_OF_KINGS_1: + mask |= SPECIFIC_BLESSING_KINGS; + break; + case BLESSING_OF_SANCTUARY_1: + case GREATER_BLESSING_OF_SANCTUARY_1: + mask |= SPECIFIC_BLESSING_SANCTUARY; + break; + case BLESSING_OF_MIGHT_1: + case GREATER_BLESSING_OF_MIGHT_1: + case BATTLESHOUT_1: + mask |= SPECIFIC_BLESSING_MIGHT; + break; + default: + blessing = false; //next aura + break; + } + + if (blessing && itr->second->GetBase()->GetCasterGUID() == me->GetGUID()) + mask |= SPECIFIC_BLESSING_MY_BLESSING; + } + + return mask; + } + //uint32 _getAurasMask(Unit const*) const + //Scans target for paladin's auras + //returns applied auras mask + //used for finding out which auras target lacks + uint32 _getAurasMask(std::map& idMap) const + { + uint32 mask = 0; + + uint32 baseId; + bool isAura; + Unit::AuraApplicationMap const& aurapps = me->GetAppliedAuras(); + for (Unit::AuraApplicationMap::const_iterator itr = aurapps.begin(); itr != aurapps.end(); ++itr) + { + isAura = true; + baseId = itr->second->GetBase()->GetSpellInfo()->GetFirstRankSpell()->Id; + switch (baseId) + { + case DEVOTION_AURA_1: + mask |= SPECIFIC_AURA_DEVOTION; + break; + case CONCENTRATION_AURA_1: + mask |= SPECIFIC_AURA_CONCENTRATION; + break; + case FIRE_RESISTANCE_AURA_1: + mask |= SPECIFIC_AURA_FIRE_RES; + break; + case FROST_RESISTANCE_AURA_1: + mask |= SPECIFIC_AURA_FROST_RES; + break; + case SHADOW_RESISTANCE_AURA_1: + mask |= SPECIFIC_AURA_SHADOW_RES; + break; + case RETRIBUTION_AURA_1: + mask |= SPECIFIC_AURA_RETRIBUTION; + break; + case CRUSADER_AURA_1: + mask |= SPECIFIC_AURA_CRUSADER; + break; + default: + isAura = false; //next aura + break; + } + + if (isAura) + { + idMap[baseId] = itr->first; + if (itr->second->GetBase()->GetCasterGUID() == me->GetGUID()) + mask |= SPECIFIC_AURA_MY_AURA; + } + } + + return mask; + } + }; +}; + +void AddSC_paladin_bot() +{ + new paladin_bot(); +} diff --git a/src/server/game/AI/NpcBots/bot_priest_ai.cpp b/src/server/game/AI/NpcBots/bot_priest_ai.cpp new file mode 100644 index 000000000..be4bb347f --- /dev/null +++ b/src/server/game/AI/NpcBots/bot_priest_ai.cpp @@ -0,0 +1,2011 @@ +#include "bot_ai.h" +#include "botmgr.h" +#include "botspell.h" +#include "bottext.h" +#include "bottraits.h" +#include "Group.h" +#include "Map.h" +#include "MotionMaster.h" +#include "Player.h" +#include "ScriptMgr.h" +#include "Spell.h" +#include "SpellAuraEffects.h" +#include "SpellMgr.h" +#include "TemporarySummon.h" +/* +Priest NpcBot (reworked by Trickerer onlysuffering@gmail.com) +Complete - Around 90% +TODO: Mana Burn, Binding Heal, Lightwell +*/ + +enum PriestBaseSpells +{ + DISPEL_MAGIC_1 = 527, + MASS_DISPEL_1 = 32375, + CURE_DISEASE_1 = 528, + ABOLISH_DISEASE_1 = 552, + FEAR_WARD_1 = 6346, + PAIN_SUPPRESSION_1 = 33206, + PSYCHIC_SCREAM_1 = 8122, + FADE_1 = 586, + PSYCHIC_HORROR_1 = 64044, + SILENCE_1 = 15487, + PENANCE_1 = 47540, + VAMPIRIC_EMBRACE_1 = 15286, + DISPERSION_1 = 47585, + MIND_SEAR_1 = 48045, + GUARDIAN_SPIRIT_1 = 47788, + SHACKLE_UNDEAD_1 = 9484, + LESSER_HEAL_1 = 2050, + NORMAL_HEAL_1 = 2054, + GREATER_HEAL_1 = 2060, + RENEW_1 = 139, + FLASH_HEAL_1 = 2061, + PRAYER_OF_HEALING_1 = 596, + CIRCLE_OF_HEALING_1 = 34861, + DIVINE_HYMN_1 = 64843, + PRAYER_OF_MENDING_1 = 33076, + RESURRECTION_1 = 2006, + PW_SHIELD_1 = 17, + INNER_FIRE_1 = 588, + PW_FORTITUDE_1 = 1243, + SHADOW_PROTECTION_1 = 976, + DIVINE_SPIRIT_1 = 14752, + HOLY_FIRE_1 = 14914, + SMITE_1 = 585, + SW_PAIN_1 = 589, + MIND_BLAST_1 = 8092, + SW_DEATH_1 = 32379, + DEVOURING_PLAGUE_1 = 2944, + MIND_FLAY_1 = 15407, + VAMPIRIC_TOUCH_1 = 34914, + SHADOWFORM_1 = 15473, + INNER_FOCUS_1 = 14751, + DESPERATE_PRAYER_1 = 19236, + POWER_INFUSION_1 = 10060, + HYMN_OF_HOPE_1 = 64901, + + LEVITATE_1 = 1706 +}; +enum PriestPassives +{ +//Talents + UNBREAKABLE_WILL = 14791,//rank 5 + SPIRIT_TAP = 15336,//rank 3 + IMPROVED_SPIRIT_TAP = 15338,//rank 2 + MEDITATION = 14777,//rank 3 + INSPIRATION1 = 14892, + INSPIRATION2 = 15362, + INSPIRATION3 = 15363, + SHADOW_WEAVING1 = 15257, + SHADOW_WEAVING2 = 15331, + SHADOW_WEAVING3 = 15332, + SURGE_OF_LIGHT = 33154,//rank 2 + IMPROVED_DEVOURING_PLAGUE = 63627,//rank 3 + HOLY_CONCENTRATION = 34860,//rank 3 + RENEWED_HOPE = 57472,//rank 3 + RAPTURE = 47537,//rank 3 + BODY_AND_SOUL1 = 64127, + SERENDIPITY = 63737,//rank 3 + IMPROVED_SHADOWFORM = 47570,//rank 2 + MISERY1 = 33191, + MISERY2 = 33192, + MISERY3 = 33193, + DIVINE_AEGIS = 47515,//rank 3 + GRACE = 47517,//rank 2 + EMPOWERED_RENEW1 = 63534, + EMPOWERED_RENEW2 = 63542, + EMPOWERED_RENEW3 = 63543, + BORROWED_TIME = 52800,//rank 5 +//Glyphs + //GLYPH_SW_PAIN = 55681, + GLYPH_PW_SHIELD = 55672, + GLYPH_DISPEL_MAGIC = 55677, + GLYPH_PRAYER_OF_HEALING = 55680, + GLYPH_SHADOW = 55689, +//other + PRIEST_T10_2P_BONUS = 70770 //33% renew +}; +enum PriestSpecial +{ + SHADOW_WEAVING_BUFF = 15258, + MIND_FLAY_DAMAGE = 58381, + MIND_SEAR_DAMAGE_1 = 49821, + SW_DEATH_BACKLASH = 32409, + WEAKENED_SOUL_DEBUFF = 6788, + SURGE_OF_LIGHT_BUFF = 33151, + SERENDIPITY_BUFF = 63734, + DIVINE_HYMN_HEAL = 64844, + PRAYER_OF_MENDING_AURA_1 = 41635, + PRAYER_OF_MENDING_HEAL = 33110, + PENANCE_HEAL_1 = 47750, + IMPROVED_MIND_BLAST_DEBUFF = 48301,//Mind Trauma + HYMN_OF_HOPE_BUFF = 64904, + + SHADOWFIEND_1 = 34433 +}; + +static const uint32 Priest_spells_damage_arr[] = +{ DEVOURING_PLAGUE_1, HOLY_FIRE_1, MIND_BLAST_1, MIND_FLAY_1, MIND_SEAR_1, PENANCE_1, SMITE_1, SW_PAIN_1, SW_DEATH_1, +VAMPIRIC_TOUCH_1 }; + +static const uint32 Priest_spells_cc_arr[] = +{ PSYCHIC_HORROR_1, PSYCHIC_SCREAM_1, SHACKLE_UNDEAD_1, SILENCE_1 }; + +static const uint32 Priest_spells_heal_arr[] = +{ RENEW_1, FLASH_HEAL_1, LESSER_HEAL_1, NORMAL_HEAL_1, GREATER_HEAL_1, PRAYER_OF_HEALING_1, PRAYER_OF_MENDING_1, +GUARDIAN_SPIRIT_1, PENANCE_1, DIVINE_HYMN_1, CIRCLE_OF_HEALING_1, DESPERATE_PRAYER_1 }; + +static const uint32 Priest_spells_support_arr[] = +{ PW_FORTITUDE_1, DIVINE_SPIRIT_1, SHADOW_PROTECTION_1, ABOLISH_DISEASE_1, CURE_DISEASE_1, +DISPEL_MAGIC_1, MASS_DISPEL_1, DISPERSION_1, FADE_1, FEAR_WARD_1, HYMN_OF_HOPE_1, INNER_FIRE_1, INNER_FOCUS_1, +LEVITATE_1, PAIN_SUPPRESSION_1, POWER_INFUSION_1, PW_SHIELD_1, RESURRECTION_1, SHADOWFORM_1, VAMPIRIC_EMBRACE_1 }; + +static const std::vector Priest_spells_damage(FROM_ARRAY(Priest_spells_damage_arr)); +static const std::vector Priest_spells_cc(FROM_ARRAY(Priest_spells_cc_arr)); +static const std::vector Priest_spells_heal(FROM_ARRAY(Priest_spells_heal_arr)); +static const std::vector Priest_spells_support(FROM_ARRAY(Priest_spells_support_arr)); + +class priest_bot : public CreatureScript +{ +public: + priest_bot() : CreatureScript("priest_bot") { } + + CreatureAI* GetAI(Creature* creature) const override + { + return new priest_botAI(creature); + } +/* + bool OnGossipHello(Player* player, Creature* creature) + { + return creature->GetBotAI()->OnGossipHello(player, 0); + } + + bool OnGossipSelect(Player* player, Creature* creature, uint32 sender, uint32 action) + { + if (bot_ai* ai = creature->GetBotAI()) + return ai->OnGossipSelect(player, creature, sender, action); + return true; + } + + bool OnGossipSelectCode(Player* player, Creature* creature, uint32 sender, uint32 action, char const* code) + { + if (bot_ai* ai = creature->GetBotAI()) + return ai->OnGossipSelectCode(player, creature, sender, action, code); + return true; + } +*/ + struct priest_botAI : public bot_ai + { + priest_botAI(Creature* creature) : bot_ai(creature) + { + _botclass = BOT_CLASS_PRIEST; + + InitUnitFlags(); + } + + bool doCast(Unit* victim, uint32 spellId) + { + if (CheckBotCast(victim, spellId) != SPELL_CAST_OK) + return false; + return bot_ai::doCast(victim, spellId); + } + + void CheckHymnOfHope(uint32 diff) + { + if (!IsSpellReady(HYMN_OF_HOPE_1, diff) || Rand() > 45 || IsCasting() || IsTank()) + return; + + Group const* gr = !IAmFree() ? master->GetGroup() : GetGroup(); + if (!gr) + return; + + uint8 LMPcount = 0; + for (Unit const* member : BotMgr::GetAllGroupMembers(gr)) + { + if (me->GetMap() != member->FindMap() || !member->IsAlive() || !member->IsInCombat() || + me->GetDistance(member) > 40 || GetManaPCT(member) > (HasRole(BOT_ROLE_HEAL) ? 10 : 50) || + (member->IsNPCBot() && member->ToCreature()->IsTempBot()) || + member->GetAuraEffect(SPELL_AURA_MOD_INCREASE_ENERGY, SPELLFAMILY_PRIEST, 0x0, 0x0, 0x10)) + continue; + if (++LMPcount > 2) + break; + } + + if (LMPcount > 2 && doCast(me, GetSpell(HYMN_OF_HOPE_1))) + return; + } + + bool MassGroupHeal(uint32 diff) + { + if (!HasRole(BOT_ROLE_HEAL) || IsCasting() || Rand() > (65 + 40 * me->GetMap()->IsRaid())) + return false; + + Group const* gr = !IAmFree() ? master->GetGroup() : GetGroup(); + if (!gr) + return false; + + bool canHymn = IsSpellReady(DIVINE_HYMN_1, diff, false); + bool canPray = !!GetSpell(PRAYER_OF_HEALING_1); + bool canCirc = IsSpellReady(CIRCLE_OF_HEALING_1, diff, false); + + uint8 LHPcount1, LHPcount2, LHPcount3; + LHPcount1 = LHPcount2 = LHPcount3 = 0; + uint8 lowestPCT = 100; + Unit* castTarget = nullptr; + + for (Unit* member : BotMgr::GetAllGroupMembers(gr)) + { + if (me->GetMap() != member->FindMap() || !member->IsAlive() || !member->IsInCombat() || + member->isPossessed() || member->IsCharmed() || (member->IsNPCBot() && member->ToCreature()->IsTempBot()) || + member->GetAuraEffect(SPELL_AURA_MOD_INCREASE_ENERGY, SPELLFAMILY_PRIEST, 0x0, 0x0, 0x10)) + continue; + + float dist = me->GetDistance(member); + uint8 pct = GetHealthPCT(member); + if (canHymn && pct < std::min(80, 50 + member->getAttackers().size()*10) && GetLostHP(member) > 4000 && dist < 40) + { + if (++LHPcount1 > 2) + break; + } + if (canPray && pct < 65 && dist < 36) + { + if (++LHPcount2 > 3) + break; + } + if (canCirc && pct < 85 && dist < 40 && (!castTarget || castTarget->GetDistance(member) < 18)) + { + if (++LHPcount3 > 1) + break; + if (pct < lowestPCT) + { + lowestPCT = pct; + castTarget = member; + } + } + } + + if (LHPcount1 > 2 && doCast(me, GetSpell(DIVINE_HYMN_1))) + return true; + if (LHPcount2 > 3) + { + if (me->IsInCombat() && IsSpellReady(INNER_FOCUS_1, diff) && GetManaPCT(me) < 70 && + doCast(me, GetSpell(INNER_FOCUS_1))) + {} + if (doCast(me, GetSpell(PRAYER_OF_HEALING_1))) + return true; + } + if (LHPcount3 > 1 && castTarget && doCast(castTarget, GetSpell(CIRCLE_OF_HEALING_1))) + return true; + + return false; + } + + bool ShieldGroup(uint32 diff) + { + if (!IsSpellReady(PW_SHIELD_1, false, diff) || IsCasting() || Rand() > 65 + 100 * (me->GetMap()->IsRaid())) + return false; + if (!IAmFree() && !(me->GetLevel() >= 30 && _spec == BOT_SPEC_PRIEST_DISCIPLINE) && + master->GetBotMgr()->HasBotWithSpec(BOT_SPEC_PRIEST_DISCIPLINE)) + return false; + + Group const* gr = !IAmFree() ? master->GetGroup() : GetGroup(); + if (!gr) + { + Unit* u = master; + if (u->IsAlive() && !u->getAttackers().empty() && (IsTank(u) || GetHealthPCT(u) < 75) && me->GetDistance(u) < 40 && + ShieldTarget(u, diff)) + return true; + if (!IAmFree()) + { + BotMap const* map = master->GetBotMgr()->GetBotMap(); + for (BotMap::const_iterator itr = map->begin(); itr != map->end(); ++itr) + { + u = itr->second; + if (u->IsAlive() && !u->getAttackers().empty() && !u->ToCreature()->IsTempBot() && + (IsTank(u) || GetHealthPCT(u) < 75) && me->GetDistance(u) < 40 && + ShieldTarget(u, diff)) + return true; + } + for (Unit::ControlList::const_iterator itr = master->m_Controlled.begin(); itr != master->m_Controlled.end(); ++itr) + { + u = *itr; + if (!u || !u->IsPet() || me->GetMap() != u->FindMap()) + continue; + if (u->IsAlive() && !u->getAttackers().empty() && (IsTank(u) || GetHealthPCT(u) < 75) && me->GetDistance(u) < 40 && + ShieldTarget(u, diff)) + return true; + } + } + } + else + { + std::vector members = BotMgr::GetAllGroupMembers(gr); + for (uint8 i = 0; i < 2; ++i) + { + for (Unit* member : members) + { + if (!(i == 0 ? member->IsPlayer() : member->IsNPCBot()) || me->GetMap() != member->FindMap() || + !member->IsAlive() || me->GetDistance(member) > 40 || member->isPossessed() || member->IsCharmed() || + member->getAttackers().empty() || (!IsTank(member) && GetHealthPCT(member) > 75) || + (member->IsNPCBot() && member->ToCreature()->IsTempBot()) || + member->GetAuraEffect(SPELL_AURA_DUMMY, SPELLFAMILY_PALADIN, 0x0, 0x80000, 0x0)) + continue; + if (ShieldTarget(member, diff)) + return true; + } + } + } + return false; + } + + bool ShieldTarget(Unit* target, uint32 diff) + { + if (!IsSpellReady(PW_SHIELD_1, diff) || IsCasting()) + return false; + if (target->HasAuraTypeWithFamilyFlags(SPELL_AURA_MECHANIC_IMMUNITY, SPELLFAMILY_PRIEST, 0x20000000) || + target->HasAuraTypeWithFamilyFlags(SPELL_AURA_SCHOOL_ABSORB, SPELLFAMILY_PRIEST, 0x1)) + return false; + + if (doCast(target, GetSpell(PW_SHIELD_1))) + return true; + + return false; + } + + void StartAttack(Unit* u, bool force = false) + { + if (!bot_ai::StartAttack(u, force)) + return; + GetInPosition(force, u); + } + + void JustEnteredCombat(Unit* u) override { bot_ai::JustEnteredCombat(u); } + void KilledUnit(Unit* u) override { bot_ai::KilledUnit(u); } + void EnterEvadeMode(EvadeReason why = EVADE_REASON_OTHER) override { bot_ai::EnterEvadeMode(why); } + void MoveInLineOfSight(Unit* u) override { bot_ai::MoveInLineOfSight(u); } + void JustDied(Unit* u) override { UnsummonAll(false); bot_ai::JustDied(u); } + + bool removeShapeshiftForm() override + { + ShapeshiftForm form = me->GetShapeshiftForm(); + if (form != FORM_NONE) + { + switch (form) + { + case FORM_SHADOW: + me->RemoveAurasDueToSpell(SHADOWFORM_1); + break; + default: + break; + } + } + + return true; + } + + void BreakCC(uint32 diff) override + { + //Improved Shadowform: Fade + if (IsSpellReady(FADE_1, diff) && me->GetShapeshiftForm() == FORM_SHADOW && me->GetLevel() >= 45 && + Rand() < 35 && me->HasAuraWithMechanic((1<HasAuraType(SPELL_AURA_MOD_SILENCE))) + DrinkPotion(false); + } + + CheckRacials(diff); + + doDefend(diff); + + if (me->GetMap()->IsRaid()) + { + CureGroup(GetSpell(DISPEL_MAGIC_1), diff); + CureGroup(GetSpell(ABOLISH_DISEASE_1) ? GetSpell(ABOLISH_DISEASE_1) : GetSpell(CURE_DISEASE_1), diff); + MassGroupHeal(diff); + ShieldGroup(diff); + CheckMending(diff); + BuffAndHealGroup(diff); + } + else + { + MassGroupHeal(diff); + ShieldGroup(diff); + CheckMending(diff); + BuffAndHealGroup(diff); + CureGroup(GetSpell(DISPEL_MAGIC_1), diff); + CureGroup(GetSpell(CURE_DISEASE_1), diff); + } + + if (master->IsInCombat() || me->IsInCombat()) + { + CheckSilence(diff); + CheckDispel(diff); + CheckHymnOfHope(diff); + } + + Counter(diff); + + if (me->IsInCombat()) + { + CheckShackles(diff); + CheckPowerInfusion(diff); + } + else + DoNonCombatActions(diff); + + if (IsCasting()) + return; + + if (IsSpellReady(SHADOWFORM_1, diff) && HasRole(BOT_ROLE_DPS) && !HasRole(BOT_ROLE_HEAL)) + { + if (doCast(me, SHADOWFORM_1)) + return; + } + + if (ProcessImmediateNonAttackTarget()) + return; + + if (!CheckAttackTarget()) + return; + + CheckUsableItems(diff); + + Attack(diff); + } + + void Attack(uint32 diff) + { + Unit* mytar = opponent ? opponent : disttarget ? disttarget : nullptr; + if (!mytar) + return; + + StartAttack(mytar, IsMelee()); + + CheckAttackState(); + if (!me->IsAlive() || !mytar->IsAlive()) + return; + + MoveBehind(mytar); + + if (GC_Timer > diff) + return; + + //shadow skills range + if (me->GetDistance(mytar) > CalcSpellMaxRange(MIND_FLAY_1)) + return; + + auto [can_do_shadow, can_do_holy] = CanAffectVictimBools(mytar, SPELL_SCHOOL_SHADOW, SPELL_SCHOOL_HOLY); + + if (IsSpellReady(PSYCHIC_HORROR_1, diff) && can_do_shadow && Rand() < 20 && + mytar->GetHealth() > me->GetMaxHealth()/8 && !CCed(mytar) && + !mytar->HasAuraType(SPELL_AURA_MOD_DISARM) && + (mytar->GetTypeId() == TYPEID_PLAYER ? + mytar->ToPlayer()->GetWeaponForAttack(BASE_ATTACK) && mytar->ToPlayer()->IsUseEquipedWeapon(true) : + mytar->GetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID) && mytar->CanUseAttackType(BASE_ATTACK))) + { + if (doCast(mytar, GetSpell(PSYCHIC_HORROR_1))) + return; + } + + //spell reflections + if (IsSpellReady(SW_PAIN_1, diff) && can_do_shadow && CanRemoveReflectSpells(mytar, SW_PAIN_1) && + doCast(mytar, SW_PAIN_1)) //yes, using rank 1 + return; + + if (!HasRole(BOT_ROLE_DPS)) + return; + + if (IsSpellReady(SHADOWFIEND_1, diff) && GetManaPCT(me) < 50) + { + SummonBotPet(mytar); + SetSpellCooldown(SHADOWFIEND_1, 180000); // (5 - 2) min with Veiled Shadows + return; + } + + if (!HasRole(BOT_ROLE_HEAL) || GetManaPCT(me) > 35 || botPet) + { + if (IsSpellReady(SW_DEATH_1, diff) && can_do_shadow && Rand() < 90 && GetHealthPCT(me) > 50 && + (me->GetMap()->IsRaid() || GetHealthPCT(mytar) < 15 || mytar->GetHealth() < me->GetMaxHealth()/8) && + doCast(mytar, GetSpell(SW_DEATH_1))) + return; + if (IsSpellReady(VAMPIRIC_TOUCH_1, diff) && can_do_shadow && Rand() < 80 && + mytar->GetHealth() > me->GetMaxHealth()/4 * (1 + mytar->getAttackers().size()) && + !mytar->GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_PRIEST, 0x0, 0x400, 0x0, me->GetGUID()) && + doCast(mytar, GetSpell(VAMPIRIC_TOUCH_1))) + return; + if (IsSpellReady(SW_PAIN_1, diff) && can_do_shadow && Rand() < 60 && + mytar->GetHealth() > me->GetMaxHealth()/2 * (1 + mytar->getAttackers().size()) && + !mytar->GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_PRIEST, 0x8000, 0x0, 0x0, me->GetGUID())) + { + AuraEffect const* weav = me->GetAuraEffect(SPELL_AURA_MOD_DAMAGE_PERCENT_DONE, SPELLFAMILY_PRIEST, 0x0, 0x8, 0x0); + if (me->GetLevel() < 60 || (weav && weav->GetBase()->GetStackAmount() >= 4)) + if (doCast(mytar, GetSpell(SW_PAIN_1))) + return; + } + if (IsSpellReady(DEVOURING_PLAGUE_1, diff) && can_do_shadow && !Devcheck && Rand() < 80 && + (GetSpec() == BOT_SPEC_PRIEST_SHADOW || mytar->IsControlledByPlayer()) && + mytar->GetHealth() > me->GetMaxHealth()/2 * (1 + mytar->getAttackers().size()) && + !(mytar->GetTypeId() == TYPEID_UNIT && (mytar->ToCreature()->GetCreatureTemplate()->MechanicImmuneMask & (1<<(MECHANIC_INFECTED-1)))) && + !mytar->GetAuraEffect(SPELL_AURA_PERIODIC_LEECH, SPELLFAMILY_PRIEST, 0x02000000, 0x0, 0x0, me->GetGUID()) && + doCast(mytar, GetSpell(DEVOURING_PLAGUE_1))) + return; + if (IsSpellReady(MIND_BLAST_1, diff) && can_do_shadow && + doCast(mytar, GetSpell(MIND_BLAST_1))) + return; + if (IsSpellReady(MIND_SEAR_1, diff) && can_do_shadow && (!me->isMoving() || Rand() < 80) && + mytar->GetVictim() && mytar->GetVictim()->getAttackers().size() > 3) + { + if (Unit* u = FindSplashTarget(CalcSpellMaxRange(MIND_SEAR_1), mytar, 14.f, 3)) //glyphed, cluster of 4 + if (doCast(u, GetSpell(MIND_SEAR_1))) + return; + } + if (IsSpellReady(HOLY_FIRE_1, diff) && can_do_holy && + (HasRole(BOT_ROLE_HEAL) || me->GetShapeshiftForm() != FORM_SHADOW) && + doCast(mytar, GetSpell(HOLY_FIRE_1))) + return; + if (IsSpellReady(MIND_FLAY_1, diff) && can_do_shadow && + (!HasRole(BOT_ROLE_HEAL) || mytar->GetHealth() < me->GetMaxHealth()/2) && + doCast(mytar, GetSpell(MIND_FLAY_1))) + return; + if (IsSpellReady(SMITE_1, diff) && can_do_holy && me->GetLevel() < 20 &&//MF is lvl 20, MB is lvl 10 + doCast(mytar, GetSpell(SMITE_1))) + return; + } + + if (Spell const* shot = me->GetCurrentSpell(CURRENT_AUTOREPEAT_SPELL)) + { + if (shot->GetSpellInfo()->Id == SHOOT_WAND && shot->m_targets.GetUnitTarget() != mytar) + me->InterruptSpell(CURRENT_AUTOREPEAT_SPELL); + } + else if (IsSpellReady(SHOOT_WAND, diff) && !me->isMoving() && me->GetDistance(mytar) < 30 && GetEquips(BOT_SLOT_RANGED) && + doCast(mytar, SHOOT_WAND)) + return; + } + + bool HealTarget(Unit* target, uint32 diff) override + { + if (!target || !target->IsAlive() || target->GetShapeshiftForm() == FORM_SPIRITOFREDEMPTION || me->GetDistance(target) > 40) + return false; + + uint8 hp = GetHealthPCT(target); + if (hp > GetHealHpPctThreshold()) + return false; + bool pointed = IsPointedHealTarget(target); + if (hp > 90 && !(pointed && me->GetMap()->IsRaid()) && + (!target->IsInCombat() || target->getAttackers().empty() || !IsTank(target) || !me->GetMap()->IsRaid())) + return false; + + int32 hps = GetHPS(target); + int32 xphp = target->GetHealth() + hps * (me->GetLevel() < 60 ? 2.5f : 2.0f); + int32 hppctps = int32(hps * 100.f / float(target->GetMaxHealth())); + int32 xphploss = xphp > int32(target->GetMaxHealth()) ? 0 : abs(int32(xphp - target->GetMaxHealth())); + int32 xppct = hp + hppctps * (me->GetLevel() < 60 ? 2.5f : 2.0f); + //TC_LOG_ERROR("entities.player", "priest_bot:HealTarget(): {}'s pct {}, hppctps {}, epct {}", + // target->GetName(), uint32(hp), int32(hppctps), int32(xppct)); + if (xppct >= 95 && hp >= 25 && !pointed) + return false; + + //GUARDIAN SPIRIT no GCD + if (IsSpellReady(GUARDIAN_SPIRIT_1, diff, false) && !IAmFree() && target->IsInCombat() && !target->getAttackers().empty() && + (xppct <= 0 || (hp <= 50 && hppctps <= -15) || + (me->GetMap()->Instanceable() && target->GetMaxHealth() > me->GetMaxHealth() << 5)) && + !target->HasAuraTypeWithFamilyFlags(SPELL_AURA_MOD_HEALING_PCT, SPELLFAMILY_PRIEST, 0x40000000)) + { + me->InterruptNonMeleeSpells(false); + if (doCast(target, GetSpell(GUARDIAN_SPIRIT_1))) + { + if (target->GetTypeId() == TYPEID_PLAYER) + ReportSpellCast(GUARDIAN_SPIRIT_1, LocalizedNpcText(target->ToPlayer(), BOT_TEXT__ON_YOU), target->ToPlayer()); + + if (!IAmFree() && target != master) + { + std::string msg = target == me ? LocalizedNpcText(master, BOT_TEXT__ON_MYSELF) : (LocalizedNpcText(master, BOT_TEXT__ON_) + target->GetName() + '!'); + ReportSpellCast(GUARDIAN_SPIRIT_1, msg, master); + } + //return true; + } + } + + //PAIN SUPPRESSION + if (IsSpellReady(PAIN_SUPPRESSION_1, diff, false) && xppct >= 5 && hp >= 25 && hp <= 55 && hppctps <= -10 && + Rand() < 80 && !target->getAttackers().empty() && + !target->HasAuraTypeWithFamilyFlags(SPELL_AURA_MOD_DISPEL_RESIST, SPELLFAMILY_PRIEST, 0x80000000) && + !target->HasAuraTypeWithFamilyFlags(SPELL_AURA_MOD_HEALING_PCT, SPELLFAMILY_PRIEST, 0x40000000)) + { + me->InterruptNonMeleeSpells(false); + if (doCast(target, GetSpell(PAIN_SUPPRESSION_1))) + { + if (target->GetTypeId() == TYPEID_PLAYER) + ReportSpellCast(PAIN_SUPPRESSION_1, LocalizedNpcText(target->ToPlayer(), BOT_TEXT__ON_YOU), target->ToPlayer()); + + if (!IAmFree() && target != master) + { + std::string msg = target == me ? LocalizedNpcText(master, BOT_TEXT__ON_MYSELF) : (LocalizedNpcText(master, BOT_TEXT__ON_) + target->GetName() + '!'); + ReportSpellCast(PAIN_SUPPRESSION_1, msg, master); + } + return true; + } + } + + if (target == me && IsSpellReady(DESPERATE_PRAYER_1, diff) && hp <= 50 && Rand() < 45 && + int32(GetLostHP(me)) > _heals[DESPERATE_PRAYER_1]) + { + me->InterruptNonMeleeSpells(false); + if (doCast(target, GetSpell(DESPERATE_PRAYER_1))) + return true; + } + + if (IsCasting()) + return false; + + Unit const* u = target->GetVictim(); + bool tanking = u && IsTank(target) && u->GetTypeId() == TYPEID_UNIT && u->ToCreature()->isWorldBoss(); + + //Penance + if (IsSpellReady(PENANCE_1, diff) && !target->IsCharmed() && !target->isPossessed() && hp <= 80 && + Rand() < 90 && xphploss > _heals[PENANCE_1]) + { + if (doCast(target, GetSpell(PENANCE_1))) + return true; + } + //Big Heal + if (IsSpellReady(HEAL, diff) && (xppct > 15 || !GetSpell(FLASH_HEAL_1)) && (tanking || xphploss > _heals[HEAL])) + { + if (me->IsInCombat() && IsSpellReady(INNER_FOCUS_1, diff) && GetManaPCT(me) < 70 && + doCast(me, GetSpell(INNER_FOCUS_1))) + {} + if (doCast(target, GetSpell(HEAL))) + return true; + } + //Renew + if (IsSpellReady(RENEW_1, diff) && (tanking || !target->getAttackers().empty() || me->GetMap()->IsDungeon()) && + !target->GetAuraEffect(SPELL_AURA_PERIODIC_HEAL, SPELLFAMILY_PRIEST, 0x40, 0x0, 0x0, me->GetGUID()) + /*!target->HasAura(GetSpell(RENEW_1), me->GetGUID())*/) + { + if (doCast(target, GetSpell(RENEW_1))) + return true; + } + //Flash Heal + if (IsSpellReady(FLASH_HEAL_1, diff) && xphploss > _heals[FLASH_HEAL_1]) + { + if (doCast(target, GetSpell(FLASH_HEAL_1))) + return true; + } + + return false; + } + + bool BuffTarget(Unit* target, uint32 diff) override + { + if (IsSpellReady(FEAR_WARD_1, diff) && (!IAmFree() || target == me) && + !target->HasAuraTypeWithMiscvalue(SPELL_AURA_MECHANIC_IMMUNITY, MECHANIC_FEAR) && + doCast(target, GetSpell(FEAR_WARD_1))) + return true; + + if (target == me) + { + if (GetSpell(INNER_FIRE_1) && + !me->HasAuraTypeWithFamilyFlags(SPELL_AURA_MOD_RESISTANCE, SPELLFAMILY_PRIEST, 0x2) && + doCast(me, GetSpell(INNER_FIRE_1))) + return true; + if (HasRole(BOT_ROLE_DPS) && GetSpell(VAMPIRIC_EMBRACE_1) && + !me->HasAuraTypeWithFamilyFlags(SPELL_AURA_DUMMY, SPELLFAMILY_PRIEST, 0x4) && + doCast(me, GetSpell(VAMPIRIC_EMBRACE_1))) + return true; + } + + if (me->IsInCombat() && !master->GetMap()->IsRaid()) + return false; + + if (uint32 PW_FORTITUDE = GetSpell(PW_FORTITUDE_1)) + { + if (!target->HasAuraTypeWithFamilyFlags(SPELL_AURA_MOD_STAT, SPELLFAMILY_PRIEST, 0x8) && + doCast(target, PW_FORTITUDE)) + return true; + } + if (uint32 SHADOW_PROTECTION = GetSpell(SHADOW_PROTECTION_1)) + { + if (!target->HasAuraTypeWithFamilyFlags(SPELL_AURA_MOD_RESISTANCE_EXCLUSIVE, SPELLFAMILY_PRIEST, 0x100) && + doCast(target, SHADOW_PROTECTION)) + return true; + } + if (uint32 DIVINE_SPIRIT = GetSpell(DIVINE_SPIRIT_1)) + { + if ((target->GetMaxPower(POWER_MANA) > 1) && + !target->HasAuraTypeWithFamilyFlags(SPELL_AURA_MOD_STAT, SPELLFAMILY_PRIEST, 0x20) && + doCast(target, DIVINE_SPIRIT)) + return true; + } + + return false; + } + + void DoNonCombatActions(uint32 diff) + { + if (GC_Timer > diff || me->IsMounted() || IsCasting()) + return; + + ResurrectGroup(GetSpell(RESURRECTION_1)); + + if (GetSpell(LEVITATE_1) && !IAmFree() && Rand() < 30) + { + Group const* gr = master->GetGroup(); + if (gr) + { + for (GroupReference const* ref = gr->GetFirstMember(); ref != nullptr; ref = ref->next()) + { + Player* pl = ref->GetSource(); + if (pl && pl->IsAlive() && pl->FindMap() == me->GetMap() && pl->GetDistance(me) < 30 && + pl->IsFalling() && pl->m_movementInfo.fallTime > 1000 && + !pl->HasAuraType(SPELL_AURA_HOVER)) + { + if (doCast(pl, GetSpell(LEVITATE_1))) + return; + } + } + } + else if (master->IsAlive() && master->GetDistance(me) < 30 && master->IsFalling() && + master->m_movementInfo.fallTime > 1000 && !master->HasAuraType(SPELL_AURA_HOVER)) + { + if (doCast(master, GetSpell(LEVITATE_1))) + return; + } + } + } + + void Counter(uint32 diff) + { + if (ShackcheckTimer > diff || !IsSpellReady(SHACKLE_UNDEAD_1, diff) || Shackcheck || Rand() > 65 || + (HasRole(BOT_ROLE_HEAL) && (IsCasting() || GetManaPCT(me) < 20))) + return; + + //always glyphed so <= 0.5 sec cast time + if (Unit* target = FindCastingTarget(CalcSpellMaxRange(SHACKLE_UNDEAD_1), 0, SHACKLE_UNDEAD_1)) + { + me->InterruptNonMeleeSpells(false); + if (doCast(target, GetSpell(SHACKLE_UNDEAD_1))) + return; + } + } + + void CheckDispel(uint32 diff) + { + if (HasRole(BOT_ROLE_HEAL) && !HasRole(BOT_ROLE_DPS)) + return; + + if (DispelcheckTimer > diff || IsCasting() || Rand() > 35) + return; + + DispelcheckTimer = urand(750, 1000); + + uint32 DM = GetSpell(DISPEL_MAGIC_1); + uint32 MD = (GetSpec() == BOT_SPEC_PRIEST_DISCIPLINE) ? GetSpell(MASS_DISPEL_1) : 0; + + if (!DM && !MD) + return; + + if (Unit* target = FindHostileDispelTarget(CalcSpellMaxRange(DISPEL_MAGIC_1))) + { + uint32 dm = DM && !target->HasAuraWithMechanic(1< diff || !IsSpellReady(PRAYER_OF_MENDING_1, diff) || !HasRole(BOT_ROLE_HEAL) || IsCasting() || Rand() > 75) + return; + + Mend_Timer = urand(1000, 3000); + + Group const* gr = !IAmFree() ? master->GetGroup() : GetGroup(); + if (!gr) + return; + + uint32 MENDING_AURA = InitSpell(me, PRAYER_OF_MENDING_AURA_1); + if (FindAffectedTarget(MENDING_AURA, me->GetGUID(), 70, 4)) + return; + + for (Unit* member : BotMgr::GetAllGroupMembers(gr)) + { + if (me->GetMap() == member->FindMap() && member->IsAlive() && !member->getAttackers().empty() && + (IsTank(member) || GetBG()) && GetHealthPCT(member) < 85 && me->IsWithinDistInMap(member, 40) && + !member->HasAuraType(SPELL_AURA_RAID_PROC_FROM_CHARGE_WITH_VALUE)) + { + if (doCast(member, GetSpell(PRAYER_OF_MENDING_1))) + return; + } + } + } + + void CheckShackles(uint32 diff) + { + if (Shackle_Timer > diff || !IsSpellReady(SHACKLE_UNDEAD_1, diff) || IsCasting() || Rand() > 50) + return; + + Shackle_Timer = 500; + + if (FindAffectedTarget(GetSpell(SHACKLE_UNDEAD_1), me->GetGUID(), 60, 255)) + return; + Unit* target = FindUndeadCCTarget(CalcSpellMaxRange(SHACKLE_UNDEAD_1), SHACKLE_UNDEAD_1); + if (target && doCast(target, GetSpell(SHACKLE_UNDEAD_1))) + {} + } + + void CheckSilence(uint32 diff) + { + if (IsCasting() || Rand() > 40) + return; + + if (IsSpellReady(SILENCE_1, diff, false)) + { + if (Unit* target = FindCastingTarget(CalcSpellMaxRange(SILENCE_1), 0, SILENCE_1)) + if (doCast(target, GetSpell(SILENCE_1))) + return; + } + if (IsSpellReady(PSYCHIC_HORROR_1, diff)) + { + if (Unit* target = FindCastingTarget(CalcSpellMaxRange(PSYCHIC_HORROR_1), 0, PSYCHIC_HORROR_1)) + if (doCast(target, GetSpell(PSYCHIC_HORROR_1))) + return; + } + } + + void CheckPowerInfusion(uint32 diff) + { + if (!IsSpellReady(POWER_INFUSION_1, diff, false) || IsCasting() || Rand() > 25) + return; + + if (IAmFree()) + { + if (me->GetVictim() && GetManaPCT(me) < 95 && + doCast(me, GetSpell(POWER_INFUSION_1))) + return; + + return; + } + + Group const* gr = master->GetGroup(); + BotMap const* map; + Unit* u = nullptr; + if (!gr) + { + u = master; + if (u->IsAlive() && u->IsInWorld() && u->GetPowerType() == POWER_MANA && u->GetVictim() && !IsTank(u) && + GetManaPCT(u) < 70 && me->IsWithinDistInMap(u, 30) && + !u->HasAuraTypeWithFamilyFlags(SPELL_AURA_MOD_CASTING_SPEED_NOT_STACK, SPELLFAMILY_PRIEST, 0x80000000) && + doCast(u, GetSpell(POWER_INFUSION_1))) + return; + + map = master->GetBotMgr()->GetBotMap(); + for (BotMap::const_iterator itr = map->begin(); itr != map->end(); ++itr) + { + u = itr->second; + if (u->IsAlive() && u->IsInWorld() && u->ToCreature()->GetBotAI()->HasRole(BOT_ROLE_HEAL) && + u->ToCreature()->GetBotClass() < BOT_CLASS_EX_START && + GetManaPCT(u) < 70 && me->IsWithinDistInMap(u, 30) && + !u->HasAuraTypeWithFamilyFlags(SPELL_AURA_MOD_CASTING_SPEED_NOT_STACK, SPELLFAMILY_PRIEST, 0x80000000) && + doCast(u, GetSpell(POWER_INFUSION_1))) + return; + } + for (BotMap::const_iterator itr = map->begin(); itr != map->end(); ++itr) + { + u = itr->second; + if (u->IsAlive() && u->IsInWorld() && u->GetPowerType() == POWER_MANA && u->GetVictim() && !IsTank(u) && + u->ToCreature()->GetBotClass() < BOT_CLASS_EX_START && + GetManaPCT(u) < 70 && me->IsWithinDistInMap(u, 30) && + !u->HasAuraTypeWithFamilyFlags(SPELL_AURA_MOD_CASTING_SPEED_NOT_STACK, SPELLFAMILY_PRIEST, 0x80000000) && + doCast(u, GetSpell(POWER_INFUSION_1))) + return; + } + + return; + } + + for (GroupReference const* itr = gr->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + u = itr->GetSource(); + if (u && u->IsAlive() && u->IsInWorld() && u->GetPowerType() == POWER_MANA && u->GetVictim() && !IsTank(u) && + GetManaPCT(u) < 70 && me->IsWithinDistInMap(u, 30) && + !u->HasAuraTypeWithFamilyFlags(SPELL_AURA_MOD_CASTING_SPEED_NOT_STACK, SPELLFAMILY_PRIEST, 0x80000000) && + doCast(u, GetSpell(POWER_INFUSION_1))) + return; + } + for (GroupReference const* itr = gr->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player const* player = itr->GetSource(); + if (!player || !player->IsInWorld() || me->GetMap() != player->FindMap() || !player->HaveBot()) + continue; + map = master->GetBotMgr()->GetBotMap(); + for (BotMap::const_iterator bitr = map->begin(); bitr != map->end(); ++bitr) + { + u = bitr->second; + if (u->IsAlive() && u->IsInWorld() && u->ToCreature()->GetBotAI()->HasRole(BOT_ROLE_HEAL) && + !IsHeroExClass(u->ToCreature()->GetBotClass()) && + GetManaPCT(u) < 70 && me->IsWithinDistInMap(u, 30) && + !u->HasAuraTypeWithFamilyFlags(SPELL_AURA_MOD_CASTING_SPEED_NOT_STACK, SPELLFAMILY_PRIEST, 0x80000000) && + doCast(u, GetSpell(POWER_INFUSION_1))) + return; + } + } + for (GroupReference const* itr = gr->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player const* player = itr->GetSource(); + if (!player || !player->IsInWorld() || me->GetMap() != player->FindMap() || !player->HaveBot()) + continue; + map = master->GetBotMgr()->GetBotMap(); + for (BotMap::const_iterator bitr = map->begin(); bitr != map->end(); ++bitr) + { + u = bitr->second; + if (u->IsAlive() && u->IsInWorld() && u->GetPowerType() == POWER_MANA && u->GetVictim() && !IsTank(u) && + u->ToCreature()->GetBotClass() < BOT_CLASS_EX_START && + GetManaPCT(u) < 70 && me->IsWithinDistInMap(u, 30) && + !u->HasAuraTypeWithFamilyFlags(SPELL_AURA_MOD_CASTING_SPEED_NOT_STACK, SPELLFAMILY_PRIEST, 0x80000000) && + doCast(u, GetSpell(POWER_INFUSION_1))) + return; + } + } + + SetSpellCooldown(POWER_INFUSION_1, 1500); //fail + } + + void doDefend(uint32 diff) + { + if (Rand() > 50) return; + + Unit::AttackerSet const& m_attackers = master->getAttackers(); + Unit::AttackerSet const& b_attackers = me->getAttackers(); + + //fear master's attackers + if (IsSpellReady(PSYCHIC_SCREAM_1, diff)) + { + if (!m_attackers.empty() && (!IsTank(master) || GetHealthPCT(master) < 75)) + { + uint8 tCount = 0; + for (Unit::AttackerSet::const_iterator iter = m_attackers.begin(); iter != m_attackers.end(); ++iter) + { + if (!(*iter)) continue; + if ((*iter)->ToCreature() && (*iter)->GetCreatureType() == CREATURE_TYPE_UNDEAD) continue; + if (me->GetExactDist((*iter)) > 7) continue; + if (CCed(*iter) && me->GetExactDist((*iter)) > 5) continue; + if (me->IsValidAttackTarget(*iter)) + ++tCount; + } + if (tCount > 1 && doCast(me, GetSpell(PSYCHIC_SCREAM_1))) + return; + } + + // Defend myself (psychic horror) + if (!b_attackers.empty()) + { + uint8 tCount = 0; + for (Unit::AttackerSet::const_iterator iter = b_attackers.begin(); iter != b_attackers.end(); ++iter) + { + if (!(*iter)) continue; + if ((*iter)->ToCreature() && (*iter)->GetCreatureType() == CREATURE_TYPE_UNDEAD) continue; + if (me->GetExactDist((*iter)) > 7) continue; + if (CCed(*iter) && me->GetExactDist((*iter)) > 5) continue; + if (me->IsValidAttackTarget(*iter)) + ++tCount; + } + if (tCount > 0 && doCast(me, GetSpell(PSYCHIC_SCREAM_1))) + return; + } + } + // Heal myself + if ((GetHealthPCT(me) < 95 && !b_attackers.empty()) || (IsWanderer() && IsFlagCarrier(me))) + { + if (ShieldTarget(me, diff)) return; + + if (IsSpellReady(FADE_1, diff) && me->IsInCombat()) + { + if (b_attackers.empty()) return; + uint8 Tattackers = 0; + for (Unit::AttackerSet::const_iterator iter = b_attackers.begin(); iter != b_attackers.end(); ++iter) + { + if (!(*iter)) continue; + if (!(*iter)->IsAlive()) continue; + if (!(*iter)->CanHaveThreatList()) continue; + if (me->GetDistance((*iter)) < 15) + Tattackers++; + } + if (Tattackers > 0) + { + if (doCast(me, GetSpell(FADE_1))) + return; + } + } + } + } + + void DoDevCheck(uint32 diff) + { + if (DevcheckTimer <= diff) + { + DevcheckTimer = 1000; + Devcheck = GetSpell(DEVOURING_PLAGUE_1) && FindAffectedTarget(GetSpell(DEVOURING_PLAGUE_1), me->GetGUID(), 70); + } + } + + void DoShackCheck(uint32 diff) + { + if (ShackcheckTimer <= diff) + { + ShackcheckTimer = 1000; + Shackcheck = GetSpell(SHACKLE_UNDEAD_1) && FindAffectedTarget(GetSpell(SHACKLE_UNDEAD_1), me->GetGUID(), 70); + } + } + + void Disperse(uint32 diff) + { + if (me->GetVehicle()) + return; + if (!IsSpellReady(DISPERSION_1, diff) || !me->IsInCombat() || HasRole(BOT_ROLE_HEAL) || IsCasting() || Rand() > 60) + return; + if ((me->getAttackers().size() > 3 && !IsSpellReady(FADE_1, diff, false) && GetHealthPCT(me) < 90) || + (GetHealthPCT(me) < 20 && (me->HasAuraType(SPELL_AURA_PERIODIC_DAMAGE) || !me->getAttackers().empty())) || + (GetManaPCT(me) < 35 && !IsPotionReady()) || + (me->getAttackers().size() > 1 && (CCed(me, true) || me->HasAuraWithMechanic(1<GetFirstRankSpell()->Id; + uint8 lvl = me->GetLevel(); + + //Inner Focus + if (AuraEffect const* focu = me->GetAuraEffect(INNER_FOCUS_1, 0)) + if (focu->IsAffectingSpell(spellInfo)) + crit_chance += 25.f; + + //Benediction (23236) + if (lvl >= 60 && (schoolMask & SPELL_SCHOOL_MASK_HOLY)) + crit_chance += 2.f; + //Increased Prayer of Healing Criticals (23550): 25% additional critical chance for Prayer of Healing + if (lvl >= 60 && baseId == PRAYER_OF_HEALING_1) + crit_chance += 25.f; + //Item - Priest T9 Shadow 4P Bonus (67198) + if (lvl >= 80 && baseId == MIND_FLAY_DAMAGE) + crit_chance += 5.f; + + //Holy Specialization: 5% additional critical chance for Holy spells + if (lvl >= 10 && (schoolMask & SPELL_SCHOOL_MASK_HOLY)) + crit_chance += 5.f; + //Mind Melt (part 1): 4% additional critical chance for Mind Blast, Mind Flay and Mind Sear + if ((GetSpec() == BOT_SPEC_PRIEST_SHADOW) && + lvl >= 35 && ((spellInfo->SpellFamilyFlags[0] & 0x802000) || (spellInfo->SpellFamilyFlags[1] & 0x80000))) + crit_chance += 4.f; + //Mind Melt (part 2): 6% additional critical chance for Vampiric Touch, Devouring Plague and SW: Pain + if ((GetSpec() == BOT_SPEC_PRIEST_SHADOW) && + lvl >= 35 && ((spellInfo->SpellFamilyFlags[0] & 0x2008000) || (spellInfo->SpellFamilyFlags[1] & 0x400))) + crit_chance += 6.f; + //Improved Flash Heal (part 2): 10% additional critical chance on targets at or below 50% hp for Flash Heal + if ((GetSpec() == BOT_SPEC_PRIEST_DISCIPLINE) && lvl >= 40 && baseId == FLASH_HEAL_1 && GetHealthPCT(victim) <= 50) + crit_chance += 10.f; + //Renewed Hope part 1: 4% additional critical chance on targets affected by Weakened Soul for Flash Heal, Greater Heal and Penance (Heal) + if ((GetSpec() == BOT_SPEC_PRIEST_DISCIPLINE) && + lvl >= 45 && (baseId == FLASH_HEAL_1 || baseId == HEAL || baseId == PENANCE_HEAL_1) && + victim->HasAuraTypeWithFamilyFlags(SPELL_AURA_MECHANIC_IMMUNITY, SPELLFAMILY_PRIEST, 0x20000000)) + crit_chance += 4.f; + } + + void ApplyClassDamageMultiplierSpell(int32& damage, SpellNonMeleeDamage& damageinfo, SpellInfo const* spellInfo, WeaponAttackType /*attackType*/, bool iscrit) const override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + uint8 lvl = me->GetLevel(); + float fdamage = float(damage); + + //apply bonus damage mods + float pctbonus = 0.0f; + if (iscrit) + { + //!!!spell damage is not yet critical and will be multiplied by 1.5 + //so we should put here bonus damage mult /1.5 + //Shadow Power: 50% additional crit damage bonus for Mind Blast, Mind Flay and SW:Death + if (lvl >= 40 && + (baseId == MIND_BLAST_1 || baseId == MIND_FLAY_DAMAGE || baseId == SW_DEATH_1)) + pctbonus += 0.333f; + //Shadowform crit damage increase + if (me->GetShapeshiftForm() == FORM_SHADOW && + (baseId == SW_PAIN_1 || baseId == DEVOURING_PLAGUE_1 || baseId == VAMPIRIC_TOUCH_1)) + pctbonus += 0.333f; + } + //Improved Mind Flay and Smite (37571) + if (lvl >= 10 && (baseId == MIND_FLAY_DAMAGE || baseId == SMITE_1)) + pctbonus += 0.05f; + //Item - Priest T8 Shadow 2P Bonus (64906) + if (lvl >= 80 && ((baseId == DEVOURING_PLAGUE_1) || (spellInfo->SpellFamilyFlags[2] & 0x8))) + pctbonus += 0.15f; + + //Twin Disciplines (damage part): 5% bonus damage for instant spells + if (lvl >= 10 && !spellInfo->CastTimeEntry) + pctbonus += 0.05f; + //Darkness: 10% bonus damage for shadow spells + if (lvl >= 10 && (spellInfo->GetSchoolMask() & SPELL_SCHOOL_MASK_SHADOW)) + pctbonus += 0.1f; + //Improved Shadow Word: Pain: 6% bonus damage for Shadow Word: Pain + if (lvl >= 15 && baseId == SW_PAIN_1) + pctbonus += 0.06f; + //Focused Power part 1: 4% bonus damage for all spells + if ((GetSpec() == BOT_SPEC_PRIEST_DISCIPLINE) && lvl >= 35) + pctbonus += 0.04f; + //Improved Devouring Plague part 1: 15% bonus damage Devouring Plague + if ((GetSpec() == BOT_SPEC_PRIEST_SHADOW) && lvl >= 35 && baseId == DEVOURING_PLAGUE_1) + pctbonus += 0.15f; + //Shadowform: 15% bonus damage for shadow spells (handled) + //if (lvl >= 40 && (spellInfo->GetSchoolMask() & SPELL_SCHOOL_MASK_SHADOW) && me->GetShapeshiftForm() == FORM_SHADOW) + // pctbonus += 0.15f; + //Misery part 3: 15% bonus damage (from spellpower) for Mind Blast, Mind Flay and Mind Sear + if ((GetSpec() == BOT_SPEC_PRIEST_SHADOW) && lvl >= 45) + { + if (baseId == MIND_BLAST_1 || baseId == MIND_FLAY_DAMAGE || baseId == MIND_SEAR_DAMAGE_1) + fdamage += me->SpellBaseDamageBonusDone(SPELL_SCHOOL_MASK_MAGIC) * 0.15f * me->CalculateDefaultCoefficient(spellInfo, DIRECT_DAMAGE) * me->CalculateSpellpowerCoefficientLevelPenalty(spellInfo); + } + + //If target is affected BY SW: Pain + if (lvl >= 20 && (baseId == MIND_BLAST_1 || baseId == MIND_FLAY_DAMAGE) && damageinfo.target && + damageinfo.target->GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_PRIEST, 0x8000, 0x0, 0x0, me->GetGUID())) + { + //Glyph of Mind Flay: 10% damage bonus for Mind Flay + if (baseId == MIND_FLAY_DAMAGE) + pctbonus += 0.1f; + //Twisted Faith (part 1): 10% bonus damage for Mind Blast and Mind Flay + if ((GetSpec() == BOT_SPEC_PRIEST_SHADOW) && lvl >= 55) + pctbonus += 0.1f; + } + + //Glyph of Shadow Word: Death: 10% bonus damage for Shadow Word: Death on targets below 35% health + if (lvl >= 62 && baseId == SW_DEATH_1 && damageinfo.target->HasAuraState(AURA_STATE_HEALTHLESS_35_PERCENT)) + pctbonus += 0.1f; + + //other + if (baseId == SW_DEATH_BACKLASH) + { + //not affected by +%talents + pctbonus = 1.f; + ////T13 Shadow 2P Bonus (Shadow Word: Death), part 2 + //if (lvl >= 60) //buffed + // pctbonus -= 0.95f; + //Pain and Suffering (part 2): 30% reduced backlash damage + if ((GetSpec() == BOT_SPEC_PRIEST_SHADOW) && lvl >= 50) + pctbonus -= 0.3f; + } + + damage = int32(fdamage * (1.0f + pctbonus)); + } + + void ApplyClassDamageMultiplierHeal(Unit const* victim, float& heal, SpellInfo const* spellInfo, DamageEffectType damagetype, uint32 stack) const override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + uint8 lvl = me->GetLevel(); + float pctbonus = 0.0f; + float flat_mod = 0.0f; + + //pct mods + //Improved Greater Heal (38411): 5% bonus healing for Greater Heal + if (lvl >= 60 && baseId == HEAL) + pctbonus += 0.05f; + //Priest T9 Healing 2P: 20% bonus healing for Prayer of Mending + if (lvl >= 80 && baseId == PRAYER_OF_MENDING_HEAL) + pctbonus += 0.2f; + + //Twin Disciplines (healing part): 5% bonus healing for instant spells + if (lvl >= 10 && !spellInfo->CastTimeEntry) + pctbonus += 0.05f; + //Improved Renew: 15% bonus healing for Renew + if (lvl >= 10 && baseId == RENEW_1) + pctbonus += 0.15f; + //Focused Power part 2: 4% bonus heal for all spells + if ((GetSpec() == BOT_SPEC_PRIEST_DISCIPLINE) && lvl >= 35) + pctbonus += 0.04f; + //Spiritual Healing: 10% bonus healing for all spells + if ((GetSpec() == BOT_SPEC_PRIEST_HOLY) && lvl >= 35) + pctbonus += 0.15f; + //Blessend Resilience part 1: 3% bonus healing for all spells + if ((GetSpec() == BOT_SPEC_PRIEST_HOLY) && lvl >= 40) + pctbonus += 0.03f; + //Empowered Healing: 40% bonus (from spellpower) for Greater Heal and 20% bonus (from spellpower) for Flash Heal and Binding Heal + if ((GetSpec() == BOT_SPEC_PRIEST_HOLY) && lvl >= 45) + { + if (baseId == HEAL) + flat_mod += me->SpellBaseHealingBonusDone(SPELL_SCHOOL_MASK_MAGIC) * 0.4f * me->CalculateDefaultCoefficient(spellInfo, damagetype) * 1.88f * me->CalculateSpellpowerCoefficientLevelPenalty(spellInfo) * stack; + else if (baseId == FLASH_HEAL_1/* || baseId == BINDING_HEAL_1*/) + flat_mod += me->SpellBaseHealingBonusDone(SPELL_SCHOOL_MASK_MAGIC) * 0.2f * me->CalculateDefaultCoefficient(spellInfo, damagetype) * 1.88f * me->CalculateSpellpowerCoefficientLevelPenalty(spellInfo) * stack; + } + //Empowered Renew (heal bonus part): 15% bonus healing (from spellpower) for Renew + if ((GetSpec() == BOT_SPEC_PRIEST_HOLY) && lvl >= 50 && baseId == RENEW_1) + flat_mod += me->SpellBaseHealingBonusDone(SPELL_SCHOOL_MASK_MAGIC) * 0.15f * me->CalculateDefaultCoefficient(spellInfo, damagetype) * 1.88f * me->CalculateSpellpowerCoefficientLevelPenalty(spellInfo) * stack; + //Test of Faith: 12% bonus healing on targets at or below 50% health + if ((GetSpec() == BOT_SPEC_PRIEST_HOLY) && lvl >= 50 && GetHealthPCT(victim) <= 50) + pctbonus += 0.12f; + //Divine Providence: 10% bonus healing for Circle of Healing, Binding Heal, Holy Nova, Prayer of Healing, Divine Hymn and Prayer of Mending + if ((GetSpec() == BOT_SPEC_PRIEST_HOLY) && lvl >= 55 && + ((spellInfo->SpellFamilyFlags[0] & 0x18000200) || + (spellInfo->SpellFamilyFlags[1] & 0x4) || + (spellInfo->SpellFamilyFlags[2] & 0x4))) + pctbonus += 0.1f; + + //flat mods + //Improved Prayer of Mending: 100 additional heal for Prayer of Mending + if (baseId == PRAYER_OF_MENDING_HEAL) + flat_mod += 100; + + heal = heal * (1.0f + pctbonus) + flat_mod; + } + + void ApplyClassSpellCostMods(SpellInfo const* spellInfo, int32& cost) const override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + uint8 lvl = me->GetLevel(); + float fcost = float(cost); + int32 flatbonus = 0; + float pctbonus = 0.0f; + + //percent mods + //Inner Focus + if (AuraEffect const* focu = me->GetAuraEffect(INNER_FOCUS_1, 0)) + if (focu->IsAffectingSpell(spellInfo)) + pctbonus += 1.f; + //Surge of Light + if (AuraEffect const* surg = me->GetAuraEffect(SURGE_OF_LIGHT_BUFF, 1)) + if (surg->IsAffectingSpell(spellInfo)) + pctbonus += 1.f; + + //Reduced Prayer of Healing Cost (38410): + if (lvl >= 60 && baseId == PRAYER_OF_HEALING_1) + pctbonus += 0.1f; + //Greater Heal Cost Reduction (60155): + if (lvl >= 60 && baseId == HEAL) + pctbonus += 0.05f; + + //Shadow Focus part 2 + if (lvl >= 15 && (spellInfo->GetSchoolMask() & SPELL_SCHOOL_MASK_SHADOW)) + pctbonus += 0.06f; + //Absolution: + if ((GetSpec() == BOT_SPEC_PRIEST_DISCIPLINE) && lvl >= 25 && (spellInfo->SpellFamilyFlags[1] & 0x81)) + pctbonus += 0.15f; + //Mental Agility: + if ((GetSpec() == BOT_SPEC_PRIEST_DISCIPLINE) && lvl >= 25 && !spellInfo->CastTimeEntry) + pctbonus += 0.1f; + //Improved Healing: + if ((GetSpec() == BOT_SPEC_PRIEST_HOLY) && + lvl >= 25 && (baseId == HEAL || baseId == DIVINE_HYMN_1 || baseId == PENANCE_HEAL_1)) + pctbonus += 0.15f; + //Soul Warding part 2 + if ((GetSpec() == BOT_SPEC_PRIEST_DISCIPLINE) && lvl >= 30 && baseId == PW_SHIELD_1) + pctbonus += 0.15f; + //Healing Prayers: + if ((GetSpec() == BOT_SPEC_PRIEST_HOLY) && + lvl >= 30 && (baseId == PRAYER_OF_HEALING_1 || baseId == PRAYER_OF_MENDING_1)) + pctbonus += 0.2f; + //Focused Mind + if ((GetSpec() == BOT_SPEC_PRIEST_SHADOW) && + lvl >= 30 && (baseId == MIND_BLAST_1 || baseId == MIND_FLAY_1 || + baseId == MIND_SEAR_1/* || baseId == MIND_CONTROL_1*/)) + pctbonus += 0.15f; + //Improved Flash Heal part 1 + if ((GetSpec() == BOT_SPEC_PRIEST_HOLY) && lvl >= 40 && baseId == FLASH_HEAL_1) + pctbonus += 0.15f; + + //Glyph of Fading + if (lvl >= 15 && baseId == FADE_1) + pctbonus += 0.3f; + //Glyph of Fortitude + if (lvl >= 15 && baseId == PW_FORTITUDE_1) + pctbonus += 0.5f; + //Glyph of Flash Heal + if (lvl >= 20 && baseId == FLASH_HEAL_1) + pctbonus += 0.1f; + //Glyph of Mass Dispel + if (lvl >= 70 && baseId == MASS_DISPEL_1) + pctbonus += 0.35f; + + //flat mods + //Cleanse Cost Reduced (id: 27847): -25 mana cost for Cleanse + //if (lvl >= 40 && spellId == GetSpell(CLEANSE_1)) + // flatbonus += 25; + + //cost can be < 0 + cost = int32(fcost * (1.0f - pctbonus)) - flatbonus; + } + + void ApplyClassSpellCastTimeMods(SpellInfo const* spellInfo, int32& casttime) const override + { + //casttime is in milliseconds + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + uint8 lvl = me->GetLevel(); + int32 timebonus = 0; + float pctbonus = 0.0f; + + //100% mods + //Surge of Light + if (AuraEffect const* surg = me->GetAuraEffect(SURGE_OF_LIGHT_BUFF, 1)) + if (surg->IsAffectingSpell(spellInfo)) + pctbonus += 1.f; + + //pct mods + //Serendipity: -12% per stack cast time for Prayer of Healing or Greater Heal + if (baseId == GREATER_HEAL_1 || baseId == PRAYER_OF_HEALING_1) + { + if (AuraEffect const* sere = me->GetAuraEffect(SERENDIPITY_BUFF, 0)) + if (sere->IsAffectingSpell(spellInfo)) + pctbonus += 0.12f * sere->GetBase()->GetStackAmount(); + } + + //flat mods + //Improved Prayer of Healing (21339) + if (lvl >= 60 && baseId == PRAYER_OF_HEALING_1) + timebonus += 100; + //Master Healer (15027) rank 5 + if (lvl >= 60 && baseId == HEAL) + timebonus += 500; + //Prophesy Flash Heal Bonus (21973) part 1 + if (lvl >= 60 && baseId == FLASH_HEAL_1) + timebonus += 100; + + //Divine Fury + if (lvl >= 15 && (baseId == HEAL || baseId == SMITE_1 || baseId == HOLY_FIRE_1)) + timebonus += 500; + //Focused Power part 3 + if ((GetSpec() == BOT_SPEC_PRIEST_DISCIPLINE) && lvl >= 35 && baseId == MASS_DISPEL_1) + timebonus += 1000; + //Improved Mana Burn + //if (lvl >= 35 && baseId == MANA_BURN_1) + // timebonus += 1000; + + //Glyph of Scourge Imprisonment + if (lvl >= 20 && baseId == SHACKLE_UNDEAD_1) + timebonus += 1000; + + casttime = std::max((float(casttime) * (1.0f - pctbonus)) - timebonus, 0); + } + + void ApplyClassSpellCooldownMods(SpellInfo const* spellInfo, uint32& cooldown) const override + { + //cooldown is in milliseconds + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + uint8 lvl = me->GetLevel(); + int32 timebonus = 0; + float pctbonus = 0.0f; + + //pct mods + //Aspiration + if ((GetSpec() == BOT_SPEC_PRIEST_DISCIPLINE) && + lvl >= 45 && (baseId == INNER_FOCUS_1 || baseId == POWER_INFUSION_1 || baseId == PAIN_SUPPRESSION_1)) + pctbonus += 0.2f; + + //flat mods + //Veiled Shadows part 2 + //if (lvl >= 25 && baseId == SHADOWFIEND_1) + // timebonus += 120000; + //Glyph of Dispersion: + if (lvl >= 60 && baseId == DISPERSION_1) + timebonus += 45000; + //Glyph of Penance: + if (lvl >= 60 && baseId == PENANCE_1) + timebonus += 2000; + + cooldown = std::max((float(cooldown) * (1.0f - pctbonus)) - timebonus, 0); + } + + void ApplyClassSpellCategoryCooldownMods(SpellInfo const* spellInfo, uint32& cooldown) const override + { + //cooldown is in milliseconds + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + uint8 lvl = me->GetLevel(); + int32 timebonus = 0; + float pctbonus = 0.0f; + + //pct mods + //Aspiration + if ((GetSpec() == BOT_SPEC_PRIEST_DISCIPLINE) && lvl >= 45 && baseId == PENANCE_1) + pctbonus += 0.2f; + //Divine Providence: + if ((GetSpec() == BOT_SPEC_PRIEST_HOLY) && lvl >= 55 && baseId == PRAYER_OF_MENDING_1) + pctbonus += 0.3f; + + //flat mods + //Quick Fade (18388) + if (lvl >= 40 && baseId == FADE_1) + timebonus += 2000; + + //Improved Psychic Scream + if (lvl >= 20 && baseId == PSYCHIC_SCREAM_1) + timebonus += 4000; + //Improved Mind Blast + if (lvl >= 20 && baseId == MIND_BLAST_1) + timebonus += 2500; + //Veiled Shadows part 1 + if ((GetSpec() == BOT_SPEC_PRIEST_SHADOW) && lvl >= 25 && baseId == FADE_1) + timebonus += 6000; + //Soul Warding part 1 + if ((GetSpec() == BOT_SPEC_PRIEST_DISCIPLINE) && lvl >= 30 && baseId == PW_SHIELD_1) + timebonus += 4000; + + //Glyph of Fade + if (lvl >= 15 && baseId == FADE_1) + timebonus += 9000; + + cooldown = std::max((float(cooldown) * (1.0f - pctbonus)) - timebonus, 0); + } + + void ApplyClassSpellGlobalCooldownMods(SpellInfo const* spellInfo, float& cooldown) const override + { + //cooldown is in milliseconds + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + uint8 lvl = me->GetLevel(); + float timebonus = 0.0f; + float pctbonus = 0.0f; + + //Prophesy Flash Heal Bonus (21973) part 2 + if (lvl >= 60 && baseId == FLASH_HEAL_1) + timebonus += 100; + + cooldown = (cooldown * (1.0f - pctbonus)) - timebonus; + } + + void ApplyClassSpellRadiusMods(SpellInfo const* spellInfo, float& radius) const override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + uint8 lvl = me->GetLevel(); + float flatbonus = 0.0f; + float pctbonus = 0.0f; + + //pct mods + //Holy Reach + if ((GetSpec() == BOT_SPEC_PRIEST_HOLY) && + lvl >= 25 && ((spellInfo->SpellFamilyFlags[0] & 0x18400200) || (spellInfo->SpellFamilyFlags[2] & 0x4))) + pctbonus += 0.2f; + + //flat mods + //Glyph of Mind Sear + if (lvl >= 75 && baseId == MIND_SEAR_DAMAGE_1) + flatbonus += 5.f; + + radius = radius * (1.0f + pctbonus) + flatbonus; + } + + void ApplyClassSpellRangeMods(SpellInfo const* spellInfo, float& maxrange) const override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + uint8 lvl = me->GetLevel(); + float flatbonus = 0.0f; + float pctbonus = 0.0f; + + //pct mods + //Shadow Reach: +20% range for Shadow Spells + if ((GetSpec() == BOT_SPEC_PRIEST_SHADOW) && lvl >= 25 && + ((spellInfo->SpellFamilyFlags[0] & 0x682A004) || + (spellInfo->SpellFamilyFlags[1] & 0x300502) || + (spellInfo->SpellFamilyFlags[2] & 0x2040))) + pctbonus += 0.2f; + //Holy Reach: +20% range for Holy Spells + if ((GetSpec() == BOT_SPEC_PRIEST_HOLY) && lvl >= 25 && (spellInfo->SpellFamilyFlags[0] & 0x100080)) + pctbonus += 0.2f; + + //flat mods + //Glyph of Shackle Undead: +5 yd range for Shackle Undead + if (lvl >= 20 && baseId == SHACKLE_UNDEAD_1) + flatbonus += 5.f; + + maxrange = maxrange * (1.0f + pctbonus) + flatbonus; + } + + void ApplyClassSpellMaxTargetsMods(SpellInfo const* spellInfo, uint32& targets) const override + { + uint32 bonusTargets = 0; + + //Glyph of Circle of Healing: + 1 target + if (spellInfo->SpellFamilyFlags[0] & 0x10000000) + bonusTargets += 1; + + targets = targets + bonusTargets; + } + + void ApplyClassEffectMods(SpellInfo const* spellInfo, uint8 effIndex, float& value) const override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + uint8 lvl = me->GetLevel(); + float pctbonus = 1.0f; + + //Improved Power Word: Fortitude + if (lvl >= 15 && baseId == PW_FORTITUDE_1 && effIndex == EFFECT_0) + pctbonus *= 1.3f; + if (lvl >= 20 && baseId == PW_SHIELD_1 && effIndex == EFFECT_0) + { + //Improved PWSH: +15% effect + pctbonus *= 1.15f; + //Borrowed Time: +40% of spellpower + if (GetSpec() == BOT_SPEC_PRIEST_DISCIPLINE && lvl >= 55) + value += me->SpellBaseDamageBonusDone(SPELL_SCHOOL_MASK_MAGIC) * 0.4f; + } + + value = value * pctbonus; + } + + void OnClassSpellGo(SpellInfo const* spellInfo) override + { + //Surge of Light + //Inner Focus + AuraEffect const* surg = me->GetAuraEffect(SURGE_OF_LIGHT_BUFF, 1); + AuraEffect const* focu = me->GetAuraEffect(INNER_FOCUS_1, 0); + if (surg && surg->IsAffectingSpell(spellInfo)) + me->RemoveAurasDueToSpell(SURGE_OF_LIGHT_BUFF); + else if (focu && focu->IsAffectingSpell(spellInfo)) + me->RemoveAurasDueToSpell(INNER_FOCUS_1); + + //Serendipity + if (AuraEffect const* sere = me->GetAuraEffect(SERENDIPITY_BUFF, 0)) + if (sere->IsAffectingSpell(spellInfo)) + me->RemoveAurasDueToSpell(SERENDIPITY_BUFF); + } + + void SpellHitTarget(WorldObject* wtarget, SpellInfo const* spell) override + { + Unit* target = wtarget->ToUnit(); + if (!target) + return; + + uint32 spellId = spell->Id; + uint32 baseId = spell->GetFirstRankSpell()->Id; + uint8 lvl = me->GetLevel(); + + //Glyph of Hymn of Hope: +2 sec duration + if (lvl >= 60 && (baseId == HYMN_OF_HOPE_1 || baseId == HYMN_OF_HOPE_BUFF)) + { + if (Aura* hymn = target->GetAura(spellId)) + { + hymn->SetDuration(hymn->GetDuration() + 2000); + hymn->SetDuration(hymn->GetMaxDuration() + 2000); + } + } + //Priest T9 Shadow 2P Bonus (67193) + if (lvl >= 80 && baseId == VAMPIRIC_TOUCH_1) + { + if (Aura* touc = target->GetAura(spellId)) + { + uint32 dur = touc->GetMaxDuration() + 6000; + touc->SetDuration(dur); + touc->SetMaxDuration(dur); + } + } + + //Improved Mind Blast part 2 + if ((GetSpec() == BOT_SPEC_PRIEST_SHADOW) && lvl >= 20 && baseId == MIND_BLAST_1) + me->CastSpell(target, IMPROVED_MIND_BLAST_DEBUFF, true); + + //Weakened Soul Reduction (id: 33333): -2 sec to Weakened Soul duration + if (lvl >= 51 && baseId == WEAKENED_SOUL_DEBUFF) + { + if (Aura* soul = target->GetAura(spellId)) + { + uint32 dur = soul->GetMaxDuration() - 2000; + soul->SetDuration(dur); + soul->SetMaxDuration(dur); + } + } + //Pain and Suffering (part 1): 100% to refresh Shadow Word: Pain on target hit by Mind Flay + if ((GetSpec() == BOT_SPEC_PRIEST_SHADOW) && lvl >= 50 && baseId == MIND_FLAY_1 && GetSpell(SW_PAIN_1)) + if (Aura* pain = target->GetAura(GetSpell(SW_PAIN_1), me->GetGUID())) + pain->RefreshDuration(); + if (baseId == FEAR_WARD_1) + { + //2 minutes bonus duration for Fear Ward + if (Aura* ward = target->GetAura(spellId, me->GetGUID())) + { + uint32 dur = ward->GetDuration() + 120000; + ward->SetDuration(dur); + ward->SetMaxDuration(dur); + } + } + //buffs duration + if (baseId == INNER_FIRE_1 || baseId == VAMPIRIC_EMBRACE_1 || baseId == PW_FORTITUDE_1 || + baseId == SHADOW_PROTECTION_1 || baseId == DIVINE_SPIRIT_1) + { + //1 hour duration for all buffs + if (Aura* buff = target->GetAura(spellId, me->GetGUID())) + { + uint32 dur = HOUR * IN_MILLISECONDS; + buff->SetDuration(dur); + buff->SetMaxDuration(dur); + } + } + + OnSpellHitTarget(target, spell); + } + + void SpellHit(WorldObject* wcaster, SpellInfo const* spell) override + { + Unit* caster = wcaster->ToUnit(); + if (!caster) + return; + + uint32 spellId = spell->Id; + uint32 baseId = spell->GetFirstRankSpell()->Id; + uint8 lvl = me->GetLevel(); + + //Glyph of Inner Fire + Improved Inner Fire: + if (lvl >= 15 && baseId == INNER_FIRE_1) + { + if (Aura* fire = me->GetAura(spellId)) + { + fire->SetCharges(fire->GetCharges() + 12); + for (uint8 i = 0; i != MAX_SPELL_EFFECTS; ++i) + if (AuraEffect* eff = fire->GetEffect(i)) + eff->ChangeAmount(int32(eff->GetAmount() * (i == 0 ? 1.45f*1.5f : 1.45f))); + } + } + //Improved Vampiric Embrace + if ((GetSpec() == BOT_SPEC_PRIEST_SHADOW) && lvl >= 30 && baseId == VAMPIRIC_EMBRACE_1) + { + if (AuraEffect* vamp = me->GetAuraEffect(spellId, 0)) + vamp->ChangeAmount(vamp->GetAmount() + 10); //67% is essentially this + } + + OnSpellHit(caster, spell); + } + + void DamageDealt(Unit* victim, uint32& damage, DamageEffectType damageType) override + { + bot_ai::DamageDealt(victim, damage, damageType); + } + + void DamageTaken(Unit* u, uint32& /*damage*/, DamageEffectType /*damageType*/, SpellInfo const* /*spellInfo*/) override + { + if (!u) + return; + if (!u->IsInCombat() && !me->IsInCombat()) + return; + OnOwnerDamagedBy(u); + } + + void OwnerAttackedBy(Unit* u) override + { + if (!u) + return; + OnOwnerDamagedBy(u); + } + + float GetSpellAttackRange(bool longRange) const override + { + return longRange ? CalcSpellMaxRange(MIND_FLAY_1) : 20.f; + } + + void SummonBotPet(Unit* target) + { + if (botPet) + UnsummonAll(false); + + uint32 entry = BOT_PET_SHADOWFIEND; + + //Position pos; + + //15 sec duration + Creature* myPet = me->SummonCreature(entry, *me, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 5s); + //me->GetNearPoint(myPet, pos.m_positionX, pos.m_positionY, pos.m_positionZ, 0, 2, me->GetOrientation()); + //myPet->GetMotionMaster()->MovePoint(me->GetMapId(), pos); + myPet->SetCreator(master); + myPet->SetOwnerGUID(me->GetGUID()); + myPet->SetFaction(master->GetFaction()); + myPet->SetControlledByPlayer(!IAmFree()); + myPet->SetPvP(me->IsPvP()); + myPet->SetByteValue(UNIT_FIELD_BYTES_2, 1, master->GetByteValue(UNIT_FIELD_BYTES_2, 1)); + myPet->SetUInt32Value(UNIT_CREATED_BY_SPELL, SHADOWFIEND_1); + + botPet = myPet; + + myPet->Attack(target, true); + if (!HasBotCommandState(BOT_COMMAND_MASK_UNCHASE)) + myPet->GetMotionMaster()->MoveChase(target); + } + + void UnsummonAll(bool savePets = true) override + { + UnsummonPet(savePets); + } + + void SummonedCreatureDies(Creature* /*summon*/, Unit* /*killer*/) override + { + } + + void SummonedCreatureDespawn(Creature* summon) override + { + //TC_LOG_ERROR("entities.unit", "SummonedCreatureDespawn: {}'s {}", me->GetName(), summon->GetName()); + if (summon == botPet) + botPet = nullptr; + } + + uint32 GetAIMiscValue(uint32 data) const override + { + switch (data) + { + case BOTAI_MISC_PET_TYPE: + return BOT_PET_SHADOWFIEND; + default: + return 0; + } + } + + void Reset() override + { + UnsummonAll(false); + + Shackle_Timer = 0; + Mend_Timer = 0; + + DispelcheckTimer = 0; + DevcheckTimer = 0; + ShackcheckTimer = 0; + + Devcheck = false; + Shackcheck = false; + + DefaultInit(); + } + + void ReduceCD(uint32 diff) override + { + if (Shackle_Timer > diff) Shackle_Timer -= diff; + if (Mend_Timer > diff) Mend_Timer -= diff; + + if (DispelcheckTimer > diff) DispelcheckTimer -= diff; + if (DevcheckTimer > diff) DevcheckTimer -= diff; + if (ShackcheckTimer > diff) ShackcheckTimer -= diff; + } + + void InitPowers() override + { + me->SetPowerType(POWER_MANA); + } + + void InitSpells() override + { + uint8 lvl = me->GetLevel(); + bool isDisc = GetSpec() == BOT_SPEC_PRIEST_DISCIPLINE; + bool isHoly = GetSpec() == BOT_SPEC_PRIEST_HOLY; + bool isShad = GetSpec() == BOT_SPEC_PRIEST_SHADOW; + + InitSpellMap(DISPEL_MAGIC_1); + InitSpellMap(MASS_DISPEL_1); + InitSpellMap(CURE_DISEASE_1); + InitSpellMap(ABOLISH_DISEASE_1); + InitSpellMap(FEAR_WARD_1); + InitSpellMap(PSYCHIC_SCREAM_1); + InitSpellMap(FADE_1); + InitSpellMap(MIND_SEAR_1); + InitSpellMap(SHACKLE_UNDEAD_1); + InitSpellMap(GREATER_HEAL_1); + InitSpellMap(NORMAL_HEAL_1); + InitSpellMap(LESSER_HEAL_1); + InitSpellMap(RENEW_1); + InitSpellMap(FLASH_HEAL_1); + InitSpellMap(PRAYER_OF_HEALING_1); + InitSpellMap(DIVINE_HYMN_1); + InitSpellMap(PRAYER_OF_MENDING_1); + InitSpellMap(RESURRECTION_1); + InitSpellMap(PW_SHIELD_1); + InitSpellMap(INNER_FIRE_1); + InitSpellMap(PW_FORTITUDE_1); + InitSpellMap(SHADOW_PROTECTION_1); + InitSpellMap(DIVINE_SPIRIT_1); + InitSpellMap(HOLY_FIRE_1); + InitSpellMap(SMITE_1); + InitSpellMap(SW_PAIN_1); + InitSpellMap(MIND_BLAST_1); + InitSpellMap(SW_DEATH_1); + InitSpellMap(DEVOURING_PLAGUE_1); + InitSpellMap(HYMN_OF_HOPE_1); + InitSpellMap(LEVITATE_1); + InitSpellMap(SHADOWFIEND_1); //not casted + + /*Talent*/lvl >= 20 && isDisc ? InitSpellMap(INNER_FOCUS_1) : RemoveSpell(INNER_FOCUS_1); + /*Talent*/lvl >= 40 && isDisc ? InitSpellMap(POWER_INFUSION_1) : RemoveSpell(POWER_INFUSION_1); + /*Talent*/lvl >= 50 && isDisc ? InitSpellMap(PAIN_SUPPRESSION_1) : RemoveSpell(PAIN_SUPPRESSION_1); + /*Talent*/lvl >= 60 && isDisc ? InitSpellMap(PENANCE_1) : RemoveSpell(PENANCE_1); + + /*Talent*/lvl >= 20 && isHoly ? InitSpellMap(DESPERATE_PRAYER_1) : RemoveSpell(DESPERATE_PRAYER_1); + /*Talent*/lvl >= 50 && isHoly ? InitSpellMap(CIRCLE_OF_HEALING_1) : RemoveSpell(CIRCLE_OF_HEALING_1); + /*Talent*/lvl >= 60 && isHoly ? InitSpellMap(GUARDIAN_SPIRIT_1) : RemoveSpell(GUARDIAN_SPIRIT_1); + + /*Talent*/lvl >= 20 && isShad ? InitSpellMap(MIND_FLAY_1) : RemoveSpell(MIND_FLAY_1); + /*Talent*/lvl >= 30 && isShad ? InitSpellMap(SILENCE_1) : RemoveSpell(SILENCE_1); + /*Talent*/lvl >= 30 && isShad ? InitSpellMap(VAMPIRIC_EMBRACE_1) : RemoveSpell(VAMPIRIC_EMBRACE_1); + /*Talent*/lvl >= 40 && isShad ? InitSpellMap(SHADOWFORM_1) : RemoveSpell(SHADOWFORM_1); + /*Talent*/lvl >= 50 && isShad ? InitSpellMap(VAMPIRIC_TOUCH_1) : RemoveSpell(VAMPIRIC_TOUCH_1); + /*Talent*/lvl >= 50 && isShad ? InitSpellMap(PSYCHIC_HORROR_1) : RemoveSpell(PSYCHIC_HORROR_1); + /*Talent*/lvl >= 60 && isShad ? InitSpellMap(DISPERSION_1) : RemoveSpell(DISPERSION_1); + + HEAL = GetSpell(GREATER_HEAL_1) ? GREATER_HEAL_1 : + GetSpell(NORMAL_HEAL_1) ? NORMAL_HEAL_1 : + GetSpell(LESSER_HEAL_1) ? LESSER_HEAL_1 : 0; + } + + void ApplyClassPassives() const override + { + uint8 level = master->GetLevel(); + bool isDisc = GetSpec() == BOT_SPEC_PRIEST_DISCIPLINE; + bool isHoly = GetSpec() == BOT_SPEC_PRIEST_HOLY; + bool isShad = GetSpec() == BOT_SPEC_PRIEST_SHADOW; + + RefreshAura(UNBREAKABLE_WILL, level >= 10 ? 1 : 0); + RefreshAura(MEDITATION, level >= 20 ? 1 : 0); + RefreshAura(RENEWED_HOPE, isDisc && level >= 45 ? 1 : 0); + RefreshAura(RAPTURE, isDisc && level >= 45 ? 1 : 0); + RefreshAura(DIVINE_AEGIS, isDisc && level >= 50 ? 1 : 0); + RefreshAura(GRACE, isDisc && level >= 50 ? 1 : 0); + RefreshAura(BORROWED_TIME, isDisc && level >= 55 ? 1 : 0); + + RefreshAura(INSPIRATION3, isHoly && level >= 25 ? 1 : 0); + RefreshAura(INSPIRATION2, isHoly && level >= 23 && level < 25 ? 1 : 0); + RefreshAura(INSPIRATION1, isHoly && level >= 20 && level < 23 ? 1 : 0); + RefreshAura(SURGE_OF_LIGHT, isHoly && level >= 35 ? 1 : 0); + RefreshAura(HOLY_CONCENTRATION, isHoly && level >= 40 ? 1 : 0); + RefreshAura(BODY_AND_SOUL1, isHoly && level >= 45 ? 1 : 0); + RefreshAura(SERENDIPITY, isHoly && level >= 45 ? 1 : 0); + RefreshAura(EMPOWERED_RENEW3, isHoly && level >= 55 ? 1 : 0); + RefreshAura(EMPOWERED_RENEW2, isHoly && level >= 53 && level < 55 ? 1 : 0); + RefreshAura(EMPOWERED_RENEW1, isHoly && level >= 50 && level < 53 ? 1 : 0); + + RefreshAura(SPIRIT_TAP, isShad && level >= 10 ? 1 : 0); + RefreshAura(IMPROVED_SPIRIT_TAP, isShad && level >= 10 ? 1 : 0); + RefreshAura(SHADOW_WEAVING3, isShad && level >= 30 ? 1 : 0); + RefreshAura(SHADOW_WEAVING2, isShad && level >= 28 && level < 30 ? 1 : 0); + RefreshAura(SHADOW_WEAVING1, isShad && level >= 25 && level < 28 ? 1 : 0); + RefreshAura(IMPROVED_DEVOURING_PLAGUE, isShad && level >= 35 ? 1 : 0); + RefreshAura(IMPROVED_SHADOWFORM, isShad && level >= 45 ? 1 : 0); + RefreshAura(MISERY3, isShad && level >= 50 ? 1 : 0); + RefreshAura(MISERY2, isShad && level >= 48 && level < 50 ? 1 : 0); + RefreshAura(MISERY1, isShad && level >= 45 && level < 48 ? 1 : 0); + + //RefreshAura(GLYPH_SW_PAIN, level >= 15 ? 1 : 0); + RefreshAura(GLYPH_PW_SHIELD, level >= 15 ? 1 : 0); + RefreshAura(GLYPH_DISPEL_MAGIC, level >= 18 ? 1 : 0); + RefreshAura(GLYPH_PRAYER_OF_HEALING, level >= 30 ? 1 : 0); + RefreshAura(GLYPH_SHADOW, level >= 30 ? 1 : 0); + RefreshAura(PRIEST_T10_2P_BONUS, level >= 70 ? 1 : 0); + } + + bool CanUseManually(uint32 basespell) const override + { + switch (basespell) + { + case MASS_DISPEL_1: + case ABOLISH_DISEASE_1: + case PAIN_SUPPRESSION_1: + case FADE_1: + case PENANCE_1: + case VAMPIRIC_EMBRACE_1: + case DISPERSION_1: + case GUARDIAN_SPIRIT_1: + case RENEW_1: + case PRAYER_OF_HEALING_1: + case CIRCLE_OF_HEALING_1: + case DIVINE_HYMN_1: + case PRAYER_OF_MENDING_1: + case PW_SHIELD_1: + case INNER_FIRE_1: + case PW_FORTITUDE_1: + case SHADOW_PROTECTION_1: + case DIVINE_SPIRIT_1: + case FEAR_WARD_1: + case FLASH_HEAL_1: + case GREATER_HEAL_1: + case LEVITATE_1: + return true; + case NORMAL_HEAL_1: + return !GetSpell(GREATER_HEAL_1); + case LESSER_HEAL_1: + return !GetSpell(NORMAL_HEAL_1) && !GetSpell(GREATER_HEAL_1); + case SHADOWFORM_1: + return me->GetShapeshiftForm() != FORM_SHADOW; + default: + return false; + } + } + + std::vector const* GetDamagingSpellsList() const override + { + return &Priest_spells_damage; + } + std::vector const* GetCCSpellsList() const override + { + return &Priest_spells_cc; + } + std::vector const* GetHealingSpellsList() const override + { + return &Priest_spells_heal; + } + std::vector const* GetSupportSpellsList() const override + { + return &Priest_spells_support; + } + + void InitHeals() override + { + SpellInfo const* spellInfo; + if (InitSpell(me, HEAL)) + { + spellInfo = sSpellMgr->GetSpellInfo(InitSpell(me, HEAL)); + _heals[HEAL] = me->SpellHealingBonusDone(me, spellInfo, spellInfo->_effects[0].CalcValue(me), DamageEffectType(3), spellInfo->GetEffect(EFFECT_0), {}); + } + else + _heals[HEAL] = 0; + + if (InitSpell(me, FLASH_HEAL_1)) + { + spellInfo = sSpellMgr->GetSpellInfo(InitSpell(me, FLASH_HEAL_1)); + _heals[FLASH_HEAL_1] = me->SpellHealingBonusDone(me, spellInfo, spellInfo->_effects[0].CalcValue(me), DamageEffectType(3), spellInfo->GetEffect(EFFECT_0), {}); + } + else + _heals[FLASH_HEAL_1] = 0; + + if (InitSpell(me, PENANCE_HEAL_1)) + { + spellInfo = sSpellMgr->GetSpellInfo(InitSpell(me, PENANCE_HEAL_1)); + _heals[PENANCE_1] = me->SpellHealingBonusDone(me, spellInfo, spellInfo->_effects[0].CalcValue(me), DamageEffectType(3), spellInfo->GetEffect(EFFECT_0), {}); + } + else + _heals[PENANCE_1] = 0; + + if (InitSpell(me, DESPERATE_PRAYER_1)) + { + spellInfo = sSpellMgr->GetSpellInfo(InitSpell(me, DESPERATE_PRAYER_1)); + _heals[DESPERATE_PRAYER_1] = me->SpellHealingBonusDone(me, spellInfo, spellInfo->_effects[0].CalcValue(me), DamageEffectType(3), spellInfo->GetEffect(EFFECT_0), {}); + } + else + _heals[DESPERATE_PRAYER_1] = 0; + } + + private: + uint32 HEAL; + uint32 Shackle_Timer, Mend_Timer, DispelcheckTimer, DevcheckTimer, ShackcheckTimer; +/*Misc*/bool Devcheck, Shackcheck; + + typedef std::unordered_map HealMap; + HealMap _heals; + }; +}; + +void AddSC_priest_bot() +{ + new priest_bot(); +} diff --git a/src/server/game/AI/NpcBots/bot_rogue_ai.cpp b/src/server/game/AI/NpcBots/bot_rogue_ai.cpp new file mode 100644 index 000000000..72637db21 --- /dev/null +++ b/src/server/game/AI/NpcBots/bot_rogue_ai.cpp @@ -0,0 +1,2055 @@ +#include "bot_ai.h" +#include "botmgr.h" +#include "bottext.h" +#include "bottraits.h" +#include "Creature.h" +#include "Group.h" +#include "Item.h" +#include "Map.h" +#include "MotionMaster.h" +#include "Player.h" +#include "ScriptMgr.h" +#include "SpellAuraEffects.h" +#include "Spell.h" +#include "SpellMgr.h" +#include "WorldSession.h" +/* +Rogue NpcBot (reworked by Trickerer onlysuffering@gmail.com) +Complete - 90% +TODO: +*/ + +enum RogueBaseSpells +{ + KICK_1 = 1766, + EXPOSE_ARMOR_1 = 8647, //NYI + FEINT_1 = 1966, + DISMANTLE_1 = 51722, + + BACKSTAB_1 = 53, + SINISTER_STRIKE_1 = 1752, + EVISCERATE_1 = 2098, + ENVENOM_1 = 32645, + RUPTURE_1 = 1943, + MUTILATE_1 = 1329, + HEMORRHAGE_1 = 16511, + GHOSTLY_STRIKE_1 = 14278, + RIPOSTE_1 = 14251, + DEADLY_THROW_1 = 26679, + FAN_OF_KNIVES_1 = 51723, + + SPRINT_1 = 2983, + EVASION_1 = 5277, + BLIND_1 = 2094, + VANISH_1 = 1856, + COLD_BLOOD_1 = 14177, + HUNGER_FOR_BLOOD_1 = 51662, + ADRENALINE_RUSH_1 = 13750, + KILLING_SPREE_1 = 51690, + PREPARATION_1 = 14185, + PREMEDITATION_1 = 14183, + + GOUGE_1 = 1776, + + KIDNEY_SHOT_1 = 408, + SLICE_DICE_1 = 5171, + BLADE_FLURRY_1 = 13877, + SHADOWSTEP_1 = 36554, + CLOAK_OF_SHADOWS_1 = 31224, + TRICKS_OF_THE_TRADE_1 = 57934, + SHADOW_DANCE_1 = 51713, + + STEALTH_1 = 1784, + SAP_1 = 6770, //NYI + GARROTE_1 = 703, + CHEAP_SHOT_1 = 1833, + AMBUSH_1 = 8676, + + DISTRACT_1 = 1725, //NYI + DISARM_TRAP_1 = 1842, //Unused, see bot_ai::ProcessImmediateNonAttackTarget() + + //Poisons + CRIPPLING_POISON_1 = 3408, + INSTANT_POISON_1 = 8679, + DEADLY_POISON_1 = 2823, + WOUND_POISON_1 = 13219, + MIND_NUMBING_POISON_1 = 5761, //manual use only + ANESTHETIC_POISON_1 = 26785, + + PICK_LOCK_1 = 1804 +}; + +enum RoguePassives +{ + //Talents + SEAL_FATE1 = 14189, + SEAL_FATE2 = 14190, + SEAL_FATE3 = 14193, + SEAL_FATE4 = 14194, + SEAL_FATE5 = 14195, + COMBAT_POTENCY1 = 35541, + COMBAT_POTENCY2 = 35550, + COMBAT_POTENCY3 = 35551, + COMBAT_POTENCY4 = 35552, + COMBAT_POTENCY5 = 35553, + QUICK_RECOVERY1 = 31244, + QUICK_RECOVERY2 = 31245, + //BLADE_TWISTING1 = 31124, + //BLADE_TWISTING2 = 31126, + DEADLY_BREW = 51626,//rank 2 + IMPROVED_KIDNEY_SHOT = 14176,//rank 3 + VIGOR = 14983, + REMORSELESS_ATTACKS = 14148,//rank 2 + FLEET_FOOTED = 31209,//rank 2 + MURDER = 14159,//rank 2 + OVERKILL = 58426, + FOCUSED_ATTACKS = 51636,//rank 3 + MASTER_POISONER = 58410,//rank 3 + DUAL_WIELD_SPECIALIZATION = 13852,//rank 5 + IMPROVED_KICK = 13867,//rank 2 + IMPROVED_SPRINT = 13875,//rank 2 + HACK_AND_SLASH = 13964,//rank 5 + VITALITY = 61329,//rank 3 + NERVES_OF_STEEL = 31131,//rank 2 + THROWING_SPECIALIZATION = 51679,//rank 2 + //SAVAGE_COMBAT = 58413,//rank 2 + UNFAIR_ADVANTAGE = 51674,//rank 2 + SURPRISE_ATTACKS = 32601, + PREY_ON_THE_WEAK = 51689,//rank 5 + MASTER_OF_DECEPTION = 13971,//rank 3 + SETUP = 14071,//rank 3 + INITIATIVE = 13980,//rank 3 + DIRTY_DEEDS = 14083,//rank 2 + MASTER_OF_SUBTLETY = 31223,//rank 3 + CHEAT_DEATH = 31230,//rank 3 + ENVELOPING_SHADOWS = 31213,//rank 3 + TURN_THE_TABLES = 51629,//rank 3 + HONOR_AMONG_THIEVES = 51701,//rank 3 + + //Other + VIGOR_GLADIATOR = 21975, + GLYPH_BACKSTAB = 56800, + + ROGUE_PASSIVE_DND = 21184 //from playercreateinfo_spell +}; + +enum RogueSpecial +{ + MUTILATE_DAMAGE_MAINHAND_1 = 5374, + MUTILATE_DAMAGE_OFFHAND_1 = 27576, + + //TURN_THE_TABLES_BUFF = 52910,//'rank 3' + HUNGER_FOR_BLOOD_BUFF = 63848, + WAYLAY_DEBUFF = 51693, + REMORSELESS_ATTACKS_BUFF = 14149, + CHEATING_DEATH_BUFF = 45182, //hidden + TRICKS_OF_THE_TRADE_BUFF = 57933, + + RELENTLESS_STRIKES_EFFECT = 14181, + RUTHLESSNESS_EFFECT = 14157, + SEAL_FATE_EFFECT = 14189, + SETUP_EFFECT = 15250, + INITIATIVE_EFFECT = 13977, + HONOR_AMONG_THIEVES_EFFECT = 51699, + + VANISH_TRIGGERED_1 = 11327, + VANISH_TRIGGERED_2 = 11329, + VANISH_TRIGGERED_3 = 26888, + + //Poisons + CRIPPLING_POISON_PROC_1 = 3409, + //INSTANT_POISON_PROC_1 = 8680, + DEADLY_POISON_PROC_1 = 2818, + WOUND_POISON_PROC_1 = 13218, + MIND_NUMBING_POISON_PROC_1 = 5760, + //ANESTHETIC_POISON_PROC_1 = 26688, + + THISTLE_TEA = 9512 //'Restore Energy' 1 min cd +}; + +static const uint32 Rogue_spells_damage_arr[] = +{ AMBUSH_1, BACKSTAB_1, DEADLY_THROW_1, EVISCERATE_1, ENVENOM_1, FAN_OF_KNIVES_1, GARROTE_1, GHOSTLY_STRIKE_1, GOUGE_1, +HEMORRHAGE_1, KILLING_SPREE_1, MUTILATE_1, RIPOSTE_1, RUPTURE_1, SINISTER_STRIKE_1 }; + +static const uint32 Rogue_spells_cc_arr[] = +{ BLIND_1, CHEAP_SHOT_1, /*DEADLY_THROW_1, */DISMANTLE_1, GOUGE_1, KICK_1, KIDNEY_SHOT_1, /*SAP_1*/ }; + +static const uint32 Rogue_spells_support_arr[] = +{ /*EXPOSE_ARMOR_1, DISTRACT_1, PICK_LOCK_1,*/ STEALTH_1, ADRENALINE_RUSH_1, BLADE_FLURRY_1, CLOAK_OF_SHADOWS_1, +COLD_BLOOD_1, DISMANTLE_1, EVASION_1, FEINT_1, HUNGER_FOR_BLOOD_1, PREMEDITATION_1, PREPARATION_1, SHADOW_DANCE_1, +SHADOWSTEP_1, SLICE_DICE_1, SPRINT_1, TRICKS_OF_THE_TRADE_1, VANISH_1, DISARM_TRAP_1, THISTLE_TEA, +/*CRIPPLING_POISON_1, INSTANT_POISON_1, DEADLY_POISON_1, WOUND_POISON_1, MIND_NUMBING_POISON_1, ANESTHETIC_POISON_1*/ }; + +static const std::vector Rogue_spells_damage(FROM_ARRAY(Rogue_spells_damage_arr)); +static const std::vector Rogue_spells_cc(FROM_ARRAY(Rogue_spells_cc_arr)); +static const std::vector Rogue_spells_support(FROM_ARRAY(Rogue_spells_support_arr)); + +class rogue_bot : public CreatureScript +{ +public: + rogue_bot() : CreatureScript("rogue_bot") { } + + CreatureAI* GetAI(Creature* creature) const override + { + return new rogue_botAI(creature); + } +/* + bool OnGossipHello(Player* player, Creature* creature) + { + return creature->GetBotAI()->OnGossipHello(player, 0); + } + + bool OnGossipSelect(Player* player, Creature* creature, uint32 sender, uint32 action) + { + if (bot_ai* ai = creature->GetBotAI()) + return ai->OnGossipSelect(player, creature, sender, action); + return true; + } + + bool OnGossipSelectCode(Player* player, Creature* creature, uint32 sender, uint32 action, char const* code) + { + if (bot_ai* ai = creature->GetBotAI()) + return ai->OnGossipSelectCode(player, creature, sender, action, code); + return true; + } +*/ + struct rogue_botAI : public bot_ai + { + rogue_botAI(Creature* creature) : bot_ai(creature) + { + _botclass = BOT_CLASS_ROGUE; + + InitUnitFlags(); + } + + bool doCast(Unit* victim, uint32 spellId) + { + if (CheckBotCast(victim, spellId) != SPELL_CAST_OK) + return false; + return bot_ai::doCast(victim, spellId); + } + + void StartAttack(Unit* u, bool force = false) + { + if (!bot_ai::StartAttack(u, force)) + return; + GetInPosition(force, u); + } + + void JustEnteredCombat(Unit* u) override { bot_ai::JustEnteredCombat(u); } + void KilledUnit(Unit* u) override { bot_ai::KilledUnit(u); } + void EnterEvadeMode(EvadeReason why = EVADE_REASON_OTHER) override { bot_ai::EnterEvadeMode(why); } + void MoveInLineOfSight(Unit* u) override { bot_ai::MoveInLineOfSight(u); } + void JustDied(Unit* u) override { comboPoints = 0; bot_ai::JustDied(u); } + + void getenergy() + { + energy = me->GetPower(POWER_ENERGY); + } + + int32 ecost(uint32 spellId) const + { + if (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId)) + return spellInfo->CalcPowerCost(me, spellInfo->GetSchoolMask()); + return 0; + } + + void UpdateAI(uint32 diff) override + { + if (combopointsSpent) + { + combopointsSpent = false; + comboPoints = 0; + } + + getenergy(); + + if (!GlobalUpdate(diff)) + return; + + DoVehicleActions(diff); + if (!CanBotAttackOnVehicle()) + return; + + if (IsPotionReady()) + { + if (GetHealthPCT(me) < 50) + DrinkPotion(false); + } + + CheckRacials(diff); + + if (!me->IsInCombat()) + DoNonCombatActions(diff); + + if (IsCasting()) + return; + + if (ProcessImmediateNonAttackTarget()) + return; + + CheckUsableItems(diff); + + CheckSprint(diff); + CheckCloakOfShadows(diff); + CheckVanish(diff); + + if (!CheckAttackTarget()) + { + if (!me->IsInCombat() && Rand() < 5 && me->HasAuraType(SPELL_AURA_MOD_STEALTH) && + !me->GetAuraEffect(SPELL_AURA_MOD_INCREASE_SPEED, SPELLFAMILY_ROGUE, 0x800, 0x0, 0x0) && //vanish + !(!HasRole(BOT_ROLE_DPS) && GetLastWMOArea() == 29476)) + me->RemoveAurasDueToSpell(STEALTH_1); + return; + } + + CheckBlind(diff); + CheckPreparation(diff); + CheckTricksOfTheTrade(diff); + + Attack(diff); + } + + void Attack(uint32 diff) + { + Unit* mytar = opponent ? opponent : disttarget ? disttarget : nullptr; + if (!mytar) + return; + + StartAttack(mytar, IsMelee()); + + CheckAttackState(); + if (!me->IsAlive() || !mytar->IsAlive()) + return; + + float dist = me->GetDistance(mytar); + + //Stealth (for Cooldown handling see bot_ai::ReleaseSpellCooldown) + //we don't want rogue to swith into stealth for no purpose + if (IsSpellReady(STEALTH_1, diff, false) && !me->IsInCombat() && !IsTank() && Rand() < 50 && dist < 28 && + (!me->HasAuraType(SPELL_AURA_PERIODIC_DAMAGE) || (mytar->GetTypeId() == TYPEID_PLAYER && dist < 6)) && + (me->GetLevel() >= 35 || (energy >= 40 && me->GetLevel() >= 30) || dist > 8) && !IsFlagCarrier(me)) + { + if (doCast(me, GetSpell(STEALTH_1))) + {} + } + + if (!CanAffectVictimAny(mytar, SPELL_SCHOOL_NORMAL)) + return; + + bool stealthed = me->HasAuraType(SPELL_AURA_MOD_STEALTH); + bool shadowdance = me->HasAuraType(SPELL_AURA_MOD_IGNORE_SHAPESHIFT); + + //Hunger for Blood + if (IsSpellReady(HUNGER_FOR_BLOOD_1, diff) && !shadowdance && HasRole(BOT_ROLE_DPS) && Rand() < 50 && dist < 30 && + mytar->HasAuraState(AURA_STATE_BLEEDING) && energy >= ecost(HUNGER_FOR_BLOOD_1) && + !me->GetAuraEffect(SPELL_AURA_MOD_DAMAGE_PERCENT_DONE, SPELLFAMILY_ROGUE, 0x0, 0x1000000, 0x0)) + { + if (doCast(mytar, GetSpell(HUNGER_FOR_BLOOD_1))) + return; + } + //Premeditation + if (IsSpellReady(PREMEDITATION_1, diff, false) && (stealthed || shadowdance) && + HasRole(BOT_ROLE_DPS) && comboPoints < 4 && dist < 15 && + (comboPoints == 0 || mytar->GetHealth() > me->GetMaxHealth() / 4)) + { + if (doCast(mytar, GetSpell(PREMEDITATION_1))) + {} + } + //Kick + if (IsSpellReady(KICK_1, diff, false) && !stealthed && dist <= 5 && Rand() < 70 && + energy >= ecost(KICK_1) && mytar->IsNonMeleeSpellCast(false,false,true)) + { + if (doCast(mytar, GetSpell(KICK_1))) + getenergy(); + } + //Killing Spree + if (IsSpellReady(KILLING_SPREE_1, diff) && !stealthed && !shadowdance && HasRole(BOT_ROLE_DPS) && + Rand() < (70 - energy) && dist < 10 && GetHealthPCT(me) > 25 && (!CCed(mytar) || dist > 5) && + (mytar->getAttackers().size() < 4 || mytar->GetMaxHealth() > me->GetMaxHealth() * 2) && + (mytar->GetHealth() > me->GetMaxHealth() / 2 || me->getAttackers().size() > 1)) + { + if (doCast(mytar, GetSpell(KILLING_SPREE_1))) + return; + } + //Gouge: if mytar is trying to attack/cast on us he will always try to face us + if (IsSpellReady(GOUGE_1, diff) && !stealthed && !shadowdance && HasRole(BOT_ROLE_DPS) && dist <= 5 && + Rand() < 30 && !CCed(mytar) && energy >= ecost(GOUGE_1) && + ((energy < 55 && mytar->getAttackers().size() <= 1 && !mytar->HasAuraType(SPELL_AURA_PERIODIC_DAMAGE)) || + mytar->IsNonMeleeSpellCast(false,false,true)) && mytar->HasInArc(float(M_PI), me)) + { + if (doCast(mytar, GetSpell(GOUGE_1))) + return; + } + //Blind: in pvp only for restealth + if (IsSpellReady(BLIND_1, diff) && !stealthed && !shadowdance && dist <= 15 && Rand() < 30 && + !CCed(mytar) && !mytar->IsTotem() && energy >= ecost(BLIND_1) && + ((energy <= 30 && mytar->GetTarget() == me->GetGUID() && + mytar->getAttackers().size() <= 1 && + !mytar->HasAuraType(SPELL_AURA_PERIODIC_DAMAGE) && + !me->HasAuraType(SPELL_AURA_PERIODIC_DAMAGE)) || + (mytar->GetTypeId() == TYPEID_UNIT && + !IsSpellReady(KICK_1, diff) && !IsSpellReady(GOUGE_1, diff) && + mytar->IsNonMeleeSpellCast(false,false,true)))) + { + if (doCast(mytar, GetSpell(BLIND_1))) + return; + } + //Blade Flurry + if (IsSpellReady(BLADE_FLURRY_1, diff) && HasRole(BOT_ROLE_DPS) && !stealthed && !shadowdance && + dist <= 5 && Rand() < 50 && energy >= ecost(BLADE_FLURRY_1) && !CCed(mytar) && + !me->GetAuraEffect(SPELL_AURA_MOD_MELEE_HASTE, SPELLFAMILY_ROGUE, 0x40000000, 0x800, 0x0) && + (mytar->GetTypeId() == TYPEID_PLAYER || mytar->GetHealth() > me->GetHealth() || FindSplashTarget(7, mytar))) + { + if (doCast(me, GetSpell(BLADE_FLURRY_1))) + return; + } + //Slice and Dice + if (IsSpellReady(SLICE_DICE_1, diff) && !shadowdance && HasRole(BOT_ROLE_DPS) && comboPoints > 0 && + Rand() < 110 && energy >= ecost(SLICE_DICE_1) && dist < 10 && + (me->getAttackers().size() <= 1 || !IsSpellReady(BLADE_FLURRY_1, diff)) && + !me->GetAuraEffect(SPELL_AURA_MOD_MELEE_HASTE, SPELLFAMILY_ROGUE, 0x40000, 0x0, 0x0)) + { + if (doCast(mytar, GetSpell(SLICE_DICE_1))) + return; + } + //Dismantle + if (IsSpellReady(DISMANTLE_1, diff) && !stealthed && !shadowdance && + mytar->GetHealth() >= me->GetHealth() / 2 && energy >= ecost(DISMANTLE_1) && dist <= 5 && + !CCed(mytar) && Rand() < (30 + 90*mytar->HasAuraType(SPELL_AURA_ALLOW_ONLY_ABILITY)) && + !mytar->HasAuraType(SPELL_AURA_MOD_DISARM) && + (mytar->GetTypeId() == TYPEID_PLAYER ? + mytar->ToPlayer()->GetWeaponForAttack(BASE_ATTACK) && mytar->ToPlayer()->IsUseEquipedWeapon(true) : + mytar->GetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID) && mytar->CanUseAttackType(BASE_ATTACK))) + { + if (doCast(mytar, GetSpell(DISMANTLE_1))) + return; + } + //Shadowstep + if (IsSpellReady(SHADOWSTEP_1, diff, false) && !IsTank() && HasRole(BOT_ROLE_DPS) && + Rand() < 50 && dist < 25 && energy >= ecost(SHADOWSTEP_1) && + (mytar->GetTypeId() != TYPEID_PLAYER || dist > 12 || CCed(me, true)) && + (mytar->GetTypeId() == TYPEID_PLAYER || mytar->GetVictim() != me) && + ((!stealthed && !shadowdance) || me->HasAuraWithMechanic(1<GetLevel() >= 20 && CCed(me, true) && Rand() < 35) || + (Rand() < (25 + 10*stealthed + 40*shadowdance) && dist > (20 - (5*stealthed + 10*shadowdance)))) && + !me->GetAuraEffect(SPELL_AURA_MOD_INCREASE_SPEED, SPELLFAMILY_ROGUE, 0x40, 0x0, 0x0)) + { + if (doCast(me, GetSpell(SPRINT_1))) + {} + } + //Evasion (no GCD) + if (IsSpellReady(EVASION_1, diff, false) && !stealthed && Rand() < 65 && !me->getAttackers().empty() && + GetHealthPCT(me) < 65 + 10 * me->getAttackers().size() && + !me->GetAuraEffect(SPELL_AURA_MOD_DODGE_PERCENT, SPELLFAMILY_ROGUE, 0x20, 0x0, 0x0)) + { + if (doCast(me, GetSpell(EVASION_1))) + return; //custom: do not skip animation + } + //Deadly Throw + if (IsSpellReady(DEADLY_THROW_1, diff) && !stealthed && !shadowdance && HasRole(BOT_ROLE_DPS) && + comboPoints > 0 && Rand() < 55 && dist < 30 && dist > 5 && energy >= ecost(DEADLY_THROW_1) && + ((_spec != BOT_SPEC_ROGUE_COMBAT) || mytar->IsNonMeleeSpellCast(false,false,true))) + { + Item const* thrown = GetEquips(BOT_SLOT_RANGED); + if (thrown && thrown->GetTemplate()->Class == ITEM_CLASS_WEAPON && + thrown->GetTemplate()->SubClass == ITEM_SUBCLASS_WEAPON_THROWN && + doCast(mytar, GetSpell(DEADLY_THROW_1))) + return; + } + + //if target is affected by gouge or blind just try to regen some energy + bool hasnormalstun = false; + int32 duration = 0; + //sizes of theese are typically 1, sometimes maybe 2 + Unit::AuraEffectList const& stunAuras = mytar->GetAuraEffectsByType(SPELL_AURA_MOD_STUN); + for (Unit::AuraEffectList::const_iterator itr = stunAuras.begin(); itr != stunAuras.end(); ++itr) + { + if (!((*itr)->GetSpellInfo()->AuraInterruptFlags & AURA_INTERRUPT_FLAG_TAKE_DAMAGE) && + (*itr)->GetBase()->GetDuration() > 2000) + { + hasnormalstun = true; + break; + } + if ((*itr)->GetBase()->GetDuration() > duration) + duration = (*itr)->GetBase()->GetDuration(); + } + if (!hasnormalstun) + { + Unit::AuraEffectList const& confuseAuras = mytar->GetAuraEffectsByType(SPELL_AURA_MOD_CONFUSE); + for (Unit::AuraEffectList::const_iterator itr = confuseAuras.begin(); itr != confuseAuras.end(); ++itr) + { + if (!((*itr)->GetSpellInfo()->AuraInterruptFlags & AURA_INTERRUPT_FLAG_TAKE_DAMAGE) && + (*itr)->GetBase()->GetDuration() > 2000) + { + hasnormalstun = true; + break; + } + if ((*itr)->GetBase()->GetDuration() > duration) + duration = (*itr)->GetBase()->GetDuration(); + } + } + + if (mytar->IsControlledByPlayer() || me->GetHealthPct() < 25.f) + { + //Vanish (no GCD) + if (IsSpellReady(VANISH_1, diff, false) && !stealthed && !shadowdance && !IsTank() && Rand() < 45 && !me->HasAuraType(SPELL_AURA_PERIODIC_DAMAGE)) + { + bool cast = false; + //case 1: restealth for opener + if (!hasnormalstun && duration < 500 && me->IsInCombat() && dist <= 5) + cast = true; + //case 2: evade casted spell + if (!cast) + { + if (Spell const* spell = mytar->GetCurrentSpell(CURRENT_GENERIC_SPELL)) + { + if (spell->m_targets.GetUnitTarget() == me && spell->GetTimer() < 500 && + spell->GetSpellInfo()->HasEffect(SPELL_EFFECT_SCHOOL_DAMAGE)) + cast = true; + } + } + //case 3: reset threat / evade in CheckVanish (regardless of mytar availability) + if (cast && doCast(me, GetSpell(VANISH_1))) + return; //custom: do not skip animation + } + } + + if (dist > 5) + { + //if (mytar->IsPolymorphed()) + // TC_LOG_ERROR("entities.player", "rogue_bot: cannot attack target (dist)..."); + return; + } + + MoveBehind(mytar); + + if (IsSpellReady(THISTLE_TEA, diff, false) && !hasnormalstun && duration < 1000 && + energy <= std::max(me->GetMaxPower(POWER_ENERGY) - 110, 10)) + { + if (doCast(me, THISTLE_TEA)) + getenergy(); + } + + //No IsSpellReady checks for spells with no cd below + if (GC_Timer > diff) + return; + + //Feint + if (mytar->CanHaveThreatList()) + { + if (IsSpellReady(FEINT_1, diff) && !stealthed && !IsTank() && mytar->GetVictim() == me && Rand() < 35 && + energy >= ecost(FEINT_1) && int32(mytar->GetThreatManager().GetThreatListSize()) > 1 && + int32(mytar->getAttackers().size()) > 1) + { + if (doCast(mytar, GetSpell(FEINT_1))) + return; + } + } + + //Adrenaline Rush + if (GetSpell(ADRENALINE_RUSH_1) && !stealthed && !shadowdance && HasRole(BOT_ROLE_DPS) && (hasnormalstun || duration < 1300) && Rand() < 40 && + energy < 50 && GetHealthPCT(me) > 35 && + (mytar->getAttackers().size() < 3 || mytar->GetMaxHealth() > me->GetMaxHealth() * 2) && + (mytar->GetHealth() > me->GetMaxHealth() / 2 || me->getAttackers().size() > 1)) + { + if (doCast(me, GetSpell(ADRENALINE_RUSH_1))) + return; + } + + DiminishingLevels const stunDivider = mytar->GetDiminishing(DIMINISHING_OPENING_STUN); + + bool hasHunger = me->GetAuraEffect(SPELL_AURA_MOD_DAMAGE_PERCENT_DONE, SPELLFAMILY_ROGUE, 0x0, 0x1000000, 0x0); + + //Rupture: little troll with applying rupture on target without breaking gouge (creatures only, pvp - restealth) + if (GetSpell(RUPTURE_1) && !stealthed && !shadowdance && HasRole(BOT_ROLE_DPS) && + comboPoints > ((hasHunger || !GetSpell(HUNGER_FOR_BLOOD_1)) ? 1 : 0) && + !(hasHunger && GetSpec() == BOT_SPEC_ROGUE_ASSASINATION && GetSpell(ENVENOM_1)) && + (hasnormalstun || (mytar->CanHaveThreatList() && duration < 2000)) && + (comboPoints < 4 || !GetSpell(KIDNEY_SHOT_1) || stunDivider > DIMINISHING_LEVEL_2) && + energy >= ecost(RUPTURE_1) && mytar->GetHealth() > me->GetMaxHealth() / 4 * (1 + mytar->getAttackers().size()) && + Rand() < (40 + 40 * (mytar->GetTypeId() == TYPEID_PLAYER && IsMeleeClass(mytar->GetClass()))) && + !mytar->GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_ROGUE, 0x100000, 0x0, 0x0, me->GetGUID())) + { + if (doCast(mytar, GetSpell(RUPTURE_1))) + return; + } + + if (!hasnormalstun && duration > 300 && uint32(energy) < me->GetMaxPower(POWER_ENERGY)) + { + //TC_LOG_ERROR("entities.player", "bot_rogue: delaying attacks on gouged or blinded target..."); + return; + } + + //Finishers + if (comboPoints > 0) + { + //Kidney Shot + if (GetSpell(KIDNEY_SHOT_1) && !stealthed && stunDivider < DIMINISHING_LEVEL_4 && + Rand() < 80 && !CCed(mytar) && + !IsImmunedToMySpellEffect(mytar, sSpellMgr->GetSpellInfo(KIDNEY_SHOT_1), EFFECT_0) && + ((comboPoints >= 4 && stunDivider < DIMINISHING_LEVEL_3 && + (mytar->GetHealth() > me->GetMaxHealth() / 2 || mytar->GetTypeId() == TYPEID_PLAYER)) || + mytar->IsNonMeleeSpellCast(false,false,true)) && + energy >= ecost(KIDNEY_SHOT_1)) + { + if (doCast(mytar, GetSpell(KIDNEY_SHOT_1))) + return; + } + //Envenom / Eviscerate + uint32 envescerate = (GetSpec() == BOT_SPEC_ROGUE_ASSASINATION && GetSpell(ENVENOM_1) && + (mytar->GetHealth() > me->GetMaxHealth() / 5 || !GetSpell(EVISCERATE_1))) ? ENVENOM_1 : GetSpell(EVISCERATE_1) ? EVISCERATE_1 : 0; + if (envescerate && IsSpellReady(envescerate, diff) && !stealthed && !shadowdance && HasRole(BOT_ROLE_DPS) && + (comboPoints >= 4 || (envescerate == EVISCERATE_1 && mytar->GetHealth() < me->GetMaxHealth() / 4)) && + (envescerate != ENVENOM_1 || + (mytar->GetAuraEffect(SPELL_AURA_MOD_ATTACKER_SPELL_AND_WEAPON_CRIT_CHANCE, SPELLFAMILY_ROGUE, 0x10000, 0x0, 0x0, me->GetGUID()) && + (energy >= 110 || !me->HasAuraTypeWithFamilyFlags(SPELL_AURA_ADD_FLAT_MODIFIER, SPELLFAMILY_ROGUE, 0x800000)))) && + energy >= ecost(envescerate) && Rand() < (70 + comboPoints * 20)) + { + //Cold Blood (no GCD) + if (IsSpellReady(COLD_BLOOD_1, diff, false) && comboPoints > 3 && Rand() > 50 + 100*(comboPoints == 5)) + if (doCast(me, GetSpell(COLD_BLOOD_1))) + {} + if (doCast(mytar, GetSpell(envescerate))) + return; + } + } + + //Shadow Dance: if have energy or under effect of Adrenaline Rush + if (IsSpellReady(SHADOW_DANCE_1, diff, false) && !stealthed && HasRole(BOT_ROLE_DPS) && Rand() < 55 && + GetHealthPCT(me) > 40 && (stunDivider == DIMINISHING_LEVEL_1 || CCed(mytar)) && + (energy >= 60 || (energy >= 40 && me->GetAuraEffect(SPELL_AURA_MOD_POWER_REGEN_PERCENT, SPELLFAMILY_ROGUE, 0x0, 0x80, 0x0))) && + (mytar->GetTypeId() == TYPEID_PLAYER || mytar->GetHealth() > (me->GetMaxHealth() / 4) * mytar->getAttackers().size())) + { + if (doCast(me, GetSpell(SHADOW_DANCE_1))) + {} + } + + //Openers + if (stealthed || shadowdance) + { + uint32 opener = + GetSpell(CHEAP_SHOT_1) && + !mytar->HasAuraType(SPELL_AURA_MOD_STUN) && stunDivider < DIMINISHING_LEVEL_3 && + (mytar->GetTypeId() == TYPEID_PLAYER || (!IAmFree() && master->GetNpcBotsCount() > 1)) ? CHEAP_SHOT_1 : + GetSpell(GARROTE_1) && HasRole(BOT_ROLE_DPS) && mytar->GetHealth() > me->GetMaxHealth() / 4 && + !IsImmunedToMySpellEffect(mytar, sSpellMgr->GetSpellInfo(GARROTE_1), EFFECT_0) && + (!isdaggerMH || (mytar->GetTypeId() == TYPEID_PLAYER && + (mytar->GetClass() == CLASS_MAGE || mytar->GetClass() == CLASS_PRIEST || mytar->GetClass() == CLASS_WARLOCK))) && + !mytar->GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_ROGUE, 0x100, 0x0, 0x0, me->GetGUID()) ? GARROTE_1 : + GetSpell(AMBUSH_1) && HasRole(BOT_ROLE_DPS) && isdaggerMH ? AMBUSH_1 : + GetSpell(BACKSTAB_1) && HasRole(BOT_ROLE_DPS) && isdaggerMH ? BACKSTAB_1 : 0; + + //all opener spells disabled/unusable + if (!opener) + { + if (stealthed && HasRole(BOT_ROLE_DPS)) + me->RemoveAurasByType(SPELL_AURA_MOD_STEALTH); + //if (shadowdance) + // me->RemoveAurasByType(SPELL_AURA_MOD_IGNORE_SHAPESHIFT); + + return; + } + //openers from behind (backstab too) + if (opener != CHEAP_SHOT_1 && mytar->HasInArc(float(M_PI), me)) + return; + + //We do not check combo points amount + if (energy >= ecost(opener)) + { + if (doCast(mytar, GetSpell(opener))) + return; + } + + return; + } + + if (!HasRole(BOT_ROLE_DPS)) + return; + + //Fan of Knives + if (GetSpell(FAN_OF_KNIVES_1) && energy >= ecost(FAN_OF_KNIVES_1) && + Rand() < 35 + + 40*(me->GetAuraEffect(SPELL_AURA_MOD_POWER_REGEN_PERCENT, SPELLFAMILY_ROGUE, 0x0, 0x80, 0x0) != nullptr) + + 50*(me->GetAuraEffect(SPELL_AURA_MOD_MELEE_HASTE, SPELLFAMILY_ROGUE, 0x40000000, 0x0, 0x0) != nullptr) + /*Adrenaline Rush and Blade Flurry*/) + { + std::list targets; + GetNearbyTargetsList(targets, 7.f, 1); + if (targets.size() > 2 && doCast(me, GetSpell(FAN_OF_KNIVES_1))) + return; + } + + //Combo points generating + //Riposte: only after parry + if (IsSpellReady(RIPOSTE_1, diff) && comboPoints < 5 && me->HasReactive(REACTIVE_DEFENSE) && + energy >= ecost(RIPOSTE_1)) + { + if (doCast(mytar, GetSpell(RIPOSTE_1))) + return; + } + //Ghostly Strike: tank mode only + if (IsSpellReady(GHOSTLY_STRIKE_1, diff) && comboPoints < 5 && IsTank() && !me->getAttackers().empty() && + energy >= ecost(GHOSTLY_STRIKE_1)) + { + if (doCast(mytar, GetSpell(GHOSTLY_STRIKE_1))) + return; + } + //Hemorrhage: does not stack from different casters + if (GetSpell(HEMORRHAGE_1) && !isdaggerMH && comboPoints < 5 && !IsTank() && energy >= ecost(HEMORRHAGE_1) && + !mytar->GetAuraEffect(SPELL_AURA_MOD_DAMAGE_TAKEN, SPELLFAMILY_ROGUE, 0x800000, 0x0, 0x0)) + { + if (doCast(mytar, GetSpell(HEMORRHAGE_1))) + return; + } + //Sinister Strike: tank mode + if (GetSpell(SINISTER_STRIKE_1) && comboPoints < 5 && + (!isdaggerMH || IsTank() || (mytar->GetVictim() == me && energy >= 60 && mytar->HasInArc(float(M_PI), me)) || !GetSpell(BACKSTAB_1)) && + energy >= ecost(SINISTER_STRIKE_1)) + { + if (doCast(mytar, GetSpell(SINISTER_STRIKE_1))) + return; + } + //Backstab/Mutilate + uint32 mutistab = + isdaggerMH && isdaggerOH && GetSpell(MUTILATE_1) ? MUTILATE_1 : + isdaggerMH && GetSpell(BACKSTAB_1) ? BACKSTAB_1 : 0; + if (mutistab && comboPoints < 4 && energy >= ecost(mutistab) && (mutistab == MUTILATE_1 || !mytar->HasInArc(float(M_PI), me))) + { + if (doCast(mytar, GetSpell(mutistab))) + return; + } + } + + void BreakCC(uint32 diff) override + { + if (me->IsInCombat() && Rand() < 25) + { + bool canVanish = IsSpellReady(VANISH_1, diff, false); + bool canSprint = (GetSpec() == BOT_SPEC_ROGUE_COMBAT) && me->GetLevel() >= 25 && !HasBotCommandState(BOT_COMMAND_STAY) && IsSpellReady(SPRINT_1, diff, false); + if ((canVanish || canSprint) && me->HasAuraWithMechanic((1< diff || me->IsMounted() || IsCasting() || Rand() > 25) + return; + + if (mhEnchantExpireTimer > 0 && mhEnchantExpireTimer <= diff) + RemoveItemClassEnchantment(BOT_SLOT_MAINHAND); + if (ohEnchantExpireTimer > 0 && ohEnchantExpireTimer <= diff) + RemoveItemClassEnchantment(BOT_SLOT_OFFHAND); + + // Weapon Enchants + if (me->isMoving()) + return; + uint8 lvl = me->GetLevel(); + if (lvl < 20) + return; + + Item* mhWeapon = GetEquips(BOT_SLOT_MAINHAND); + Item* ohWeapon = GetEquips(BOT_SLOT_OFFHAND); + + bool mhReady = mhWeapon && !mhWeapon->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT); + bool ohReady = ohWeapon && !ohWeapon->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT); + if (!mhReady && !ohReady) + return; //no ecnhantable weapons + + //OK choose the poisons + //MH 20+ Instant, 32+ Wound, envenom Instant + //OH 20+ Crippling, 40+ Instant (deadly brew inc), 68+ Anesthetic, envenom Deadly + if (needChooseMHEnchant && mhReady) + mhEnchant = (GetSpec() == BOT_SPEC_ROGUE_ASSASINATION && GetSpell(ENVENOM_1)) ? INSTANT_POISON_1 : + lvl >= 32 ? WOUND_POISON_1 : + lvl >= 20 ? INSTANT_POISON_1 : 0; + + if (needChooseOHEnchant && ohReady) + ohEnchant = (GetSpec() == BOT_SPEC_ROGUE_ASSASINATION && GetSpell(ENVENOM_1)) ? DEADLY_POISON_1 : + lvl >= 68 ? ANESTHETIC_POISON_1 : + lvl >= 40 ? INSTANT_POISON_1 : + lvl >= 20 ? CRIPPLING_POISON_1 : 0; + + uint32 MhPoison = !mhReady ? 0 : GetSpell(mhEnchant); + uint32 OhPoison = !ohReady ? 0 : GetSpell(ohEnchant); + + SpellInfo const* MhPoisonInfo = mhReady && MhPoison ? sSpellMgr->GetSpellInfo(MhPoison) : nullptr; + SpellInfo const* OhPoisonInfo = ohReady && OhPoison ? sSpellMgr->GetSpellInfo(OhPoison) : nullptr; + + Item* targetWeapon = nullptr; + SpellInfo const* targetInfo = nullptr; + + if (mhReady && MhPoison && mhWeapon->IsFitToSpellRequirements(MhPoisonInfo)) + { + targetWeapon = mhWeapon; + targetInfo = MhPoisonInfo; + } + if (!targetWeapon && ohReady && OhPoison && ohWeapon->IsFitToSpellRequirements(OhPoisonInfo)) + { + targetWeapon = ohWeapon; + targetInfo = OhPoisonInfo; + } + if (targetWeapon) + { + Spell* spell = new Spell(me, targetInfo, TRIGGERED_NONE); + SpellCastTargets targets; + targets.SetItemTarget(targetWeapon); + spell->prepare(targets); + return; + } + } + + void CheckVanish(uint32 diff) + { + if (!IsSpellReady(VANISH_1, diff, false) || !me->IsInCombat() || me->IsMounted() || IsTank() || Rand() > 50 || + me->getAttackers().empty() || IsFlagCarrier(me) || me->HasAuraType(SPELL_AURA_MOD_STEALTH) || + me->HasAuraType(SPELL_AURA_ALLOW_ONLY_ABILITY) || me->HasAuraType(SPELL_AURA_PERIODIC_DAMAGE)) + return; + + if (GetHealthPCT(me) < 30 + 20*me->getAttackers().size() || + (!IAmFree() && GetHealthPCT(me) < 70 && master->GetNpcBotsCount() > 1)) + { + //Unit* victim = me->GetVictim(); + if (doCast(me, GetSpell(VANISH_1))) + return; + } + } + + void CheckCloakOfShadows(uint32 diff) + { + if (!IsSpellReady(CLOAK_OF_SHADOWS_1, diff) || !me->IsInCombat() || me->IsMounted() || + Rand() > 40 + 60 * me->GetMap()->IsDungeon()) + return; + + uint32 count = 0; + + //dispel debuffs + uint32 const dispelMask = DISPEL_ALL_MASK; + Unit::AuraApplicationMap const& Auras = me->GetAppliedAuras(); + for (Unit::AuraApplicationMap::const_iterator iter = Auras.begin(); iter != Auras.end(); ++iter) + { + // remove all harmful spells on you... + SpellInfo const* spellInfo = iter->second->GetBase()->GetSpellInfo(); + if ((spellInfo->DmgClass == SPELL_DAMAGE_CLASS_MAGIC || (spellInfo->GetDispelMask() & dispelMask)) && + !iter->second->IsPositive() && !iter->second->GetBase()->IsPassive()) + { + if (spellInfo->HasAura(SPELL_AURA_PERIODIC_DAMAGE) || + spellInfo->HasAura(SPELL_AURA_MOD_SPEED_SLOW_ALL) || + spellInfo->HasAura(SPELL_AURA_HASTE_SPELLS)) + if (++count > 1) + break; + } + } + + //defend from enemy cast cast + if (Unit const* target = FindCastingTarget(50)) + { + if (Spell const* spell = target->GetCurrentSpell(CURRENT_GENERIC_SPELL)) + { + if (spell->GetTimer() < 1000 && !spell->GetSpellInfo()->IsPassive() && !spell->GetSpellInfo()->IsPositive() && + !(spell->GetSpellInfo()->Attributes & (SPELL_ATTR0_ABILITY|SPELL_ATTR0_UNAFFECTED_BY_INVULNERABILITY))) + { + //direct spell + if (spell->m_targets.GetUnitTarget() == me && + spell->GetSpellInfo()->DmgClass == SPELL_DAMAGE_CLASS_MAGIC && + me->IsWithinLOSInMap(target, LINEOFSIGHT_ALL_CHECKS, VMAP::ModelIgnoreFlags::M2)) + { + count += 2; + } + //area spell + if ((spell->GetSpellInfo()->_effects[0].IsEffect() && + spell->GetSpellInfo()->_effects[0].TargetB.GetSelectionCategory() == TARGET_SELECT_CATEGORY_NEARBY) || + (spell->GetSpellInfo()->_effects[1].IsEffect() && + spell->GetSpellInfo()->_effects[1].TargetB.GetSelectionCategory() == TARGET_SELECT_CATEGORY_NEARBY)) + { + count += 2; + } + } + } + } + + if (!(count > 1)) + return; + + if (doCast(me, GetSpell(CLOAK_OF_SHADOWS_1))) + return; + } + + void CheckBlind(uint32 diff) + { + if (!IsSpellReady(BLIND_1, diff) || !me->IsInCombat() || me->IsMounted() || IsTank() || Rand() > 40 || + me->HasAuraType(SPELL_AURA_MOD_STEALTH) || me->HasAuraType(SPELL_AURA_ALLOW_ONLY_ABILITY) || + IsSpellReady(BLADE_FLURRY_1, diff, false) || IsSpellReady(EVASION_1, diff, false) || + me->GetAuraEffect(SPELL_AURA_MOD_DODGE_PERCENT, SPELLFAMILY_ROGUE, 0x20, 0x0, 0x0) ||//evasion + me->GetAuraEffect(SPELL_AURA_MOD_MELEE_HASTE, SPELLFAMILY_ROGUE, 0x40000000, 0x800, 0x0)) + return; + + Unit* u = FindStunTarget(15); //improved always (base 10, improved 15) + if (!u) + u = FindCastingTarget(15, 0, BLIND_1); + + if (u && doCast(u, GetSpell(BLIND_1))) + return; + } + + void CheckPreparation(uint32 diff) + { + if (!IsSpellReady(PREPARATION_1, diff) || !me->IsInCombat() || me->IsMounted() || Rand() > 30) + return; + + //TODO: recheck priorities + uint32 needFactor = 0; + uint32 cooldown; + cooldown = GetSpellCooldown(EVASION_1); + needFactor += !cooldown ? 0 : 3 * cooldown / 1200; //1-100 x3 + cooldown = GetSpellCooldown(SPRINT_1); + needFactor += !cooldown ? 0 : 1 * cooldown / 1200; //1-100 + cooldown = GetSpellCooldown(VANISH_1); + needFactor += !cooldown ? 0 : 3 * cooldown / 1200; //1-100 x3 + cooldown = GetSpellCooldown(COLD_BLOOD_1); + needFactor += !cooldown ? 0 : 2 * cooldown / 1800; //1-100 x2 + cooldown = GetSpellCooldown(SHADOWSTEP_1); + needFactor += !cooldown ? 0 : 2 * cooldown / 200; //1-100 x2 + cooldown = GetSpellCooldown(BLADE_FLURRY_1); + needFactor += !cooldown ? 0 : 1 * cooldown / 1200; //1-100 + cooldown = GetSpellCooldown(DISMANTLE_1); + needFactor += !cooldown ? 0 : 1 * cooldown / 600; //1-100 + //0-1300 + //ignore Kick + + if (needFactor >= 800 && doCast(me, GetSpell(PREPARATION_1))) + return; + } + + void CheckTricksOfTheTrade(uint32 diff) + { + if (!IsSpellReady(TRICKS_OF_THE_TRADE_1, diff) || !me->IsInCombat() || me->IsMounted() || IAmFree() || + IsTank() || Rand() > 30 || !me->GetMap()->IsDungeon() || + me->HasAuraType(SPELL_AURA_MOD_STEALTH) || me->HasAuraType(SPELL_AURA_ALLOW_ONLY_ABILITY)) + return; + + Group const* group = master->GetGroup(); + if (!group) + return; + Unit* victim = me->GetVictim(); + if (!victim) + return; + + Unit* target = nullptr; + for (GroupReference const* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* player = itr->GetSource(); + if (!player || !player->IsInWorld() || !player->IsAlive() || me->GetMap() != player->FindMap() || + me->GetDistance(player) > 20) + continue; + + if (IsTank(player) || player->GetVictim() == victim) + { + if (!victim->CanHaveThreatList() || + victim->GetThreatManager().GetThreat(player) < victim->GetThreatManager().GetThreat(me) * 0.75f) + { + target = player; + break; + } + } + } + + if (target) + if (doCast(target, GetSpell(TRICKS_OF_THE_TRADE_1))) + return; + } + + void CheckSprint(uint32 diff) + { + if (!IsSpellReady(SPRINT_1, diff) || (!IAmFree() && !HasBotCommandState(BOT_COMMAND_FOLLOW)) || Rand() > 35 || me->IsMounted()) + return; + + if (IAmFree()) + { + InstanceTemplate const* instt = sObjectMgr->GetInstanceTemplate(me->GetMap()->GetId()); + bool map_allows_mount = (!me->GetMap()->IsDungeon() || me->GetMap()->IsBattlegroundOrArena()) && (!instt || instt->AllowMount); + if (me->HasUnitMovementFlag(MOVEMENTFLAG_FORWARD) && + (!me->GetVictim() ? (!map_allows_mount || me->IsInCombat() || IsFlagCarrier(me)) : !me->IsWithinDist(me->GetVictim(), 8.0f))) + { + if (doCast(me, GetSpell(SPRINT_1))) + return; + } + + return; + } + + if (me->GetExactDist2d(master) > std::max(master->GetBotMgr()->GetBotFollowDist(), 45)) + { + if (doCast(me, GetSpell(SPRINT_1))) + return; + } + } + + void ApplyClassSpellCritMultiplierAll(Unit const* /*victim*/, float& crit_chance, SpellInfo const* spellInfo, SpellSchoolMask /*schoolMask*/, WeaponAttackType /*attackType*/) const override + { + if (spellInfo->DmgClass != SPELL_DAMAGE_CLASS_MELEE) + return; + + //uint32 spellId = spellInfo->Id; + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + uint8 lvl = me->GetLevel(); + + //Cold Blood + if (AuraEffect const* bloo = me->GetAuraEffect(COLD_BLOOD_1, 0, me->GetGUID())) + if (bloo->IsAffectingSpell(spellInfo)) + crit_chance += 100.f; + + //Puncturing Wounds: + if (lvl >= 15) + { + //30% additional critical chance for Backstab + if (baseId == BACKSTAB_1) + crit_chance += 30.f; + //Puncturing Wounds: 15% additional critical chance for Mutilate + else if (baseId == MUTILATE_1 || + baseId == MUTILATE_DAMAGE_MAINHAND_1 || baseId == MUTILATE_DAMAGE_OFFHAND_1) + crit_chance += 15.f; + } + //Glyph of Eviscerate: 10% additional critical chance for Eviscerate + if (lvl >= 15 && baseId == EVISCERATE_1) + crit_chance += 10.f; + //Improved Ambush: 50% additional critical chance for Ambush + if ((GetSpec() == BOT_SPEC_ROGUE_SUBTLETY) && lvl >= 25 && baseId == AMBUSH_1) + crit_chance += 50.f; + //Turn the Tables: + if (lvl >= 50 && + ((spellInfo->SpellFamilyFlags[0] & 0x2600070E) || (spellInfo->SpellFamilyFlags[1] & 0x7900106)) && + me->GetAuraEffect(SPELL_AURA_ADD_FLAT_MODIFIER, SPELLFAMILY_ROGUE, 0x0, 0x200000, 0x0)) + crit_chance += 6.f; + //Remorseless Attacks: + if (AuraEffect const* remo = me->GetAuraEffect(REMORSELESS_ATTACKS_BUFF, 0, me->GetGUID())) + if (remo->IsAffectingSpell(spellInfo)) + crit_chance += 40.f; + } + + void ApplyClassDamageMultiplierMeleeSpell(int32& damage, SpellNonMeleeDamage& /*damageinfo*/, SpellInfo const* spellInfo, WeaponAttackType /*attackType*/, bool iscrit) const override + { + //uint32 spellId = spellInfo->Id; + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + uint8 lvl = me->GetLevel(); + float fdamage = float(damage); + + //apply bonus damage mods + float pctbonus = 0.0f; + if (iscrit) + { + //!!!Melee spell damage is not yet critical, all reduced by half + //Lethality: 30% crit damage bonus for non-stealth combo-generating abilities (on 25 lvl) + if (lvl >= 25 && + (baseId == SINISTER_STRIKE_1 || baseId == BACKSTAB_1 || baseId == MUTILATE_1 || baseId == RIPOSTE_1 || + baseId == GOUGE_1 || baseId == HEMORRHAGE_1 || baseId == GHOSTLY_STRIKE_1 + /*|| baseId == SHIV_1*/ || baseId == MUTILATE_DAMAGE_MAINHAND_1 || baseId == MUTILATE_DAMAGE_OFFHAND_1)) + pctbonus += 0.15f; + } + + //DeathDealer set bonus: 15% damage bonus for Eviscerate + if (lvl >= 60 && baseId == EVISCERATE_1) + pctbonus += 0.15f; + //Find Weakness: 6% bonus damage to all abilities + if ((GetSpec() == BOT_SPEC_ROGUE_ASSASINATION) && lvl >= 45) + pctbonus += 0.06f; + //Improved Eviscerate: 20% damage bonus for Eviscerate + if (lvl >= 10 && baseId == EVISCERATE_1) + pctbonus += 0.2f; + //Opportunity: 20% damage bonus for Backstab, Mutilate, Garrote and Ambush + if (lvl >= 10 && + (baseId == BACKSTAB_1 || baseId == MUTILATE_1 || baseId == MUTILATE_DAMAGE_MAINHAND_1 || + baseId == MUTILATE_DAMAGE_OFFHAND_1 || baseId == GARROTE_1 || baseId == AMBUSH_1)) + pctbonus += 0.2f; + //Aggression: 15% damage bonus for Sinister Strike, Backstab and Eviscerate + if ((GetSpec() == BOT_SPEC_ROGUE_COMBAT) && + lvl >= 25 && (baseId == SINISTER_STRIKE_1 || baseId == BACKSTAB_1 || baseId == EVISCERATE_1)) + pctbonus += 0.15f; + //Blood Spatter: 30% bonus damage for Rupture and Garrote + if (lvl >= 15 && (baseId == RUPTURE_1 || baseId == GARROTE_1)) + pctbonus += 0.3f; + //Vile Poisons: 20% damage bonus for Poisons and Envenom + if (lvl >= 25 && ((spellInfo->SpellFamilyFlags[0] & 0x10012000) || (spellInfo->SpellFamilyFlags[1] & 0x18))) + pctbonus += 0.2f; + //Serrated Blades part 2: 30% bonus damage for Rupture + if (lvl >= 20 && baseId == RUPTURE_1) + pctbonus += 0.3f; + //Surprise Attacks: 10% bonus damage for Sinister Strike, Backstab, Shiv, Hemmorhage and Gouge + if ((GetSpec() == BOT_SPEC_ROGUE_COMBAT) && + lvl >= 50 && (baseId == SINISTER_STRIKE_1 || baseId == BACKSTAB_1 || + /*baseId == SHIV_1 || */baseId == HEMORRHAGE_1 || baseId == GOUGE_1)) + pctbonus += 0.1f; + //Blade Twisting: 10% bonus damage for Sinister Strike and Backstab + if ((GetSpec() == BOT_SPEC_ROGUE_COMBAT) && lvl >= 35 && (baseId == SINISTER_STRIKE_1 || baseId == BACKSTAB_1)) + pctbonus += 0.1f; + //Sinister Calling: 10% bonus percentage damage for Backstab and Hemorrhage + //We add bonus damage pct because SpellMods are not handled + if ((GetSpec() == BOT_SPEC_ROGUE_SUBTLETY) && lvl >= 45 && (baseId == BACKSTAB_1 || baseId == HEMORRHAGE_1)) + pctbonus += 0.1f; + //Glyph of Fan of Knives: 20% bonus damage for Fan of Knives + if (lvl >= 80 && baseId == FAN_OF_KNIVES_1) + pctbonus += 0.2f; + + //Glyph of Sinister Strike: 50% chance to add 1 cp on crit + if (baseId == SINISTER_STRIKE_1) + glyphSSProc = iscrit && lvl >= 15 && urand(1,100) <= 50; + + damage = int32(fdamage * (1.0f + pctbonus)); + } + + void ApplyClassSpellCostMods(SpellInfo const* spellInfo, int32& cost) const override + { + //uint32 spellId = spellInfo->Id; + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + uint8 lvl = me->GetLevel(); + float fcost = float(cost); + int32 flatbonus = 0; + float pctbonus = 0.0f; + + //100% mods + //Glyph of Blade Flurry: -100% cost for Blade Flurry + if (lvl >= 30 && baseId == BLADE_FLURRY_1) + pctbonus += 1.0f; + + //percent mods + //Dirty Tricks: -50% cost for Blind and Sap + if (lvl >= 15 && (baseId == BLIND_1 || baseId == SAP_1)) + pctbonus += 0.5f; + + //flat mods + //Improved Expose Armor: -10 energy cost for Expose Armor + if (lvl >= 20 && baseId == EXPOSE_ARMOR_1) + flatbonus += 10; + //Improved Sinister Strike: -5 energy cost for Sinister Strike + if (lvl >= 10 && baseId == SINISTER_STRIKE_1) + flatbonus += 5; + //Dirty Deeds part 1: -20 energy cost for Cheap Shot and Garrote + if ((GetSpec() == BOT_SPEC_ROGUE_SUBTLETY) && lvl >= 30 && (baseId == CHEAP_SHOT_1 || baseId == GARROTE_1)) + flatbonus += 20; + //Filthy Tricks part 2: -10 energy cost for Tricks of the Trade, Distract and Shadowstep + if ((GetSpec() == BOT_SPEC_ROGUE_SUBTLETY) && + lvl >= 50 && (baseId == TRICKS_OF_THE_TRADE_1 || baseId == DISTRACT_1 || baseId == SHADOWSTEP_1)) + flatbonus += 10; + //Slaugher from the Shadows part 1: -20 energy cost for Backstab and Ambush + if ((GetSpec() == BOT_SPEC_ROGUE_SUBTLETY) && lvl >= 55 && (baseId == BACKSTAB_1 || baseId == AMBUSH_1)) + flatbonus += 20; + //Slaugher from the Shadows part 2: -5 energy cost for Hemorrhage + if ((GetSpec() == BOT_SPEC_ROGUE_SUBTLETY) && lvl >= 55 && baseId == HEMORRHAGE_1) + flatbonus += 5; + //Glyph of Feint: -20 energy cost for Feint + if (lvl >= 16 && baseId == FEINT_1) + flatbonus += 20; + //Glyph of Gouge: -15 energy cost for Gouge + if (lvl >= 15 && baseId == GOUGE_1) + flatbonus += 15; + //Glyph of Mutilate: -5 energy cost for Mutilate + if (lvl >= 50 && baseId == MUTILATE_1) + flatbonus += 5; + + //cost can be < 0 + cost = int32(fcost * (1.0f - pctbonus)) - flatbonus; + } + + void ApplyClassSpellCastTimeMods(SpellInfo const* spellInfo, int32& casttime) const override + { + //casttime is in milliseconds + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + uint8 lvl = me->GetLevel(); + int32 timebonus = 0; + //float pctbonus = 0.0f; + + //100% mods + //Improved Slam: -100% cast time for Slam + //if (lvl >= 40 && spellId == GetSpell(SLAM_1) && me->HasAura(BLOODSURGE_BUFF)) + // timebonus += casttime; + + //flat mods + //Glyph of Pick Lock: 100% cast time for Pick Lock (reduced for bots) + if (lvl >= 16 && baseId == PICK_LOCK_1) + timebonus += 4000; + + casttime = std::max(casttime - timebonus, 0); + } + + void ApplyClassSpellCooldownMods(SpellInfo const* spellInfo, uint32& cooldown) const override + { + //cooldown is in milliseconds + //uint32 spellId = spellInfo->Id; + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + uint8 lvl = me->GetLevel(); + int32 timebonus = 0; + float pctbonus = 0.0f; + + //pct mods + //Intensify Rage: -33% cooldown for Bloodrage, Berserker Rage, Recklessness and Death Wish + //if (lvl >= 40 && + // (spellId == GetSpell(BLOODRAGE_1) || spellId == GetSpell(BERSERKERRAGE_1) || + // spellId == GetSpell(RECKLESSNESS_1) || spellId == GetSpell(DEATHWISH_1))) + // pctbonus += 0.33f; + + //flat mods + //Elusiveness part 2: -60 sec cooldown for Blind + if (lvl >= 20 && baseId == BLIND_1) + timebonus += 60000; + //Elusiveness part 3: -30 sec cooldown for Cloak of Shadows + if (lvl >= 20 && baseId == CLOAK_OF_SHADOWS_1) + timebonus += 30000; + //Filthy Tricks part 1: -10 sec cooldown for Tricks of the Trade, Distract and Shadowstep + if ((GetSpec() == BOT_SPEC_ROGUE_SUBTLETY) && + lvl >= 50 && (baseId == TRICKS_OF_THE_TRADE_1 || baseId == DISTRACT_1 || baseId == SHADOWSTEP_1)) + timebonus += 10000; + //Filthy Tricks part 3: -3 min cooldown for Preparation + if ((GetSpec() == BOT_SPEC_ROGUE_SUBTLETY) && lvl >= 50 && baseId == PREPARATION_1) + timebonus += 180000; + //Glyph of Killing Spree: -45 sec cooldown for Killing Spree + if (lvl >= 60 && baseId == KILLING_SPREE_1) + timebonus += 45000; + + cooldown = std::max((float(cooldown) * (1.0f - pctbonus)) - timebonus, 0); + } + + void ApplyClassSpellCategoryCooldownMods(SpellInfo const* spellInfo, uint32& cooldown) const override + { + //cooldown is in milliseconds + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + uint8 lvl = me->GetLevel(); + int32 timebonus = 0; + float pctbonus = 0.0f; + + //Endurance: -1 min cooldown for Sprint and Evasion + if (lvl >= 20 && (baseId == SPRINT_1 || baseId == EVASION_1)) + timebonus += 60000; + //Elusiveness part 1: -60 sec cooldown for Vanish + if (lvl >= 20 && baseId == VANISH_1) + timebonus += 60000; + //Camouflage part 2: -6 sec cooldown for Stealth + if (lvl >= 15 && baseId == STEALTH_1) + timebonus += 6000; + + cooldown = std::max((float(cooldown) * (1.0f - pctbonus)) - timebonus, 0); + } + + void ApplyClassSpellGlobalCooldownMods(SpellInfo const* /*spellInfo*/, float& cooldown) const override + { + //cooldown is in milliseconds + //uint32 spellId = spellInfo->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + //uint8 lvl = me->GetLevel(); + float timebonus = 0.0f; + float pctbonus = 0.0f; + + //Unrelenting Assault (part 1, special): -0.5 sec global cooldown for Overpower and Revenge (not for tanks) + //if (lvl >= 50 && !IsTank() && (spellId == GetSpell(OVERPOWER_1) || spellId == GetSpell(REVENGE_1))) + // timebonus += 500.f; + + cooldown = (cooldown * (1.0f - pctbonus)) - timebonus; + } + + void ApplyClassSpellRadiusMods(SpellInfo const* /*spellInfo*/, float& radius) const override + { + //uint32 spellId = spellInfo->Id; + //uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + //uint8 lvl = me->GetLevel(); + float flatbonus = 0.0f; + float pctbonus = 0.0f; + + //pct mods + //Booming Voice + //if (lvl >= 10 && ((spellInfo->SpellFamilyFlags[0] & 0x30000) || (spellInfo->SpellFamilyFlags[1] & 0x80))) + // pctbonus += 1.0f; + + //flat mods + //Glyph of Thunder Clap + //if (spellInfo->SpellFamilyFlags[0] & 0x80) + // flatbonus += 4.f; + + radius = radius * (1.0f + pctbonus) + flatbonus; + } + + void ApplyClassSpellRangeMods(SpellInfo const* spellInfo, float& maxrange) const override + { + //uint32 spellId = spellInfo->Id; + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + uint8 lvl = me->GetLevel(); + float flatbonus = 0.0f; + float pctbonus = 0.0f; + + //pct mods + //Booming Voice + //if (lvl >= 10 && ((spellInfo->SpellFamilyFlags[0] & 0x30000) || (spellInfo->SpellFamilyFlags[1] & 0x80))) + // pctbonus += 1.0f; + + //flat mods + //Throwing Specialization: + 4 yd range for Deadly Throw + if ((GetSpec() == BOT_SPEC_ROGUE_COMBAT) && lvl >= 45 && baseId == DEADLY_THROW_1) + flatbonus += 4.f; + //Dirty Tricks: + 5 yd range for Blind and Sap + if (lvl >= 15 && (baseId == BLIND_1 || baseId == SAP_1)) + flatbonus += 5.f; + //Glyph of Ambush: + 5 yd range for Ambush + if (/*lvl >= 18 && */baseId == AMBUSH_1) + flatbonus += 5.f; + if (baseId == DISARM_TRAP_1) + flatbonus += 10.f; + + maxrange = maxrange * (1.0f + pctbonus) + flatbonus; + } + + void ApplyClassSpellMaxTargetsMods(SpellInfo const* /*spellInfo*/, uint32& targets) const override + { + uint32 bonusTargets = 0; + + //Improved Revenge: +1 target (actually 2 in dbc) + //if (spellInfo->SpellFamilyFlags[0] & 0x400) + // bonusTargets += 1; + + targets = targets + bonusTargets; + } + + void OnClassSpellGo(SpellInfo const* spellInfo) override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + uint8 lvl = me->GetLevel(); + + //Thistle Tea: cooldown + if (baseId == THISTLE_TEA) + SetSpellCooldown(THISTLE_TEA, 300000); //5 min (item cd) + + //Remorseless Attacks: proc consume buff + if (AuraEffect const* remo = me->GetAuraEffect(REMORSELESS_ATTACKS_BUFF, 0, me->GetGUID())) + if (remo->IsAffectingSpell(spellInfo)) + me->RemoveAurasDueToSpell(REMORSELESS_ATTACKS_BUFF); + + //Relentless Strikes + if (spellInfo->NeedsComboPoints() && comboPoints) + { + if (lvl >= 10) + { + if (irand(1,100) <= 20 * comboPoints) + { + me->CastSpell(me, RELENTLESS_STRIKES_EFFECT, true); + //TC_LOG_ERROR("entities.player", "rogue_bot CP SPEND1: RS proc!"); + } + } + } + + //Item enchant + //We don't know which item is targeted + //Actually it is mh, then oh + if (baseId == CRIPPLING_POISON_1 || baseId == INSTANT_POISON_1 || baseId == DEADLY_POISON_1 || + baseId == WOUND_POISON_1 || baseId == ANESTHETIC_POISON_1 || baseId == MIND_NUMBING_POISON_1) + { + //We set duration to 2 seconds to prevent exploiting unequip mechanic + //to get enchanted weapons for player (for non-shaman bots it won't work) + uint32 slot = TEMP_ENCHANTMENT_SLOT; + uint32 duration = 2 * IN_MILLISECONDS; + uint32 charges = 0; + uint32 enchant_id = spellInfo->_effects[0].MiscValue; + //SpellItemEnchantmentEntry const* pEnchant = sSpellItemEnchantmentStore.LookupEntry(enchant_id); + Item* mh = GetEquips(BOT_SLOT_MAINHAND); + Item* oh = GetEquips(BOT_SLOT_OFFHAND); + Item* item = nullptr; + uint8 itemSlot = 0; + + if (mh && !mh->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT)/* && mh->IsFitToSpellRequirements(spellInfo)*/) + { + item = mh; + itemSlot = BOT_SLOT_MAINHAND; + } + else if (oh && !oh->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT)/* && oh->IsFitToSpellRequirements(spellInfo)*/) + { + item = oh; + itemSlot = BOT_SLOT_OFFHAND; + } + else + ASSERT(false, "rogue bot attempted to enchant his weapons but cannot find a weapon to apply it!"); + + if (!IAmFree()) + master->GetSession()->SendEnchantmentLog(me->GetGUID(), me->GetGUID(), item->GetEntry(), enchant_id); + + item->SetUInt32Value(ITEM_FIELD_ENCHANTMENT_1_1 + slot*MAX_ENCHANTMENT_OFFSET + ENCHANTMENT_ID_OFFSET, enchant_id); + item->SetUInt32Value(ITEM_FIELD_ENCHANTMENT_1_1 + slot*MAX_ENCHANTMENT_OFFSET + ENCHANTMENT_DURATION_OFFSET, duration); + item->SetUInt32Value(ITEM_FIELD_ENCHANTMENT_1_1 + slot*MAX_ENCHANTMENT_OFFSET + ENCHANTMENT_CHARGES_OFFSET, charges); + ApplyItemEnchantment(item, TEMP_ENCHANTMENT_SLOT, itemSlot); + if (itemSlot == BOT_SLOT_MAINHAND) + mhEnchantExpireTimer = ITEM_ENCHANTMENT_EXPIRE_TIMER; + else if (itemSlot == BOT_SLOT_OFFHAND) + ohEnchantExpireTimer = ITEM_ENCHANTMENT_EXPIRE_TIMER; + //GC_Timer = 1500; //needed + } + } + + void SpellHit(WorldObject* wcaster, SpellInfo const* spell) override + { + Unit* caster = wcaster->ToUnit(); + if (!caster) + return; + + uint32 baseId = spell->GetFirstRankSpell()->Id; + + //Vanish: handle stealth add + if (baseId == VANISH_TRIGGERED_1 || baseId == VANISH_TRIGGERED_2 || baseId == VANISH_TRIGGERED_3) + { + if (!me->GetAuraEffect(SPELL_AURA_MOD_SHAPESHIFT, SPELLFAMILY_ROGUE, 0x400000, 0x0, 0x0)) + { + //SetSpellCooldown(STEALTH_1, 0); + me->CastSpell(me, STEALTH_1, true); + } + } + //Cheat Death: assume resilience bonus + if (baseId == CHEATING_DEATH_BUFF) + { + if (AuraEffect* chea = me->GetAuraEffect(SPELL_AURA_DUMMY, SPELLFAMILY_GENERIC, 2109, 0)) + { + chea->ChangeAmount(-100); + } + } + //Camouflage part 1: +15% speed while stealthed + if (baseId == STEALTH_1 && me->GetLevel() >= 15) + { + if (AuraEffect* stea = me->GetAuraEffect(spell->Id, 2)) + { + stea->ChangeAmount(stea->GetAmount() + 15); + } + } + + OnSpellHit(caster, spell); + } + + void SpellHitTarget(WorldObject* wtarget, SpellInfo const* spell) override + { + Unit* target = wtarget->ToUnit(); + if (!target) + return; + + uint32 spellId = spell->Id; + uint32 baseId = spell->GetFirstRankSpell()->Id; + uint8 lvl = me->GetLevel(); + + //Cold Blood: handle proc + if (AuraEffect const* bloo = me->GetAuraEffect(COLD_BLOOD_1, 0, me->GetGUID())) + if (bloo->IsAffectingSpell(spell)) + me->RemoveAurasDueToSpell(COLD_BLOOD_1); + + //Combo point generating from effects + if (baseId == SEAL_FATE_EFFECT || baseId == RUTHLESSNESS_EFFECT || + baseId == SETUP_EFFECT || baseId == INITIATIVE_EFFECT || baseId == HONOR_AMONG_THIEVES_EFFECT) + { + ++comboPoints; + //TC_LOG_ERROR("entities.player", "rogue_bot CP GEN2: {} adds 1, now {}", spell->SpellName[0], uint32(comboPoints)); + if (comboPoints > 5) + { + comboPoints = 5; + //TC_LOG_ERROR("entities.player", "rogue_bot CP NOR2: now {}", uint32(comboPoints)); + } + } + //Combo point generating from spells + if (baseId == SINISTER_STRIKE_1 || baseId == BACKSTAB_1 || baseId == MUTILATE_1 || + baseId == GOUGE_1 || baseId == HEMORRHAGE_1 || baseId == GHOSTLY_STRIKE_1 || + baseId == RIPOSTE_1 || baseId == PREMEDITATION_1 || + baseId == AMBUSH_1 || baseId == GARROTE_1 || baseId == CHEAP_SHOT_1/* || baseId == SHIV_1*/) + { + (baseId == MUTILATE_1 || baseId == PREMEDITATION_1 || baseId == CHEAP_SHOT_1) ? + comboPoints += 2 : ++comboPoints; + + //TC_LOG_ERROR("entities.player", "rogue_bot CP GEN1: {} adds {}, now {}", + // spell->SpellName[0], (baseId == MUTILATE_1 || baseId == PREMEDITATION_1 || baseId == CHEAP_SHOT_1) ? + // 2 : 1, uint32(comboPoints)); + + //Glyph of Sinister Strike: handle proc + if (baseId == SINISTER_STRIKE_1 && glyphSSProc) + { + ++comboPoints; + //TC_LOG_ERROR("entities.player", "rogue_bot CP GEN1: glyphSS proc, now {}", uint32(comboPoints)); + } + + if (comboPoints > 5) + { + comboPoints = 5; + //TC_LOG_ERROR("entities.player", "rogue_bot CP NOR1: now {}", uint32(comboPoints)); + } + } + //if (spellId == EVISCERATE || spellId == KIDNEY_SHOT || spellId == SLICE_DICE || spellId == RUPTURE || spellId == EXPOSE_ARMOR || spellId == ENVENOM) + //some abilities like relentless strikes require combo points thus tries to proc itself + else if (spell->NeedsComboPoints() && comboPoints) + { + //uint32 tempCP = comboPoints; + //comboPoints = 0; + combopointsSpent = true; //envenom problem - cps spent before aura application + + //TC_LOG_ERROR("entities.player", "rogue_bot CP SPEND1: {} to 0", tempCP); + + //Relentless Strikes: moved to OnClassSpellGo (triggered even without hitting the target) + + //Ruthlessness + if (lvl >= 15) + { + if (urand(1,100) <= 60) + { + me->CastSpell(target, RUTHLESSNESS_EFFECT, true); + //TC_LOG_ERROR("entities.player", "rogue_bot CP SPEND1: RU proc!"); + } + } + } + + //Preparation: handle effect + if (baseId == PREPARATION_1) + { + //TC_LOG_ERROR("entities.player", "rogue_bot Preparation hit!"); + if (GetSpell(EVASION_1)) + SetSpellCooldown(EVASION_1, 0); + if (GetSpell(SPRINT_1)) + SetSpellCooldown(SPRINT_1, 0); + if (GetSpell(VANISH_1)) + SetSpellCooldown(VANISH_1, 0); + if (GetSpell(COLD_BLOOD_1)) + SetSpellCooldown(COLD_BLOOD_1, 0); + if (GetSpell(SHADOWSTEP_1)) + SetSpellCooldown(SHADOWSTEP_1, 0); + + //Glyph of Preparation + //if (lvl >= 30) // same level as spell itself + { + if (GetSpell(BLADE_FLURRY_1)) + SetSpellCooldown(BLADE_FLURRY_1, 0); + if (GetSpell(DISMANTLE_1)) + SetSpellCooldown(DISMANTLE_1, 0); + if (GetSpell(KICK_1)) + SetSpellCooldown(KICK_1, 0); + } + } + + //Glyph of Garrote + if (lvl >= 15 && baseId == GARROTE_1) + { + if (Aura* garr = target->GetAura(spellId, me->GetGUID())) + { + uint32 dur = garr->GetMaxDuration() - 3000; + garr->SetDuration(dur); + garr->SetMaxDuration(dur); + if (AuraEffect* garrEff = garr->GetEffect(0)) + { + garrEff->ChangeAmount(int32(garrEff->GetAmount() * 1.44f)); //not a mistake + } + } + } + //Glyph of Deadly Throw + if (lvl >= 64 && baseId == DEADLY_THROW_1) + { + if (AuraEffect* thro = target->GetAuraEffect(spellId, 1, me->GetGUID())) + { + thro->ChangeAmount(thro->GetAmount() - 20); + } + } + //Glyph of Hunger for Blood + if (lvl >= 60 && baseId == HUNGER_FOR_BLOOD_BUFF) + { + if (AuraEffect* hung = me->GetAuraEffect(spellId, 0)) + { + hung->ChangeAmount(hung->GetAmount() + 3); + } + } + //Glyph of Cloak of Shadows + if (lvl >= 66 && baseId == CLOAK_OF_SHADOWS_1) + { + if (AuraEffect* cloa = me->GetAuraEffect(spellId, 2)) + { + cloa->ChangeAmount(cloa->GetAmount() - 40); + } + } + //Glyph of Sprint + if (lvl >= 15 && baseId == SPRINT_1) + { + if (AuraEffect* spri = me->GetAuraEffect(spellId, 0)) + { + spri->ChangeAmount(spri->GetAmount() + 30); + } + } + //Glyph of Vanish + if (lvl >= 22 && baseId == VANISH_1) + { + if (AuraEffect* vani = me->GetAuraEffect(spellId, 2)) + { + vani->ChangeAmount(vani->GetAmount() + 30); + } + } + //Glyph of Adrenaline Rush + if (lvl >= 40 && baseId == ADRENALINE_RUSH_1) + { + if (Aura* rush = me->GetAura(spellId)) + { + uint32 dur = rush->GetMaxDuration() + 5000; + rush->SetDuration(dur); + rush->SetMaxDuration(dur); + } + } + //Glyph of Evasion + if (lvl >= 15 && baseId == EVASION_1) + { + if (Aura* evas = me->GetAura(spellId)) + { + uint32 dur = evas->GetMaxDuration() + 5000; + evas->SetDuration(dur); + evas->SetMaxDuration(dur); + } + } + //Glyph of Slice and Dice + //Improved Slice and Dice + if (lvl >= 15 && baseId == SLICE_DICE_1) + { + if (Aura* dice = me->GetAura(spellId)) + { + uint32 dur = dice->GetMaxDuration() + 3000; + dur = dur + dur / 2; + dice->SetDuration(dur); + dice->SetMaxDuration(dur); + } + } + //Glyph of Shadow Dance: 4 sec for bots + if (lvl >= 60 && baseId == SHADOW_DANCE_1) + { + if (Aura* danc = me->GetAura(spellId)) + { + uint32 dur = danc->GetMaxDuration() + 4000; + danc->SetDuration(dur); + danc->SetMaxDuration(dur); + } + } + //Glyph of Rupture + if (lvl >= 20 && baseId == RUPTURE_1) + { + if (Aura* rupt = target->GetAura(spellId, me->GetGUID())) + { + uint32 dur = rupt->GetMaxDuration() + 4000; + rupt->SetDuration(dur); + rupt->SetMaxDuration(dur); + } + } + //Glyph of Expose Armor + if (lvl >= 15 && baseId == EXPOSE_ARMOR_1) + { + if (Aura* expo = target->GetAura(spellId, me->GetGUID())) + { + uint32 dur = expo->GetMaxDuration() + 12000; + expo->SetDuration(dur); + expo->SetMaxDuration(dur); + } + } + //Improved Gouge: Increased duration by 1.5 sec + if (lvl >= 10 && baseId == GOUGE_1) + { + if (Aura* goug = target->GetAura(spellId, me->GetGUID())) + { + int32 duration = goug->GetMaxDuration() + 1500; + goug->SetDuration(duration); + goug->SetMaxDuration(duration); + } + } + //Glyph of Tricks of Trade + if (lvl >= 75 && baseId == TRICKS_OF_THE_TRADE_BUFF) + { + if (Aura* tric = target->GetAura(spellId, me->GetGUID())) + { + int32 duration = tric->GetMaxDuration() + 4000; + tric->SetDuration(duration); + tric->SetMaxDuration(duration); + } + } + //Cut to the Chase: Eviscerate and Envenom will refresh Slice and Dice duration as for 5 points + if (GetSpec() == BOT_SPEC_ROGUE_ASSASINATION && lvl >= 55 && (baseId == EVISCERATE_1 || baseId == ENVENOM_1) && GetSpell(SLICE_DICE_1)) + { + if (Aura* dice = me->GetAura(GetSpell(SLICE_DICE_1))) + { + int32 duration = 21000 + 3000 + 12000; //base + glyph + improved + dice->SetDuration(duration); + dice->SetMaxDuration(duration); + } + } + //Waylay + if ((GetSpec() == BOT_SPEC_ROGUE_SUBTLETY) && lvl >= 45 && (baseId == BACKSTAB_1 || baseId == AMBUSH_1)) + me->CastSpell(target, WAYLAY_DEBUFF, true); + + //Stun: move behind + if (baseId == CHEAP_SHOT_1 || baseId == KIDNEY_SHOT_1 || baseId == GOUGE_1) + if (target == opponent) + MoveBehind(target); + + OnSpellHitTarget(target, spell); + } + + void DamageDealt(Unit* victim, uint32& damage, DamageEffectType damageType) override + { + bot_ai::DamageDealt(victim, damage, damageType); + } + + void DamageTaken(Unit* u, uint32& /*damage*/, DamageEffectType /*damageType*/, SpellInfo const* /*spellInfo*/) override + { + if (!u) + return; + if (!u->IsInCombat() && !me->IsInCombat()) + return; + OnOwnerDamagedBy(u); + } + + void OwnerAttackedBy(Unit* u) override + { + if (!u) + return; + OnOwnerDamagedBy(u); + } + + void CheckAttackState() override + { + if (me->GetVictim() && HasRole(BOT_ROLE_DPS) && !me->HasAuraType(SPELL_AURA_MOD_STEALTH) && + (me->isAttackReady() || me->isAttackReady(OFF_ATTACK)) && + (!me->GetVictim()->GetAuraEffect(SPELL_AURA_MOD_STUN, SPELLFAMILY_ROGUE, 0x8, 0x0, 0x0) && + !me->GetVictim()->GetAuraEffect(SPELL_AURA_MOD_CONFUSE, SPELLFAMILY_ROGUE, 0x01000000, 0x0, 0x0))) + DoMeleeAttackIfReady(); + } + + uint32 GetAIMiscValue(uint32 data) const override + { + switch (data) + { + case BOTAI_MISC_COMBO_POINTS: + return comboPoints; + case BOTAI_MISC_ENCHANT_IS_AUTO_MH: + return needChooseMHEnchant; + case BOTAI_MISC_ENCHANT_IS_AUTO_OH: + return needChooseOHEnchant; + case BOTAI_MISC_ENCHANT_CAN_EXPIRE_MH: + return mhEnchantExpireTimer; + case BOTAI_MISC_ENCHANT_CAN_EXPIRE_OH: + return ohEnchantExpireTimer; + case BOTAI_MISC_ENCHANT_CURRENT_MH: + return mhEnchant; + case BOTAI_MISC_ENCHANT_CURRENT_OH: + return ohEnchant; + case BOTAI_MISC_ENCHANT_AVAILABLE_1: + return GetSpell(CRIPPLING_POISON_1) ? CRIPPLING_POISON_1 : 0; + case BOTAI_MISC_ENCHANT_AVAILABLE_2: + return GetSpell(INSTANT_POISON_1) ? INSTANT_POISON_1 : 0; + case BOTAI_MISC_ENCHANT_AVAILABLE_3: + return GetSpell(MIND_NUMBING_POISON_1) ? MIND_NUMBING_POISON_1 : 0; + case BOTAI_MISC_ENCHANT_AVAILABLE_4: + return GetSpell(DEADLY_POISON_1) ? DEADLY_POISON_1 : 0; + case BOTAI_MISC_ENCHANT_AVAILABLE_5: + return GetSpell(WOUND_POISON_1) ? WOUND_POISON_1 : 0; + case BOTAI_MISC_ENCHANT_AVAILABLE_6: + return GetSpell(ANESTHETIC_POISON_1) ? ANESTHETIC_POISON_1 : 0; + default: + return 0; + } + } + + void SetAIMiscValue(uint32 data, uint32 value) override + { + switch (data) + { + case BOTAI_MISC_DAGGER_MAINHAND: + isdaggerMH = bool(value); + break; + case BOTAI_MISC_DAGGER_OFFHAND: + isdaggerOH = bool(value); + break; + case BOTAI_MISC_ENCHANT_CAN_EXPIRE_MH: + if (value) + mhEnchantExpireTimer = 0; + break; + case BOTAI_MISC_ENCHANT_CAN_EXPIRE_OH: + if (value) + ohEnchantExpireTimer = 0; + break; + case BOTAI_MISC_ENCHANT_CURRENT_MH: + mhEnchant = value; + needChooseMHEnchant = value ? false : true; + break; + case BOTAI_MISC_ENCHANT_CURRENT_OH: + ohEnchant = value; + needChooseOHEnchant = value ? false : true; + break; + default: + break; + } + } + + void Reset() override + { + energy = 0; + comboPoints = 0; + combopointsSpent = false; + glyphSSProc = false; + + mhEnchantExpireTimer = 1; + ohEnchantExpireTimer = 1; + + DefaultInit(); + + mhEnchant = 0; + ohEnchant = 0; + needChooseMHEnchant = true; + needChooseOHEnchant = true; + + //after InitEquips + Item const* mh = GetEquips(BOT_SLOT_MAINHAND); + Item const* oh = GetEquips(BOT_SLOT_OFFHAND); + isdaggerMH = mh && mh->GetTemplate()->Class == ITEM_CLASS_WEAPON && mh->GetTemplate()->SubClass == ITEM_SUBCLASS_WEAPON_DAGGER; + isdaggerOH = oh && oh->GetTemplate()->Class == ITEM_CLASS_WEAPON && oh->GetTemplate()->SubClass == ITEM_SUBCLASS_WEAPON_DAGGER; + } + + void ReduceCD(uint32 diff) override + { + if (mhEnchantExpireTimer > diff) mhEnchantExpireTimer -= diff; + if (ohEnchantExpireTimer > diff) ohEnchantExpireTimer -= diff; + } + + void InitPowers() override + { + //Hack for power + me->SetPowerType(POWER_ENERGY); + + if (energy) + me->SetPower(POWER_ENERGY, energy); + else + me->SetPower(POWER_ENERGY, me->GetMaxPower(POWER_ENERGY)); + } + + void InitSpells() override + { + uint8 lvl = me->GetLevel(); + bool isAssa = GetSpec() == BOT_SPEC_ROGUE_ASSASINATION; + bool isComb = GetSpec() == BOT_SPEC_ROGUE_COMBAT; + bool isSubt = GetSpec() == BOT_SPEC_ROGUE_SUBTLETY; + + InitSpellMap(KICK_1); + //InitSpellMap(EXPOSE_ARMOR_1); + InitSpellMap(DISMANTLE_1); + InitSpellMap(FEINT_1); + InitSpellMap(DISARM_TRAP_1); + + InitSpellMap(BACKSTAB_1); + InitSpellMap(SINISTER_STRIKE_1); + InitSpellMap(EVISCERATE_1); + InitSpellMap(ENVENOM_1); + InitSpellMap(RUPTURE_1); + InitSpellMap(DEADLY_THROW_1); + InitSpellMap(FAN_OF_KNIVES_1); + + InitSpellMap(SPRINT_1); + InitSpellMap(EVASION_1); + InitSpellMap(BLIND_1); + InitSpellMap(VANISH_1); + + InitSpellMap(GOUGE_1); + + InitSpellMap(KIDNEY_SHOT_1); + InitSpellMap(SLICE_DICE_1); + InitSpellMap(CLOAK_OF_SHADOWS_1); + InitSpellMap(TRICKS_OF_THE_TRADE_1); + + InitSpellMap(STEALTH_1); + //InitSpellMap(SAP_1); + InitSpellMap(GARROTE_1); + InitSpellMap(CHEAP_SHOT_1); + InitSpellMap(AMBUSH_1); + + lvl >= 30 && isAssa ? InitSpellMap(COLD_BLOOD_1) : RemoveSpell(COLD_BLOOD_1); + lvl >= 50 && isAssa ? InitSpellMap(MUTILATE_1) : RemoveSpell(MUTILATE_1); + lvl >= 60 && isAssa ? InitSpellMap(HUNGER_FOR_BLOOD_1) : RemoveSpell(HUNGER_FOR_BLOOD_1); + + lvl >= 20 && isComb ? InitSpellMap(RIPOSTE_1) : RemoveSpell(RIPOSTE_1); + lvl >= 30 && isComb ? InitSpellMap(BLADE_FLURRY_1) : RemoveSpell(BLADE_FLURRY_1); + lvl >= 40 && isComb ? InitSpellMap(ADRENALINE_RUSH_1) : RemoveSpell(ADRENALINE_RUSH_1); + lvl >= 60 && isComb ? InitSpellMap(KILLING_SPREE_1) : RemoveSpell(KILLING_SPREE_1); + + lvl >= 20 && isSubt ? InitSpellMap(GHOSTLY_STRIKE_1) : RemoveSpell(GHOSTLY_STRIKE_1); + lvl >= 30 && isSubt ? InitSpellMap(HEMORRHAGE_1) : RemoveSpell(HEMORRHAGE_1); + lvl >= 30 && isSubt ? InitSpellMap(PREPARATION_1) : RemoveSpell(PREPARATION_1); + lvl >= 40 && isSubt ? InitSpellMap(PREMEDITATION_1) : RemoveSpell(PREMEDITATION_1); + lvl >= 50 && isSubt ? InitSpellMap(SHADOWSTEP_1) : RemoveSpell(SHADOWSTEP_1); + lvl >= 60 && isSubt ? InitSpellMap(SHADOW_DANCE_1) : RemoveSpell(SHADOW_DANCE_1); + + //InitSpellMap(DISTRACT_1); + + InitSpellMap(CRIPPLING_POISON_1); + InitSpellMap(INSTANT_POISON_1); + InitSpellMap(DEADLY_POISON_1); + InitSpellMap(WOUND_POISON_1); + InitSpellMap(MIND_NUMBING_POISON_1); + InitSpellMap(ANESTHETIC_POISON_1); + + lvl >= 10 ? InitSpellMap(THISTLE_TEA) : RemoveSpell(THISTLE_TEA); + } + + void ApplyClassPassives() const override + { + uint8 level = master->GetLevel(); + bool isAssa = GetSpec() == BOT_SPEC_ROGUE_ASSASINATION; + bool isComb = GetSpec() == BOT_SPEC_ROGUE_COMBAT; + bool isSubt = GetSpec() == BOT_SPEC_ROGUE_SUBTLETY; + + RefreshAura(REMORSELESS_ATTACKS, level >= 10 ? 1 : 0); + RefreshAura(VIGOR, level >= 20 ? 1 : 0); + RefreshAura(QUICK_RECOVERY2, isAssa && level >= 35 ? 1 : 0); + RefreshAura(QUICK_RECOVERY1, isAssa && level >= 30 && level < 35 ? 1 : 0); + RefreshAura(IMPROVED_KIDNEY_SHOT, isAssa && level >= 30 ? 1 : 0); + RefreshAura(FLEET_FOOTED, isAssa && level >= 30 ? 1 : 0); + RefreshAura(SEAL_FATE5, isAssa && level >= 45 ? 1 : 0); + RefreshAura(SEAL_FATE4, isAssa && level >= 42 && level < 45 ? 1 : 0); + RefreshAura(SEAL_FATE3, isAssa && level >= 39 && level < 42 ? 1 : 0); + RefreshAura(SEAL_FATE2, isAssa && level >= 37 && level < 39 ? 1 : 0); + RefreshAura(SEAL_FATE1, isAssa && level >= 35 && level < 37 ? 1 : 0); + RefreshAura(MURDER, isAssa && level >= 35 ? 1 : 0); + RefreshAura(DEADLY_BREW, isAssa && level >= 40 ? 1 : 0); + RefreshAura(OVERKILL, isAssa && level >= 40 ? 1 : 0); + //RefreshAura(FOCUSED_ATTACKS, isAssa && level >= 45 ? 1 : 0); + RefreshAura(MASTER_POISONER, isAssa && level >= 50 ? 1 : 0); + + RefreshAura(DUAL_WIELD_SPECIALIZATION, level >= 10 ? 1 : 0); + RefreshAura(IMPROVED_KICK, isComb && level >= 25 ? 1 : 0); + RefreshAura(IMPROVED_SPRINT, isComb && level >= 25 ? 1 : 0); + RefreshAura(HACK_AND_SLASH, isComb && level >= 30 ? 1 : 0); + //RefreshAura(BLADE_TWISTING1, isComb && level >= 35 ? 1 : 0); + RefreshAura(VITALITY, isComb && level >= 40 ? 1 : 0); + RefreshAura(NERVES_OF_STEEL, isComb && level >= 40 ? 1 : 0); + RefreshAura(COMBAT_POTENCY5, isComb && level >= 55 ? 1 : 0); + RefreshAura(COMBAT_POTENCY4, isComb && level >= 52 && level < 55 ? 1 : 0); + RefreshAura(COMBAT_POTENCY3, isComb && level >= 49 && level < 52 ? 1 : 0); + RefreshAura(COMBAT_POTENCY2, isComb && level >= 47 && level < 49 ? 1 : 0); + RefreshAura(COMBAT_POTENCY1, isComb && level >= 45 && level < 47 ? 1 : 0); + RefreshAura(THROWING_SPECIALIZATION, isComb && level >= 45 ? 1 : 0); + //RefreshAura(SAVAGE_COMBAT, isComb && level >= 50 ? 1 : 0); + RefreshAura(UNFAIR_ADVANTAGE, isComb && level >= 50 ? 1 : 0); + RefreshAura(SURPRISE_ATTACKS, isComb && level >= 50 ? 1 : 0); + RefreshAura(PREY_ON_THE_WEAK, isComb && level >= 55 ? 1 : 0); + + RefreshAura(MASTER_OF_DECEPTION, level >= 10 ? 1 : 0); + RefreshAura(SETUP, isSubt && level >= 25 ? 1 : 0); + RefreshAura(INITIATIVE, isSubt && level >= 25 ? 1 : 0); + RefreshAura(DIRTY_DEEDS, isSubt && level >= 30 ? 1 : 0); + RefreshAura(MASTER_OF_SUBTLETY, isSubt && level >= 35 ? 1 : 0); + RefreshAura(CHEAT_DEATH, isSubt && level >= 40 ? 1 : 0); + RefreshAura(ENVELOPING_SHADOWS, isSubt && level >= 40 ? 1 : 0); + RefreshAura(TURN_THE_TABLES, !IAmFree() && isSubt && level >= 55 ? 1 : 0); + //RefreshAura(HONOR_AMONG_THIEVES, isSubt && level >= 55 ? 1 : 0); + + RefreshAura(VIGOR_GLADIATOR, level >= 70 ? 1 : 0); + + RefreshAura(GLYPH_BACKSTAB, level >= 15 ? 1 : 0); + + RefreshAura(ROGUE_PASSIVE_DND); + } + + bool CanUseManually(uint32 basespell) const override + { + switch (basespell) + { + case STEALTH_1: + case SPRINT_1: + case VANISH_1: + case BLADE_FLURRY_1: + case FAN_OF_KNIVES_1: + case TRICKS_OF_THE_TRADE_1: + case PREPARATION_1: + return true; + default: + return false; + } + } + + float GetBotArmorPenetrationCoef() const override + { + float bonus = 0.0f; + + //Serrated Blades part 1 + if (me->GetLevel() >= 20) + bonus += 9.f; + + //Mace Specialization: 15% armor penetration + if (me->GetLevel() >= 30) + if (Item const* weap = GetEquips(BOT_SLOT_MAINHAND)) + if (weap->GetTemplate()->SubClass == ITEM_SUBCLASS_WEAPON_MACE) + bonus += 15.f; + + return bonus + bot_ai::GetBotArmorPenetrationCoef(); + } + + bool HasAbilitiesSpecifics() const override { return true; } + void FillAbilitiesSpecifics(Player const* player, std::list &specList) override + { + uint32 textId1, textId2; + switch (mhEnchant) + { + case CRIPPLING_POISON_1: textId1 = BOT_TEXT_CRIPPLING; break; + case INSTANT_POISON_1: textId1 = BOT_TEXT_INSTANT; break; + case DEADLY_POISON_1: textId1 = BOT_TEXT_DEADLY; break; + case WOUND_POISON_1: textId1 = BOT_TEXT_WOUND; break; + case MIND_NUMBING_POISON_1: textId1 = BOT_TEXT_MINDNUMBING; break; + case ANESTHETIC_POISON_1: textId1 = BOT_TEXT_ANESTHETIC; break; + default: textId1 = BOT_TEXT_NOTHING_C; break; + } + switch (ohEnchant) + { + case CRIPPLING_POISON_1: textId2 = BOT_TEXT_CRIPPLING; break; + case INSTANT_POISON_1: textId2 = BOT_TEXT_INSTANT; break; + case DEADLY_POISON_1: textId2 = BOT_TEXT_DEADLY; break; + case WOUND_POISON_1: textId2 = BOT_TEXT_WOUND; break; + case MIND_NUMBING_POISON_1: textId2 = BOT_TEXT_MINDNUMBING; break; + case ANESTHETIC_POISON_1: textId2 = BOT_TEXT_ANESTHETIC; break; + default: textId2 = BOT_TEXT_NOTHING_C; break; + } + specList.push_back(LocalizedNpcText(player, BOT_TEXT_SLOT_MH) + ": " + LocalizedNpcText(player, textId1)); + specList.push_back(LocalizedNpcText(player, BOT_TEXT_SLOT_OH) + ": " + LocalizedNpcText(player, textId2)); + } + + std::vector const* GetDamagingSpellsList() const override + { + return &Rogue_spells_damage; + } + std::vector const* GetCCSpellsList() const override + { + return &Rogue_spells_cc; + } + //std::vector const* GetHealingSpellsList() const override + //{ + // return &Rogue_spells_heal; + //} + std::vector const* GetSupportSpellsList() const override + { + return &Rogue_spells_support; + } + + private: + mutable bool glyphSSProc; + int32 energy; + uint8 comboPoints; + bool combopointsSpent; + bool isdaggerMH, isdaggerOH; + uint32 mhEnchantExpireTimer, ohEnchantExpireTimer; + uint32 mhEnchant, ohEnchant; + bool needChooseMHEnchant, needChooseOHEnchant; + }; +}; + +void AddSC_rogue_bot() +{ + new rogue_bot(); +} diff --git a/src/server/game/AI/NpcBots/bot_sea_witch_ai.cpp b/src/server/game/AI/NpcBots/bot_sea_witch_ai.cpp new file mode 100644 index 000000000..7107d9fe9 --- /dev/null +++ b/src/server/game/AI/NpcBots/bot_sea_witch_ai.cpp @@ -0,0 +1,785 @@ +#include "bot_ai.h" +#include "bot_GridNotifiers.h" +#include "botspell.h" +#include "bottext.h" +#include "ScriptMgr.h" +#include "SpellAuraEffects.h" +#include "TemporarySummon.h" +/* +Naga Sea Witch NpcBot (by Trickerer onlysuffering@gmail.com) +Description: +A vicious scaly denizen of ocean often associated with the coming of prodigious storms (Warcraft III tribute) +Specifics: +Spell damage taken reduced by 30%, partially immune to control effects, cloth armor, +deals physical/spellfrost/spellstorm damage, attack power bonus: agility x2, spell power bonus: 200% intellect. Main attribute: intellect +Abilities: +1) Forked Lightning. Calls forth a cone of lightning to damage enemies. Hits 2 to all targets (depending on hero level), +stunning them for 2 seconds. This damage generates no threat +2) Frost Arrows. Imbues arrow with spellfrost for extra damage, slowing target's +movement, attack and casting speed by up to 70% (depending on hero level) +3) Mana Shield. Creates a shield that absorbs 100% incoming (non-mitigated) damage by using Sea Witch's mana, +absorbs 2 to 10 damage per point of mana (depending on hero level) +4) Tornado. Summons a fierce tornado that damages and slows nearby enemy units, sometimes incapacitating them completely. +Tornado grows over time oudoors, increasing damage dealt and area of effect, but shrinks indoors, dissipating quickly +5ex) Shot. Normal shoot +6ex) Naga (passive). Swim speed increased by 200%. Damage and dodge chance are greatly increased while in water +Complete - 100% +*/ + +enum SeaWitchBaseSpells +{ + //AUTO_SHOT_1 = 75, + SHOOT_BOW_1 = SPELL_SHOOT_BOW, + FORKED_LIGHTNING_1 = SPELL_FORKED_LIGHTNING, + FROST_ARROW_1 = SPELL_FROST_ARROW, + MANA_SHIELD_1 = SPELL_MANA_SHIELD, + TORNADO_1 = SPELL_TORNADO +}; +enum SeaWitchPassives +{ +}; +enum SeaWitchSpecial +{ + MH_ATTACK_ANIM = SPELL_ATTACK_MELEE_1H, + + FORKEDLIGHTNING_COST = 110 * 5, + FROSTARROW_COST = 10 * 5, + TORNADO_COST = 250 * 5, + + FORKED_LIGHTNING_EFFECT = SPELL_FORKED_LIGHTNING_EFFECT, + FROST_ARROW_EFFECT = SPELL_FROST_ARROW_EFFECT, + + NAGA_SWIM_PASSIVE = 40513, + SPELL_THREAT_MOD = 31745, //Salvation + + SPELL_PARALYTIC_POISON = 35201 +}; + +static constexpr size_t TORNADO_MIN_TARGETS = 4u; + +static const uint32 Seawitch_spells_damage_arr[] = +{ FORKED_LIGHTNING_1, FROST_ARROW_1, TORNADO_1 }; + +static const uint32 Seawitch_spells_cc_arr[] = +{ FROST_ARROW_1, TORNADO_1 }; + +static const uint32 Seawitch_spells_support_arr[] = +{ MANA_SHIELD_1 }; + +static const std::vector Seawitch_spells_damage(FROM_ARRAY(Seawitch_spells_damage_arr)); +static const std::vector Seawitch_spells_cc(FROM_ARRAY(Seawitch_spells_cc_arr)); +static const std::vector Seawitch_spells_support(FROM_ARRAY(Seawitch_spells_support_arr)); + +class sea_witch_bot : public CreatureScript +{ +public: + sea_witch_bot() : CreatureScript("sea_witch_bot") { } + + CreatureAI* GetAI(Creature* creature) const override + { + return new sea_witch_botAI(creature); + } + + struct sea_witch_botAI : public bot_ai + { + sea_witch_botAI(Creature* creature) : bot_ai(creature) + { + _botclass = BOT_CLASS_SEA_WITCH; + + InitUnitFlags(); + + //sea witch immunities + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_POSSESS, true); + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_CHARM, true); + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_SHAPESHIFT, true); + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_TRANSFORM, true); + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_DISARM, true); + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_DISARM_OFFHAND, true); + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_DISARM_RANGED, true); + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_FEAR, true); + me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_DISARM, true); + me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_POLYMORPH, true); + me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_FEAR, true); + me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_HORROR, true); + me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_TURN, true); + me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_SLEEP, true); + //Sea Witch is immune to poisons + me->ApplySpellImmune(0, IMMUNITY_DISPEL, DISPEL_POISON, true); + } + + bool doCast(Unit* victim, uint32 spellId) + { + if (CheckBotCast(victim, spellId) != SPELL_CAST_OK) + return false; + + return bot_ai::doCast(victim, spellId); + } + + void StartAttack(Unit* u, bool force = false) + { + if (!bot_ai::StartAttack(u, force)) + return; + GetInPosition(force, u); + } + + void JustEnteredCombat(Unit* u) override { bot_ai::JustEnteredCombat(u); } + void EnterEvadeMode(EvadeReason why = EVADE_REASON_OTHER) override { bot_ai::EnterEvadeMode(why); } + void MoveInLineOfSight(Unit* u) override { bot_ai::MoveInLineOfSight(u); } + void JustDied(Unit* u) override { /*UnsummonAll(false);*/ bot_ai::JustDied(u); } + void DoNonCombatActions(uint32 /*diff*/) { } + void KilledUnit(Unit* u) override { bot_ai::KilledUnit(u); } + + void CheckManaShield(uint32 diff) + { + if (me->HasAuraType(SPELL_AURA_MANA_SHIELD) || me->HasAuraType(SPELL_AURA_SCHOOL_ABSORB)) + return; + + if (IsSpellReady(MANA_SHIELD_1, diff, false) && GetManaPCT(me) >= 25 && me->IsInCombat()) + if (doCast(me, GetSpell(MANA_SHIELD_1))) + return; + } + + void Counter(uint32 diff) + { + if (!IsSpellReady(FORKED_LIGHTNING_1, diff, false) || !HasRole(BOT_ROLE_DPS) || Rand() > 55) + return; + + if (Unit* target = FindCastingTarget(CalcSpellMaxRange(FORKED_LIGHTNING_1) - 5.f, 0, FORKED_LIGHTNING_1)) + { + Spell const* spell = target->GetCurrentSpell(CURRENT_GENERIC_SPELL); + if (!spell) + spell = target->GetCurrentSpell(CURRENT_CHANNELED_SPELL); + + if (spell && spell->GetTimer() > int32(AssertBotSpellInfoOverride(GetSpell(FORKED_LIGHTNING_1))->CalcCastTime() + 250)) + { + me->SetFacingTo(me->GetAbsoluteAngle(target)); + if (doCast(target, GetSpell(FORKED_LIGHTNING_1))) + return; + } + } + } + + void UpdateAI(uint32 diff) override + { + if (!GlobalUpdate(diff)) + return; + + DoVehicleActions(diff); + if (!CanBotAttackOnVehicle()) + return; + + if (Spell const* spell = me->GetCurrentSpell(CURRENT_GENERIC_SPELL)) + { + uint32 const baseId = spell->GetSpellInfo()->GetFirstRankSpell()->Id; + //Tornado interrupt + if (!me->IsInCombat() && baseId == TORNADO_1) + me->InterruptSpell(CURRENT_GENERIC_SPELL); + else if (baseId == FORKED_LIGHTNING_1 && (!me->GetVictim() || !me->HasInArc(float(M_PI) / 2.f, me->GetVictim()))) + { + me->InterruptSpell(CURRENT_GENERIC_SPELL); + SetSpellCooldown(FORKED_LIGHTNING_1, 500); + } + else if (_spell_preact && spell->GetTimer() < 400) + { + _spell_preact = false; + switch (baseId) + { + case TORNADO_1: + // BotSay("REAP THE STORM!"); + //[[fallthrough]]; + case FORKED_LIGHTNING_1: + me->CastSpell(me, MH_ATTACK_ANIM, true); + break; + default: + break; + } + } + } + + if (IsPotionReady()) + { + if (me->GetPower(POWER_MANA) < FORKEDLIGHTNING_COST) + DrinkPotion(true); + else if (GetHealthPCT(me) < 30) + DrinkPotion(false); + } + + if (ProcessImmediateNonAttackTarget()) + return; + + CheckManaShield(diff); + + if (!CheckAttackTarget()) + { + me->InterruptSpell(CURRENT_AUTOREPEAT_SPELL); + return; + } + + Counter(diff); + + if (IsCasting()) + return; + + CheckUsableItems(diff); + + DoRangedAttack(diff); + } + + void DoRangedAttack(uint32 diff) + { + Unit* mytar = opponent ? opponent : disttarget ? disttarget : nullptr; + if (!mytar) + return; + + StartAttack(mytar, IsMelee()); + + CheckAttackState(); + if (!me->IsAlive() || !mytar->IsAlive()) + return; + + MoveBehind(mytar); + + float dist = me->GetDistance(mytar); + static constexpr float maxRangeLong = 35.f; + + //bool inpostion = !mytar->HasAuraType(SPELL_AURA_MOD_SPEED_SLOW_ALL) || dist > maxRangeLong - 20.f; + + //Auto Shot + //Spell const* shot = me->GetCurrentSpell(CURRENT_AUTOREPEAT_SPELL); + //if (shot && shot->GetSpellInfo()->Id == AUTO_SHOT_1 && (shot->m_targets.GetUnitTarget() != mytar || !inpostion)) + // me->InterruptSpell(CURRENT_AUTOREPEAT_SPELL); + + if (!HasRole(BOT_ROLE_DPS)) + return; + + //RANGED SECTION + if (dist > maxRangeLong) + return; + + if (CheckForkedLightning(diff)) + return; + + if (CheckTornado(diff)) + return; + + if (/*inpostion && */!me->GetCurrentSpell(CURRENT_GENERIC_SPELL)) + { + //Frost Arrow / Autoshot + if (IsSpellReady(FROST_ARROW_1, diff) && me->GetPower(POWER_MANA) >= FROSTARROW_COST && + !mytar->IsImmunedToDamage(AssertBotSpellInfoOverride(FROST_ARROW_1))) + { + if (doCast(mytar, GetSpell(FROST_ARROW_1))) + return; + } + else if (IsSpellReady(SHOOT_BOW_1, diff)) + { + if (doCast(mytar, SHOOT_BOW_1)) + return; + } + } + } + + bool CheckTornado(uint32 diff) + { + if (!IsSpellReady(TORNADO_1, diff, false) || !me->GetVictim() || me->GetPower(POWER_MANA) < TORNADO_COST || Rand() > 50) + return false; + + std::list targets; + GetNearbyTargetsList(targets, 40.f, 0); + targets.erase(std::remove_if(targets.begin(), targets.end(), [healthThreshold = uint32(me->GetMaxHealth() / 4 * 3)](Unit const* u) { + return u->GetHealth() < healthThreshold; + }), targets.end()); + + size_t targets_count = (IAmFree() || !master->GetGroup()) ? TORNADO_MIN_TARGETS : std::max(master->GetGroup()->GetMemberSlots().size() / 3, TORNADO_MIN_TARGETS); + if (targets.size() >= targets_count) + { + me->SetFacingTo(me->GetAbsoluteAngle(me->GetVictim())); + if (doCast(me->GetVictim(), GetSpell(TORNADO_1))) + return true; + } + + return false; + } + + bool CheckForkedLightning(uint32 diff) + { + if (!IsSpellReady(FORKED_LIGHTNING_1, diff, false) || !me->GetVictim() || me->GetPower(POWER_MANA) < FORKEDLIGHTNING_COST || + Rand() > 90 || !me->HasInArc(float(M_PI), me->GetVictim())) + return false; + + std::list targets; + GetNearbyTargetsInConeList(targets, CalcSpellMaxRange(FORKED_LIGHTNING_1) - 5.f); + if (targets.size() > ((me->GetLevel() < 60) ? 1u : 0u)) + { + me->SetFacingTo(me->GetAbsoluteAngle(me->GetVictim())); + if (doCast(me->GetVictim(), GetSpell(FORKED_LIGHTNING_1))) + return true; + } + + return false; + } + + void ApplyClassDamageMultiplierMelee(uint32& damage, CalcDamageInfo& /*damageinfo*/) const override + { + if (IsInContactWithWater()) + { + //TC_LOG_ERROR("scripts", "ApplyClassDamageMultiplierMelee: {} now in water", me->GetName()); + damage *= 3; + } + } + + void ApplyClassDamageMultiplierMeleeSpell(int32& damage, SpellNonMeleeDamage& /*damageinfo*/, SpellInfo const* /*spellInfo*/, WeaponAttackType /*attackType*/, bool /*crit*/) const override + { + if (IsInContactWithWater()) + { + //TC_LOG_ERROR("scripts", "ApplyClassDamageMultiplierMelee: {} now in water", me->GetName()); + damage *= 3; + } + } + + void ApplyClassDamageMultiplierSpell(int32& damage, SpellNonMeleeDamage& /*damageinfo*/, SpellInfo const* spellInfo, WeaponAttackType /*attackType*/, bool iscrit) const override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + uint8 lvl = me->GetLevel(); + float fdamage = float(damage); + float flat_mod = 0.f; + + if (IsInContactWithWater()) + { + //TC_LOG_ERROR("scripts", "ApplyClassDamageMultiplierSpell: {} now in water", me->GetName()); + fdamage *= 3.f; + } + + //2) apply bonus damage mods + float pctbonus = 1.0f; + if (iscrit) + { + //!!!spell damage is not yet critical and will be multiplied by 1.5 + //so we should put here bonus damage mult /1.5 + if (/*baseId == FROST_ARROW_1 || */baseId == FORKED_LIGHTNING_1) + pctbonus *= 1.33f; + } + + if (baseId == FORKED_LIGHTNING_1) + { + constexpr float basecoef = 2.5f / 80.f; + float coef = basecoef * (lvl - 3); + fdamage += me->SpellBaseDamageBonusDone(SPELL_SCHOOL_MASK_MAGIC) * coef * me->CalculateDefaultCoefficient(spellInfo, SPELL_DIRECT_DAMAGE) * me->CalculateSpellpowerCoefficientLevelPenalty(spellInfo); + } + + damage = int32(fdamage * pctbonus + flat_mod); + } + + void ApplyClassSpellCastTimeMods(SpellInfo const* spellInfo, int32& casttime) const override + { + //uint32 spellId = spellInfo->Id; + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + //uint8 lvl = me->GetLevel(); + int32 timebonus = 0; + float pctbonus = 0.0f; + + //100% mods + //if () + // pctbonus += 1.0f; + + //pct mods + //Frost Arrow affect by attack speed + if (baseId == FROST_ARROW_1) + pctbonus += 1.0f - me->m_modAttackSpeedPct[RANGED_ATTACK]; + + //flat mods + //Starlight Wrath: -0.5 sec cast time for Wrath and Starfire + //if (lvl >= 10 && (baseId == WRATH_1 || baseId == STARFIRE_1)) + // timebonus += 500; + + casttime = std::max(int32((float(casttime) * (1.0f - pctbonus)) - timebonus), 0); + } + + void ApplyClassSpellMaxTargetsMods(SpellInfo const* spellInfo, uint32& targets) const override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + uint8 lvl = me->GetLevel(); + + if (baseId == FORKED_LIGHTNING_1) + { + switch (lvl / 10) + { + case 8: targets = 666; break; + case 7: targets = 10; break; + case 6: targets = 7; break; + case 5: targets = 6; break; + case 4: targets = 5; break; + case 3: targets = 4; break; + case 2: targets = 3; break; + case 1: targets = 3; break; + default: break; + } + } + } + + void ApplyClassEffectMods(SpellInfo const* spellInfo, uint8 effIndex, float& value) const override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + uint8 lvl = me->GetLevel(); + float pctbonus = 1.0f; + + if (baseId == FROST_ARROW_EFFECT && (effIndex == EFFECT_0 || effIndex == EFFECT_1)) + { + switch (lvl / 10) + { + case 8: case 7: case 6: value = -70; break; + case 5: case 4: value = -50; break; + default: break; + } + } + + value = value * pctbonus; + } + + void ApplyClassEffectValueMultiplierMods(SpellInfo const* spellInfo, SpellEffIndex effIndex, float& multiplier) const override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //uint8 lvl = me->GetLevel(); + float pctbonus = 1.0f; + + //Mana Shield absorption modifier + //Base is 10.f + if (baseId == MANA_SHIELD_1 && effIndex == EFFECT_0) + pctbonus *= _manaPerDamageMult(); + + multiplier = multiplier * pctbonus; + } + + void OnClassSpellStart(SpellInfo const* spellInfo) override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + switch (baseId) + { + case FORKED_LIGHTNING_1: + case TORNADO_1: + _spell_preact = true; + break; + default: + break; + } + } + + void OnClassSpellGo(SpellInfo const* spellInfo) override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + + switch (baseId) + { + case FROST_ARROW_1: + case FORKED_LIGHTNING_1: + case TORNADO_1: + { + uint32 attackTime = uint32(me->GetAttackTime(RANGED_ATTACK) * me->m_modAttackSpeedPct[RANGED_ATTACK]); + if (baseId == FROST_ARROW_1) + { + uint32 castTime = spellInfo->CalcCastTime(); + GC_Timer = castTime >= attackTime ? 0 : attackTime - castTime; + } + if (baseId == FORKED_LIGHTNING_1 || baseId == TORNADO_1) + { + me->resetAttackTimer(); + GC_Timer = attackTime; + //me->CastSpell(me, MH_ATTACK_ANIM, true); + } + break; + } + default: + break; + } + + if (baseId == TORNADO_1) + SummonBotPet(); + } + + void SpellHitTarget(WorldObject* wtarget, SpellInfo const* spell) override + { + Unit* target = wtarget->ToUnit(); + if (!target) + return; + + if (target == me) + return; + + uint32 baseId = spell->GetFirstRankSpell()->Id; + //uint8 lvl = me->GetLevel(); + + if (baseId == FORKED_LIGHTNING_1) + { + //Forked Lightning stun + me->CastSpell(target, FORKED_LIGHTNING_EFFECT, true); + } + if (baseId == FROST_ARROW_1) + { + if (AuraEffect* sarr = target->GetAuraEffect(SPELL_AURA_MOD_SPEED_SLOW_ALL, SPELLFAMILY_GENERIC, 0x0, 0x4, 0x0, me->GetGUID())) + { + sarr->GetBase()->RefreshDuration(); + } + else if (Aura* arro = me->AddAura(FROST_ARROW_EFFECT, target)) + { + int32 dur = target->IsPlayer() ? 2000 : 6000; + arro->SetDuration(dur); + arro->SetMaxDuration(dur); + } + } + if (baseId == SPELL_PARALYTIC_POISON) + { + if (Aura* para = target->GetAura(SPELL_PARALYTIC_POISON, me->GetGUID())) + { + static constexpr int32 duration_threshold = 6000; + if (para->GetMaxDuration() > duration_threshold) + { + para->SetDuration(duration_threshold); + para->SetMaxDuration(duration_threshold); + } + else + para->RefreshDuration(); + } + } + + OnSpellHitTarget(target, spell); + } + + void SpellHit(WorldObject* wcaster, SpellInfo const* spell) override + { + Unit* caster = wcaster->ToUnit(); + if (!caster) + return; + + OnSpellHit(caster, spell); + } + + void OnBotDamageDealt(Unit* victim, uint32 damage, CleanDamage const* cleanDamage, DamageEffectType /*damagetype*/, SpellInfo const* /*spellInfo*/) override + { + if (damage && victim && cleanDamage && (cleanDamage->attackType == BASE_ATTACK || cleanDamage->attackType == OFF_ATTACK) && + victim->IsWithinCombatRange(me, ATTACK_DISTANCE)) + { + if (urand(0, 100) < 5) + { + int32 baseAmount = 1; + if (AuraEffect* pois = victim->GetAuraEffect(SPELL_PARALYTIC_POISON, EFFECT_0, me->GetGUID())) + baseAmount = pois->GetAmount() * 2; + else + baseAmount = int32(me->GetFloatValue(UNIT_FIELD_MAXDAMAGE)) + 1; + CastSpellExtraArgs args(true); + args.AddSpellBP0(baseAmount); + me->CastSpell(victim, SPELL_PARALYTIC_POISON, args); + } + } + } + + void DamageDealt(Unit* victim, uint32& damage, DamageEffectType damageType) override + { + bot_ai::DamageDealt(victim, damage, damageType); + } + + void DamageTaken(Unit* u, uint32& damage, DamageEffectType /*damageType*/, SpellInfo const* /*spellInfo*/) override + { + if (damage && me->HasAuraType(SPELL_AURA_MANA_SHIELD)) + me->RemoveAurasDueToSpell(MANA_SHIELD_1); + + if (!u) + return; + if (!u->IsInCombat() && !me->IsInCombat()) + return; + OnOwnerDamagedBy(u); + } + + void OwnerAttackedBy(Unit* u) override + { + if (!u) + return; + OnOwnerDamagedBy(u); + } + + uint8 GetPetPositionNumber(Creature const* /*summon*/) const override + { + return 0; + } + + void SummonBotPet() + { + if (!_minions.empty()) + UnsummonAll(false); + + Position spos; + if (Unit const* mytar = me->GetVictim()) + mytar->GetNearPoint(mytar, spos.m_positionX, spos.m_positionY, spos.m_positionZ, me->GetDistance2d(mytar) * 0.25f, mytar->GetAbsoluteAngle(me)); + else + me->GetNearPoint(me, spos.m_positionX, spos.m_positionY, spos.m_positionZ, 10.f, 0.f); + + Creature* myPet = me->SummonCreature(BOT_PET_TORNADO, spos, TEMPSUMMON_CORPSE_DESPAWN); + myPet->SetCreator(master); + myPet->SetOwnerGUID(me->GetGUID()); + myPet->SetFaction(master->GetFaction()); + myPet->SetControlledByPlayer(!IAmFree()); + myPet->SetPvP(me->IsPvP()); + myPet->SetByteValue(UNIT_FIELD_BYTES_2, 1, master->GetByteValue(UNIT_FIELD_BYTES_2, 1)); + myPet->SetUInt32Value(UNIT_CREATED_BY_SPELL, TORNADO_1); + + CreatureMovementData& cmData = const_cast(myPet->GetMovementTemplate()); + cmData.Swim = false; + cmData.Ground = CreatureGroundMovementType::Run; + cmData.Flight = CreatureFlightMovementType::None; + + _minions.insert(myPet); + } + + void UnsummonAll(bool savePets = true) override + { + UnsummonCreatures(_minions, savePets); + } + + void SummonedCreatureDies(Creature* /*summon*/, Unit* /*killer*/) override + { + //TC_LOG_ERROR("entities.unit", "SummonedCreatureDies: {}'s {}", me->GetName(), summon->GetName()); + //if (summon == botPet) + // botPet = nullptr; + } + + void SummonedCreatureDespawn(Creature* summon) override + { + //TC_LOG_ERROR("entities.unit", "SummonedCreatureDespawn: {}'s {}", me->GetName(), summon->GetName()); + if (_minions.find(summon) != _minions.end()) + _minions.erase(summon); + } + + float GetSpellAttackRange(bool longRange) const override + { + return longRange ? CalcSpellMaxRange(FROST_ARROW_1) - 6.f : CalcSpellMaxRange(FROST_ARROW_1) - 15.f; + } + + uint32 GetAIMiscValue(uint32 data) const override + { + switch (data) + { + case BOTAI_MISC_PET_TYPE: + return BOT_PET_TORNADO; + default: + return 0; + } + } + + void Reset() override + { + UnsummonAll(false); + + _spell_preact = false; + + DefaultInit(); + + //swim mod + if (Aura* swim = me->AddAura(NAGA_SWIM_PASSIVE, me)) + swim->GetEffect(0)->ChangeAmount(200); + + //threat mod + if (Aura* threat = me->AddAura(SPELL_THREAT_MOD, me)) + threat->GetEffect(0)->ChangeAmount(-50); + } + + void ReduceCD(uint32 /*diff*/) override + { + //if (trapTimer > diff) trapTimer -= diff; + } + + void InitPowers() override + { + me->SetPowerType(POWER_MANA); + } + + void InitSpells() override + { + //uint8 lvl = me->GetLevel(); + //InitSpellMap(AUTO_SHOT_1); + InitSpellMap(SHOOT_BOW_1); + InitSpellMap(FORKED_LIGHTNING_1); + InitSpellMap(FROST_ARROW_1); + InitSpellMap(MANA_SHIELD_1); + InitSpellMap(TORNADO_1); + } + + void ApplyClassPassives() const override + { + } + + //bool CanUseManually(uint32 basespell) const override + //{ + // switch (basespell) + // { + // case FORKED_LIGHTNING_1: + // case TORNADO_1: + // return true; + // default: + // return false; + // } + //} + + bool HasAbilitiesSpecifics() const override { return true; } + void FillAbilitiesSpecifics(Player const* player, std::list &specList) override + { + bool amount_is_mana = true; + float amount = AssertBotSpellInfoOverride(MANA_SHIELD_1)->GetEffect(EFFECT_0).CalcValueMultiplier(me); //mana per damage + if (amount < 1.0f) + { + amount_is_mana = false; + amount = 1.f / amount; + } + + std::ostringstream amount_sstr; + amount_sstr.setf(std::ios_base::fixed); + amount_sstr.precision(1); + amount_sstr << amount; + uint32 text_id = amount_is_mana ? BOT_TEXT_MANA_PER_DAMAGE : BOT_TEXT_DAMAGE_PER_MANA; + + specList.push_back(LocalizedNpcText(player, text_id) + ": " + amount_sstr.str()); + } + + std::vector const* GetDamagingSpellsList() const override + { + return &Seawitch_spells_damage; + } + std::vector const* GetCCSpellsList() const override + { + return &Seawitch_spells_cc; + } + //std::vector const* GetHealingSpellsList() const override + //{ + // return &Seawitch_spells_heal; + //} + std::vector const* GetSupportSpellsList() const override + { + return &Seawitch_spells_support; + } + private: + typedef std::set Summons; + Summons _minions; + + bool _spell_preact; + + float _manaPerDamageMult() const + { + switch (me->GetLevel() / 10) + { + case 8: return 1.f / 100.00f; + case 7: return 1.f / 50.00f; + case 6: return 1.f / 20.00f; + case 5: return 1.f / 10.00f; + case 4: return 1.f / 4.00f; + case 3: return 1.f / 2.50f; + case 2: return 1.f / 1.67f; + case 1: return 1.f / 1.25f; + default:return 1.f / 1.00f; + } + } + }; +}; + +void AddSC_sea_witch_bot() +{ + new sea_witch_bot(); +} diff --git a/src/server/game/AI/NpcBots/bot_shaman_ai.cpp b/src/server/game/AI/NpcBots/bot_shaman_ai.cpp new file mode 100644 index 000000000..d8fd7267d --- /dev/null +++ b/src/server/game/AI/NpcBots/bot_shaman_ai.cpp @@ -0,0 +1,2849 @@ +#include "bot_ai.h" +#include "botmgr.h" +#include "bottext.h" +#include "bottraits.h" +#include "Containers.h" +#include "Group.h" +#include "Item.h" +#include "Log.h" +#include "Map.h" +#include "MotionMaster.h" +#include "ObjectAccessor.h" +#include "ObjectMgr.h" +#include "Player.h" +#include "ScriptMgr.h" +#include "SpellAuraEffects.h" +#include "Spell.h" +#include "SpellMgr.h" +#include "Totem.h" +#include "WorldSession.h" +/* +Shaman NpcBot (reworked by Trickerer onlysuffering@gmail.com) +Complete - around 90% +TODO: Elemental mastery (exclusive cd with NatSw), Lava Lash +Problems: +Unsummon elemental totems if Elementals are killed +Aura application bug for bot in other subgroup, maybe caused by creatorGUID mismatch +*/ + +#define MAX_WOLVES 2 +#define MAX_TOTEMS 4 + +enum ShamanBaseSpells +{ + HEALING_WAVE_1 = 331, + CHAIN_HEAL_1 = 1064, + LESSER_HEALING_WAVE_1 = 8004, + RIPTIDE_1 = 61295, + ANCESTRAL_SPIRIT_1 = 2008, + CURE_TOXINS_1 = 526, + CLEANSE_SPIRIT_1 = 51886, + FLAME_SHOCK_1 = 8050, + EARTH_SHOCK_1 = 8042, + FROST_SHOCK_1 = 8056, + STORMSTRIKE_1 = 17364, + LIGHTNING_BOLT_1 = 403, + CHAIN_LIGHTNING_1 = 421, + LAVA_BURST_1 = 51505, + THUNDERSTORM_1 = 51490, + LIGHTNING_SHIELD_1 = 324, + EARTH_SHIELD_1 = 974, + WATER_SHIELD_1 = 52127, + WATER_BREATHING_1 = 131, + WATER_WALKING_1 = 546, + PURGE_1 = 370, + WIND_SHEAR_1 = 57994, + HEX_1 = 51514, + BLOODLUST_1 = 2825, + HEROISM_1 = 32182, + SHAMANISTIC_RAGE_1 = 30823, + NATURES_SWIFTNESS_1 = 16188,//castegory = 1202 + //ELEMENTAL_MASTERY_1 = 16166,//castegory = 1202 NYI + TIDAL_FORCE_1 = 55198, + + GHOST_WOLF_1 = 2645, + + FIRE_NOVA_1 = 1535,//shaman spell + EARTHBIND_TOTEM_1 = 2484, + MAGMA_TOTEM_1 = 8190, + SEARING_TOTEM_1 = 3599, + STONECLAW_TOTEM_1 = 5730, + FIRE_ELEMENTAL_TOTEM_1 = 2894, + EARTH_ELEMENTAL_TOTEM_1 = 2062, + FIRE_RESISTANCE_TOTEM_1 = 8184, + FROST_RESISTANCE_TOTEM_1 = 8181, + NATURE_RESISTANCE_TOTEM_1 = 10595, + FLAMETONGUE_TOTEM_1 = 8227, + GROUNDING_TOTEM_1 = 8177, + SENTRY_TOTEM_1 = 6495, + STONESKIN_TOTEM_1 = 8071, + STRENGTH_OF_EARTH_TOTEM_1 = 8075, + WINDFURY_TOTEM_1 = 8512, + WRATH_OF_AIR_TOTEM_1 = 3738, + CLEANSING_TOTEM_1 = 8170, + HEALING_STREAM_TOTEM_1 = 5394, + MANA_SPRING_TOTEM_1 = 5675, + TOTEM_OF_WRATH_1 = 30706, + MANA_TIDE_TOTEM_1 = 16190, + TREMOR_TOTEM_1 = 8143, + + TOTEMIC_RECALL_1 = 36936, + + REINCARNATION_1 = 21169, + + FERAL_SPIRIT_1 = 51533, //not casted + + //ROCKBITER_WEAPON_1 = 8017, //disabled due to bonus handling method + FLAMETONGUE_WEAPON_1 = 8024, + FROSTBRAND_WEAPON_1 = 8033, + WINDFURY_WEAPON_1 = 8232, + EARTHLIVING_WEAPON_1 = 51730 +}; + +enum ShamanPassives +{ + //Elemental + ELEMENTAL_FOCUS = 16164,//clearcast + ELEMENTAL_DEVASTATION1 = 30160, + ELEMENTAL_DEVASTATION2 = 29179, + ELEMENTAL_DEVASTATION3 = 29180, + ELEMENTAL_OATH = 51470,//rank 2 + //STORM_EARTH_AND_FIRE = 51486,//rank 3 + //Enchancement + TOUGHNESS = 16309,//rank 5 + FLURRY1 = 16256, + FLURRY2 = 16281, + FLURRY3 = 16282, + FLURRY4 = 16283, + FLURRY5 = 16284, + WEAPON_MASTERY = 29086,//rank 3 + UNLEASHED_RAGE = 30809,//rank 3 + STATIC_SHOCK = 51527,//rank 3 + IMPROVED_STORMSTRIKE = 51522,//rank 2 + MAELSTROM_WEAPON1 = 51528, + MAELSTROM_WEAPON2 = 51529, + MAELSTROM_WEAPON3 = 51530, + MAELSTROM_WEAPON4 = 51531, + MAELSTROM_WEAPON5 = 51532, + EARTHEN_POWER = 51524,//rank 2 + //Restoration + ANCESTRAL_HEALING = 16240,//rank 3 + ANCESTRAL_AWAKENING = 51558,//rank 3 + IMPROVED_WATER_SHIELD = 16198,//rank 3 + TIDAL_WAVES = 51566,//rank 5 + //Special + GLYPH_THUNDERSTORM = 62132, + GLYPH_TOTEM_OF_WRATH = 63280, + SHAMAN_T10_RESTO_4P = 70808 //Chain Heal HoT +}; + +enum ShamanSpecial +{ + SHAMAN_FLAME_SHOCK_PASSIVE = 75461,//flame shock damage can be critical in 3.3.5, innate + + ELEMENTAL_FOCUS_BUFF = 16246, + TIDAL_FORCE_BUFF = 55166,//Unit::HandleAuraDummy(): case 55198: + + MAELSTROM_WEAPON_BUFF = 53817, + TIDAL_WAVES_BUFF = 53390, + STORMSTRIKE_DAMAGE = 32175, + STORMSTRIKE_DAMAGE_OFFHAND = 32176, + + LIGHTNING_SHIELD_DAMAGE_1 = 26364, + EARTH_SHIELD_HEAL = 379, + + RESURRECTION_VISUAL_SPELL = 21074, //Test NPC Resurrection + + EXHAUSTION_AURA = 57723, + SATED_AURA = 57724, + + WINDFURY_ATTACK_MAINHAND = 25504, + WINDFURY_ATTACK_OFFHAND = 33750, + + STORMEARTHANDFIRE_EARTHGRAB = 64695, + + //TOTEM_AURA_WRATH_AURA1 = 57658,//100 + //TOTEM_AURA_WRATH_AURA2 = 57660,//120 + //TOTEM_AURA_WRATH_AURA3 = 57662,//140 + //TOTEM_AURA_WRATH_AURA4 = 57663,//280 + //TOTEM_AURA_RESISTANCE_FIRE_1 = 8185, + //TOTEM_AURA_RESISTANCE_FROST_1 = 8182, + //TOTEM_AURA_RESISTANCE_NATURE_1 = 10596, + //TOTEM_AURA_FLAMETONGUE_1 = 52109, + //TOTEM_AURA_GROUNDING = 8178, + //TOTEM_AURA_STONESKIN_1 = 8072, + //TOTEM_AURA_STRENGTH_OF_EARTH_1 = 8076, + //TOTEM_AURA_WINDFURY = 8515, + //TOTEM_AURA_WRATH_OF_AIR = 2895, + //TOTEM_AURA_MANA_SPRING_1 = 5677 +}; + +enum TotemSlot +{ + T_FIRE = 0,//m_SummonSlot[1] + T_EARTH = 1,//m_SummonSlot[2] + T_WATER = 2,//m_SummonSlot[3] + T_AIR = 3,//m_SummonSlot[4] +}; +enum BotTotemType : uint32 +{ + BOT_TOTEM_NONE = 0, + BOT_TOTEM_STRENGTH_OF_EARTH = 1, //main earth totem + BOT_TOTEM_FLAMETONGUE = 2, //main fire totem + BOT_TOTEM_WRATH = 3, //main fire totem + BOT_TOTEM_MANA_SPRING = 4, //main water totem + BOT_TOTEM_WINDFURY = 5, //main air totem + BOT_TOTEM_WRATH_OF_AIR = 6, //main air totem + BOT_TOTEM_STONESKIN = 7, //secondary earth totem UNUSED + BOT_TOTEM_HEALING_STREAM = 8, //secondary water totem UNUSED + BOT_TOTEM_TREMOR = 9, //situative earth 1 + BOT_TOTEM_EARTHBIND = 10, //situative earth 2 + BOT_TOTEM_MAGMA = 11, //situative fire 1 + BOT_TOTEM_MANA_TIDE = 12, //situative water 1 + BOT_TOTEM_CLEANSING = 13, //situative water 2 non-raid + BOT_TOTEM_GROUNDING = 14, //situative air 1 + BOT_TOTEM_RESISTANCE_FROST = 15, //manual fire 1 + BOT_TOTEM_RESISTANCE_FIRE = 16, //manual water 1 + BOT_TOTEM_RESISTANCE_NATURE = 17, //manual air 1 + BOT_TOTEM_ELEMENTAL_EARTH = 18, //manual earth 1 + BOT_TOTEM_ELEMENTAL_FIRE = 19, //manual fire 2 + BOT_TOTEM_SENTRY = 20, //UNUSED + BOT_TOTEM_STONECLAW = 21, //UNUSED + BOT_TOTEM_SEARING = 22, //UNUSED, annoying as hell too + BOT_TOTEM_END, + + BOT_TOTEM_FLAG_MY_TOTEM_FIRE, + BOT_TOTEM_FLAG_MY_TOTEM_EARTH, + BOT_TOTEM_FLAG_MY_TOTEM_WATER, + BOT_TOTEM_FLAG_MY_TOTEM_AIR, + + BOT_TOTEM_MASK_SUMMONS = ((1< Shaman_spells_damage(FROM_ARRAY(Shaman_spells_damage_arr)); +static const std::vector Shaman_spells_cc(FROM_ARRAY(Shaman_spells_cc_arr)); +static const std::vector Shaman_spells_heal(FROM_ARRAY(Shaman_spells_heal_arr)); +static const std::vector Shaman_spells_support(FROM_ARRAY(Shaman_spells_support_arr)); + +class shaman_bot : public CreatureScript +{ +public: + shaman_bot() : CreatureScript("shaman_bot") { } + + CreatureAI* GetAI(Creature* creature) const override + { + return new shaman_botAI(creature); + } +/* + bool OnGossipHello(Player* player, Creature* creature) + { + return creature->GetBotAI()->OnGossipHello(player, 0); + } + + bool OnGossipSelect(Player* player, Creature* creature, uint32 sender, uint32 action) + { + if (bot_ai* ai = creature->GetBotAI()) + return ai->OnGossipSelect(player, creature, sender, action); + return true; + } + + bool OnGossipSelectCode(Player* player, Creature* creature, uint32 sender, uint32 action, char const* code) + { + if (bot_ai* ai = creature->GetBotAI()) + return ai->OnGossipSelectCode(player, creature, sender, action, code); + return true; + } +*/ + struct shaman_botAI : public bot_ai + { + shaman_botAI(Creature* creature) : bot_ai(creature) + { + _botclass = BOT_CLASS_SHAMAN; + + InitUnitFlags(); + } + + bool doCast(Unit* victim, uint32 spellId) + { + if (CheckBotCast(victim, spellId) != SPELL_CAST_OK) + return false; + return bot_ai::doCast(victim, spellId); + } + + //only for totems + bool doCast(Unit* victim, uint32 spellId, TriggerCastFlags flags) + { + if (CheckBotCast(victim, spellId) != SPELL_CAST_OK) + return false; + if (bot_ai::doCast(victim, spellId, flags)) + { + //Calls: 1.5 sec, totems: 1 sec + GC_Timer = (flags & TRIGGERED_CAST_DIRECTLY) ? 1500 : 1000; + return true; + } + + return false; + } + + void StartAttack(Unit* u, bool force = false) + { + if (!bot_ai::StartAttack(u, force)) + return; + GetInPosition(force, u); + } + + void JustEnteredCombat(Unit* u) override { TotemsCheckTimer = 0; canTremor = false; bot_ai::JustEnteredCombat(u); } + void KilledUnit(Unit* u) override { bot_ai::KilledUnit(u); } + void EnterEvadeMode(EvadeReason why = EVADE_REASON_OTHER) override { bot_ai::EnterEvadeMode(why); } + void MoveInLineOfSight(Unit* u) override { bot_ai::MoveInLineOfSight(u); } + void JustDied(Unit* u) override { UnsummonAll(false); removeShapeshiftForm(); bot_ai::JustDied(u); } + + bool removeShapeshiftForm() override + { + ShapeshiftForm form = me->GetShapeshiftForm(); + if (form != FORM_NONE) + { + switch (form) + { + case FORM_GHOSTWOLF: + me->RemoveAurasDueToSpell(GHOST_WOLF_1); + break; + default: + break; + } + } + + return true; + } + + void CheckBloodlust(uint32 diff) + { + if (BloodlustCheckTimer > diff || (!me->IsInCombat() && !master->IsInCombat()) || + me->GetDistance(master) > 18 || Rand() > 35) + return; + + BloodlustCheckTimer = 3000; + + uint32 BLOODLUST = (me->GetRaceMask() & RACEMASK_ALLIANCE) ? HEROISM_1 : BLOODLUST_1; + if (!IsSpellReady(BLOODLUST, diff)) + return; + + //already rockin' + if (me->GetAuraEffect(SPELL_AURA_MOD_MELEE_RANGED_HASTE, SPELLFAMILY_SHAMAN, 0x0, 0x40, 0x0) || + master->GetAuraEffect(SPELL_AURA_MOD_MELEE_RANGED_HASTE, SPELLFAMILY_SHAMAN, 0x0, 0x40, 0x0)) + return; + + //environment conditions + Unit const* u = me->GetVictim(); + Creature const* cre = u ? u->ToCreature() : nullptr; + if (!(u && (u->GetHealth() > me->GetMaxHealth() * 10 || u->GetTypeId() == TYPEID_PLAYER || + (cre && (cre->IsDungeonBoss() || cre->isWorldBoss())) || + me->getAttackers().size() + master->getAttackers().size() >= 8))) + return; + + //BLOODLUST = GetSpell(BLOODLUST); //not ranked + + uint32 sateSpell = (me->GetRaceMask() & RACEMASK_ALLIANCE) ? EXHAUSTION_AURA : SATED_AURA; + Unit::AuraEffectList const& dummies = me->GetAuraEffectsByType(SPELL_AURA_DUMMY); + for (Unit::AuraEffectList::const_iterator itr = dummies.begin(); itr != dummies.end(); ++itr) + { + if ((*itr)->GetEffIndex() != 0) continue; + SpellInfo const* spellInfo = (*itr)->GetSpellInfo(); + if (spellInfo->SpellFamilyName != SPELLFAMILY_GENERIC || spellInfo->SpellIconID != 44) continue; + if (spellInfo->Id == sateSpell) + return; //can't cast my type of bloodlust + } + + me->InterruptNonMeleeSpells(true); + if (doCast(me, BLOODLUST)) + return; + } + + void CheckTotems(uint32 diff) + { + if (TotemsCheckTimer > diff) + return; + + TotemsCheckTimer = urand(1500, 2000) + (!IAmFree() ? 100 * master->GetNpcBotsCount() / 2 : 0); + + //Unsummon + for (uint8 i = 0; i != MAX_TOTEMS; ++i) + { + if (_totems[i].first != ObjectGuid::Empty && + (!master->IsAlive() || master->GetDistance(_totems[i].second._pos) > _totems[i].second._effradius) && + me->GetDistance(_totems[i].second._pos) > _totems[i].second._effradius) + { + //Check if we can use totemic recall and regain some mana + if (!me->GetVictim()/* && GetManaPCT(me) < 90*/ && IsSpellReady(TOTEMIC_RECALL_1, diff)) + { + uint8 count = 0; + for (uint8 j = 0; j != MAX_TOTEMS; ++j) + { + if (j == i || _totems[j].first == ObjectGuid::Empty) continue; + if (me->GetDistance(_totems[j].second._pos) > 20.f) + ++count; + } + if (count > 1) + { + if (doCast(me, GetSpell(TOTEMIC_RECALL_1))) + return; + } + } + Unit* to = ObjectAccessor::GetUnit(*me, _totems[i].first); + if (!to) + { + TC_LOG_ERROR("entities.player", "{} has unexpectingly lost totem in slot {}!", me->GetName(), i); + _totems[i].first = ObjectGuid::Empty; + continue; + } + to->ToTotem()->UnSummon(); + //reset summon check timer; + TotemTimer[i] = 0; + } + } + //global cooldown is not performed below, intead there is a special condition for Calls + if (GC_Timer > diff || me->IsMounted() || Feasting() || IsCasting() || + (master->IsAlive() && me->GetDistance(master) > 15)) + return; + + bool CotE = me->GetLevel() >= 30; //Call of the Elements is at level 30; + + std::map idMap; + uint32 mask = _getTotemsMask(idMap); + Group const* gr = GetGroup(); + std::vector members = BotMgr::GetAllGroupMembers(gr); + uint8 subgr = GetSubGroup(); + + //EARTH + //EARTHsituative1 : tremor + if (TotemTimer[T_EARTH] <= diff && me->IsInCombat() && !IAmFree() && + IsSpellReady(TREMOR_TOTEM_1, diff, false) && _totems[T_EARTH].second._type != BOT_TOTEM_TREMOR) + { + //Tremor no cd + if (Unit const* victim = me->GetVictim()) + { + if (Spell const* vspell = victim->GetCurrentSpell(CURRENT_GENERIC_SPELL)) + { + if (vspell->m_targets.GetUnitTargetGUID() == me->GetGUID()) + { + SpellInfo const* vspellInfo = vspell->GetSpellInfo(); + static const std::array TremorMechanics = { MECHANIC_FEAR, MECHANIC_CHARM, MECHANIC_SLEEP }; + static const auto is_tremor_effect = [](SpellEffectInfo const& effect) { return effect.IsAura(SPELL_AURA_MOD_FEAR) || effect.IsAura(SPELL_AURA_MOD_CHARM); }; + if (std::find(TremorMechanics.cbegin(), TremorMechanics.cend(), vspellInfo->Mechanic) != TremorMechanics.cend() || + std::any_of(vspellInfo->_effects.cbegin(), vspellInfo->_effects.cend(), is_tremor_effect)) + { + canTremor = true; + } + } + } + } + if (!canTremor) + { + uint8 count = 0; + for (Unit const* member : members) + { + if (me->GetMap() != member->FindMap() || !member->InSamePhase(me) || + !member->IsAlive() || me->GetDistance(member) > 20 || + (member->IsPlayer() ? member->ToPlayer()->GetSubGroup() : member->ToCreature()->GetSubGroup()) != subgr || + (member->IsNPCBot() && member->ToCreature()->IsTempBot()) || + !member->HasAuraWithMechanic((1<= (1 + 1*(!!(mask & BOT_TOTEM_MASK_MY_TOTEM_EARTH))); + } + if (canTremor) + { + if (doCast(me, GetSpell(TREMOR_TOTEM_1), CotE ? TRIGGERED_CAST_DIRECTLY : TRIGGERED_NONE)) + if (!CotE) + return; + } + //check if casted + if (_totems[T_EARTH].second._type != BOT_TOTEM_TREMOR) + SetSpellCooldown(TREMOR_TOTEM_1, 3000); //fail + } + + if (!(mask & BOT_TOTEM_MASK_MY_TOTEM_EARTH) && TotemTimer[T_EARTH] <= diff && me->IsInCombat()) + { + //EARTHsituative2 : earthbind + if (GetSpell(EARTHBIND_TOTEM_1)/* && _totems[T_EARTH].second.type != BOT_TOTEM_EARTHBIND*/) + { + //15 sec cd, nearby enemies, instant effect + std::list targets; + GetNearbyTargetsList(targets, 15.f, 1); + for (std::list::iterator itr = targets.begin(); itr != targets.end();) + { + Unit* u = *itr; + bool erase = false; + if (u->HasAuraType(SPELL_AURA_MOD_DECREASE_SPEED) || u->HasAuraType(SPELL_AURA_MOD_SPEED_SLOW_ALL)) + erase = true; + else if (u->isMoving()) + { + if (me->GetDistance(u) > 10.f && !u->HasInArc(float(M_PI)/2, me)) + erase = true; + } + else if (me->GetDistance(u) > 9.f) + erase = true; + + if (erase) + { + targets.erase(itr++); + continue; + } + ++itr; + } + + if (uint8(targets.size()) >= (1 + 2*((mask & BOT_TOTEM_MASK_MY_TOTEM_EARTH) != 0))) + { + if (doCast(me, GetSpell(EARTHBIND_TOTEM_1), CotE ? TRIGGERED_CAST_DIRECTLY : TRIGGERED_NONE)) + if (!CotE) + return; + } + } + } + + if (!(mask & BOT_TOTEM_MASK_MY_TOTEM_EARTH) && TotemTimer[T_EARTH] <= diff && me->IsInCombat()) + { + //EARTHmain : strength of earth + uint32 SoE = GetSpell(STRENGTH_OF_EARTH_TOTEM_1); //tripple check + if (SoE && + (!(mask & BOT_TOTEM_MASK_STRENGTH_OF_EARTH) || idMap[STRENGTH_OF_EARTH_TOTEM_1] < SoE)) + { + //no cd + if (doCast(me, SoE, CotE ? TRIGGERED_CAST_DIRECTLY : TRIGGERED_NONE)) + if (!CotE) + return; + } + + //EARTHlast : earth elemental (for mass taunt) + //uint32 earthElem = GetSpell(EARTH_ELEMENTAL_TOTEM_1); + //if (earthElem && IsSpellReady(EARTH_ELEMENTAL_TOTEM_1, diff) && !me->GetMap()->IsDungeon() && + // me->getAttackers().size() > 1) + //{ + // //no cd + // if (doCast(me, earthElem, CotE ? TRIGGERED_CAST_DIRECTLY : TRIGGERED_NONE)) + // if (!CotE) + // return; + //} + } + + //FIRE + //FIREsituative1 : magma + if (TotemTimer[T_FIRE] <= diff && me->IsInCombat() && !IAmFree() && HasRole(BOT_ROLE_DPS) && + GetSpell(MAGMA_TOTEM_1)/* && _totems[T_FIRE].second.type != BOT_TOTEM_MAGMA*/) + { + //magma no cd 8 yd, 2 sec delay before first tick + std::list targets; + GetNearbyTargetsList(targets, 13.f, 1); + for (std::list::iterator itr = targets.begin(); itr != targets.end();) + { + Unit* u = *itr; + bool erase = false; + if (u->isMoving()) + { + if (me->GetDistance(u) > 10.f && !u->HasInArc(float(M_PI)/2, me)) + erase = true; + } + else if (me->GetDistance(u) > 7.f) + erase = true; + + if (erase) + { + targets.erase(itr++); + continue; + } + ++itr; + } + + if (uint8(targets.size()) >= (3 + 3*((mask & BOT_TOTEM_MASK_MY_TOTEM_FIRE) != 0))) + { + if (doCast(me, GetSpell(MAGMA_TOTEM_1), CotE ? TRIGGERED_CAST_DIRECTLY : TRIGGERED_NONE)) + if (!CotE) + return; + } + } + + if (!(mask & BOT_TOTEM_MASK_MY_TOTEM_FIRE) && me->IsInCombat() && TotemTimer[T_FIRE] <= diff) + { + //FIREMain : wrath or flametongue no cd + //aura is exclusive so check mask + uint32 base = TOTEM_OF_WRATH_1; + uint32 wrathTotem = GetSpell(TOTEM_OF_WRATH_1); + uint32 fMask = BOT_TOTEM_MASK_WRATH; + if (!wrathTotem) + { + base = FLAMETONGUE_TOTEM_1; + wrathTotem = GetSpell(FLAMETONGUE_TOTEM_1); + fMask = BOT_TOTEM_MASK_FLAMETONGUE; + } + if (wrathTotem && + ((mask & BOT_TOTEM_MASK_PRIMARY_FIRE) < fMask || + ((mask & BOT_TOTEM_MASK_PRIMARY_FIRE) == fMask && idMap[base] < wrathTotem))) + { + if (doCast(me, wrathTotem, CotE ? TRIGGERED_CAST_DIRECTLY : TRIGGERED_NONE)) + if (!CotE) + return; + } + + //FIREaddin : fire elemental + //uint32 fireElem = GetSpell(FIRE_ELEMENTAL_TOTEM_1); + //if (fireElem && IsSpellReady(FIRE_ELEMENTAL_TOTEM_1, diff) && !me->GetMap()->IsDungeon()) + //{ + // if (doCast(me, fireElem, CotE ? TRIGGERED_CAST_DIRECTLY : TRIGGERED_NONE)) + // if (!CotE) + // return; + //} + } + + if (!(mask & BOT_TOTEM_MASK_MY_TOTEM_FIRE) && me->IsInCombat() && TotemTimer[T_FIRE] <= diff) + { + //FIRElastresort : frostres (3 shamans of same level req) + uint32 frostRes = GetSpell(FROST_RESISTANCE_TOTEM_1); + if (frostRes && !IAmFree() && + (!(mask & BOT_TOTEM_MASK_RESISTANCE_FROST) || idMap[FROST_RESISTANCE_TOTEM_1] < frostRes)) + { + if (doCast(me, frostRes, CotE ? TRIGGERED_CAST_DIRECTLY : TRIGGERED_NONE)) + if (!CotE) + return; + } + } + + //WATER + //WATERsituative1 : manatide + if (TotemTimer[T_WATER] <= diff && me->IsInCombat() && !IAmFree() && + IsSpellReady(MANA_TIDE_TOTEM_1, diff, false)) + { + //5 min cd, party members only, instant effect +4 ticks in 12 secs + bool cast = false; + if (master->IsInCombat() && master->GetPowerType() == POWER_MANA && + GetManaPCT(master) < 35 && me->GetDistance(master) < 18) + cast = true; + else if (me->IsInCombat() && GetManaPCT(me) < 35) + cast = true; + else + { + uint8 count = 0; + for (Unit const* member : members) + { + if (me->GetMap() != member->FindMap() || !member->InSamePhase(me) || + !member->IsAlive() || !member->IsInCombat() || member->GetPowerType() != POWER_MANA || + (member->IsPlayer() ? member->ToPlayer()->GetSubGroup() : member->ToCreature()->GetSubGroup()) != subgr || + GetManaPCT(member) > 35 || me->GetDistance(member) > 20 || + (member->IsNPCBot() && member->ToCreature()->IsTempBot())) + continue; + ++count; + } + cast = (count >= (3 + 1*(!!(mask & BOT_TOTEM_MASK_MY_TOTEM_WATER)))); + } + if (cast) + { + if (doCast(me, GetSpell(MANA_TIDE_TOTEM_1), CotE ? TRIGGERED_CAST_DIRECTLY : TRIGGERED_NONE)) + if (!CotE) + return; + } + //check if casted + if (_totems[T_WATER].second._type != BOT_TOTEM_MANA_TIDE) + SetSpellCooldown(MANA_TIDE_TOTEM_1, 3000); //fail + } + + //WATERsituative2 : cleansing + //REMOVED CHECKS ARE TOO HEAVY + + if (!(mask & BOT_TOTEM_MASK_MY_TOTEM_WATER) && TotemTimer[T_WATER] <= diff) + { + //WATERmain : manaspring + uint32 MSpring = GetSpell(MANA_SPRING_TOTEM_1); //tripple check + if (MSpring && (me->IsInCombat() || !master->isMoving()) && + (!(mask & BOT_TOTEM_MASK_MANA_SPRING) || idMap[MANA_SPRING_TOTEM_1] < MSpring)) + { + //no cd + bool cast = false; + if (!master->isMoving() && master->GetPowerType() == POWER_MANA && GetManaPCT(master) < 85) + cast = true; + else if (!me->isMoving() && GetManaPCT(me) < 95) + cast = true; + else + { + for (Unit const* member : members) + { + if (me->GetMap() != member->FindMap() || !member->InSamePhase(me) || + !member->IsAlive() || member->GetPowerType() != POWER_MANA || + GetManaPCT(member) > 85 || me->GetDistance(member) > 25 || + (member->IsNPCBot() && member->ToCreature()->IsTempBot())) + continue; + cast = true; + break; + } + } + if (cast) + { + if (doCast(me, MSpring, CotE ? TRIGGERED_CAST_DIRECTLY : TRIGGERED_NONE)) + if (!CotE) + return; + } + } + } + + if (!(mask & BOT_TOTEM_MASK_MY_TOTEM_WATER) && TotemTimer[T_WATER] <= diff) + { + //WATERlastresort : fireres (2-3 shamans of same level req) + uint32 fireRes = GetSpell(FIRE_RESISTANCE_TOTEM_1); + if (fireRes && TotemTimer[T_WATER] <= diff && me->IsInCombat() && !IAmFree() && + (!(mask & BOT_TOTEM_MASK_RESISTANCE_FIRE) || idMap[FIRE_RESISTANCE_TOTEM_1] < fireRes)) + { + if (doCast(me, fireRes, CotE ? TRIGGERED_CAST_DIRECTLY : TRIGGERED_NONE)) + if (!CotE) + return; + } + } + + //AIR + //AIRsituative1 : grounding + if (TotemTimer[T_AIR] <= diff && me->IsInCombat() &&/* !IAmFree() &&*/ + IsSpellReady(GROUNDING_TOTEM_1, diff, false)) + { + //grounding 15 sec cd, party members only (and bot and master of course) + bool cast = false; + if (Unit const* u = FindCastingTarget(27)) //totem must be within cast distance + { + if (Spell const* spell = u->GetCurrentSpell(CURRENT_GENERIC_SPELL)) + { + ObjectGuid tGuid = spell->m_targets.GetUnitTargetGUID(); + if (tGuid == me->GetGUID() || tGuid == master->GetGUID() || (gr && gr->IsMember(tGuid) && gr->SameSubGroup(tGuid, me->GetGUID()))) + { + Unit const* t = ObjectAccessor::GetUnit(*me, tGuid); + if (t && t->GetDistance(me) < 27 && !t->HasAuraType(SPELL_AURA_SPELL_MAGNET)) + cast = true; + } + } + } + if (cast) + { + if (doCast(me, GetSpell(GROUNDING_TOTEM_1), CotE ? TRIGGERED_CAST_DIRECTLY : TRIGGERED_NONE)) + if (!CotE) + return; + } + } + + if (!(mask & BOT_TOTEM_MASK_MY_TOTEM_AIR) && TotemTimer[T_AIR] <= diff && me->IsInCombat()) + { + //AIRmain1 : wrathofair - if windfury is disabled or me and master both casters + if (!(mask & BOT_TOTEM_MASK_WRATH_OF_AIR) && GetSpell(WRATH_OF_AIR_TOTEM_1)) + { + bool cast = false; + + if (!IsMelee() && HasRole(BOT_ROLE_DPS|BOT_ROLE_HEAL) && !IsMeleeClass(master->GetClass())) + cast = true; + else if (!GetSpell(WINDFURY_TOTEM_1)) //disabled + cast = true; + else if (!(mask & BOT_TOTEM_MASK_MY_TOTEM_AIR)) + if (mask & BOT_TOTEM_MASK_WINDFURY) //already have windfury from someone else + cast = true; + + if (cast) + { + if (doCast(me, GetSpell(WRATH_OF_AIR_TOTEM_1), CotE ? TRIGGERED_CAST_DIRECTLY : TRIGGERED_NONE)) + //if (!CotE) + return; + } + } + } + + if (!(mask & BOT_TOTEM_MASK_MY_TOTEM_AIR) && TotemTimer[T_AIR] <= diff && me->IsInCombat()) + { + //AIRmain2 : windfury + if (!(mask & BOT_TOTEM_MASK_WINDFURY) && GetSpell(WINDFURY_TOTEM_1)) + { + bool cast = false; + + if ((IsMelee() && HasRole(BOT_ROLE_DPS)) || (!IAmFree() && IsMeleeClass(master->GetClass()))) + cast = true; + else if (!GetSpell(WRATH_OF_AIR_TOTEM_1)) //disabled or not available yet + cast = true; + else if (!(mask & BOT_TOTEM_MASK_MY_TOTEM_AIR)) + if (mask & BOT_TOTEM_MASK_WRATH_OF_AIR) //already have wrath of air from someone else + cast = true; + + if (cast) + { + if (doCast(me, GetSpell(WINDFURY_TOTEM_1), CotE ? TRIGGERED_CAST_DIRECTLY : TRIGGERED_NONE)) + //if (!CotE) + return; + } + } + } + + if (!(mask & BOT_TOTEM_MASK_MY_TOTEM_AIR) && TotemTimer[T_AIR] <= diff && me->IsInCombat()) + { + //AIRlastresort : natureres (3-4 shamans of same level req) + uint32 natureRes = GetSpell(NATURE_RESISTANCE_TOTEM_1); + if (natureRes && !IAmFree() && + (!(mask & BOT_TOTEM_MASK_RESISTANCE_NATURE) || idMap[NATURE_RESISTANCE_TOTEM_1] < natureRes)) + { + if (doCast(me, natureRes, CotE ? TRIGGERED_CAST_DIRECTLY : TRIGGERED_NONE)) + //if (!CotE) + return; + } + } + } + + void CheckShamanisticRage(uint32 diff) + { + if (!IsSpellReady(SHAMANISTIC_RAGE_1, diff) || !me->IsInCombat() || IsCasting() || Rand() > 35) + return; + + bool cast = false; + //case 1: hp pressure + if (GetHealthPCT(me) < (50 + 20 * me->HasAuraType(SPELL_AURA_PERIODIC_DAMAGE) + 5 * uint32(me->getAttackers().size()))) + cast = true; + //case 2: low mana (melee) + else if (me->GetVictim() && !CCed(me, true) && HasRole(BOT_ROLE_DPS) && IsMelee() && GetManaPCT(me) < 40) + cast = true; + + if (cast && doCast(me, GetSpell(SHAMANISTIC_RAGE_1))) + return; + } + + void CheckThunderStorm(uint32 diff) + { + if (!IsSpellReady(THUNDERSTORM_1, diff) || !me->IsAlive() || !HasRole(BOT_ROLE_DPS) || IsCasting() || Rand() > 25) + return; + + //case 1: low mana + if (GetManaPCT(me) < 25) + { + if (doCast(me, GetSpell(THUNDERSTORM_1))) + return; + } + + //case 2: AoE damage + //AttackerSet m_attackers = master->getAttackers(); + Unit::AttackerSet const& b_attackers = me->getAttackers(); + if (b_attackers.empty()) + return; + + uint8 tCount = 0; + for (Unit::AttackerSet::const_iterator iter = b_attackers.begin(); iter != b_attackers.end(); ++iter) + { + if (!(*iter)) continue; + if (me->GetDistance((*iter)) > 9) continue; + if (me->IsValidAttackTarget(*iter)) + { + ++tCount; + break; + } + } + + if (tCount > 1) + { + if (doCast(me, GetSpell(THUNDERSTORM_1))) + return; + } + } + + void Counter(uint32 diff) + { + if (!IsSpellReady(WIND_SHEAR_1, diff, false) || (HasRole(BOT_ROLE_HEAL) && IsCasting()) || Rand() > 40) + return; + + if (Unit* target = FindCastingTarget(CalcSpellMaxRange(WIND_SHEAR_1), 0, WIND_SHEAR_1)) + { + me->InterruptNonMeleeSpells(false); + if (doCast(target, GetSpell(WIND_SHEAR_1))) + return; + } + } + + void CheckShield(uint32 diff) + { + if (GC_Timer > diff || ShieldCheckTimer > diff || IsCasting() || Rand() > 15) + return; + + ShieldCheckTimer = 2000; + + //Aura const* shield = nullptr; + uint32 SHIELD = + HasRole(BOT_ROLE_TANK) ? GetSpell(EARTH_SHIELD_1) : + HasRole(BOT_ROLE_HEAL) ? GetSpell(WATER_SHIELD_1) : + HasRole(BOT_ROLE_DPS) ? GetSpell(LIGHTNING_SHIELD_1) : + 0; + SHIELD = + SHIELD ? SHIELD : + GetSpell(WATER_SHIELD_1) ? GetSpell(WATER_SHIELD_1) : + GetSpell(EARTH_SHIELD_1) ? GetSpell(EARTH_SHIELD_1) : + 0; + + if (!SHIELD && HasRole(BOT_ROLE_DPS)) + SHIELD = GetSpell(LIGHTNING_SHIELD_1); + + if (!SHIELD) + return; + + AuraApplication const* sh = me->GetAuraApplicationOfRankedSpell(SHIELD); + if (!sh || sh->GetBase()->GetCharges() < 5 || sh->GetBase()->GetDuration() < 30000 || + sh->GetBase()->GetSpellInfo()->GetRank() < sSpellMgr->GetSpellInfo(SHIELD)->GetRank()) + { + if (doCast(me, SHIELD)) + return; + } + } + + void UpdateDeadAI(uint32 diff) override + { + if (IsSpellReady(REINCARNATION_1, diff, false) && (IAmFree() || master->IsInCombat()) && Rand() < 20) + if (doCast(me, GetSpell(REINCARNATION_1))) + return; + + bot_ai::UpdateDeadAI(diff); + } + + void UpdateAI(uint32 diff) override + { + if (!GlobalUpdate(diff)) + return; + + DoVehicleActions(diff); + if (!CanBotAttackOnVehicle()) + return; + + CheckShamanisticRage(diff); + CheckThunderStorm(diff); + + CheckHexy(diff); + CheckEarthy(diff); + + if (IsPotionReady()) + { + if (GetManaPCT(me) < 33) + DrinkPotion(true); + else if (GetHealthPCT(me) < 50 && (!HasRole(BOT_ROLE_HEAL) || me->HasAuraType(SPELL_AURA_MOD_SILENCE))) + DrinkPotion(false); + } + + CheckRacials(diff); + + CheckBloodlust(diff); + BuffAndHealGroup(diff); + CheckEarthShield(diff); + CureGroup(CURE_TOXINS, diff); + CheckTotems(diff); + CheckShield(diff); + + if (master->IsInCombat() || me->IsInCombat()) + { + CheckDispel(diff); + CheckFireNova(diff); + } + + if (!me->IsInCombat()) + DoNonCombatActions(diff); + + if (ProcessImmediateNonAttackTarget()) + return; + + CheckGhostWolf(diff); + + if (!CheckAttackTarget()) + return; + + CheckHex(diff); + Counter(diff); + + if (IsCasting()) + return; + + CheckUsableItems(diff); + + DoNormalAttack(diff); + } + + void DoNormalAttack(uint32 diff) + { + Unit* mytar = opponent ? opponent : disttarget ? disttarget : nullptr; + if (!mytar) + return; + + StartAttack(mytar, IsMelee()); + + CheckAttackState(); + if (!me->IsAlive() || !mytar->IsAlive()) + return; + + auto [can_do_frost, can_do_fire, can_do_nature] = CanAffectVictimBools(mytar, SPELL_SCHOOL_FROST, SPELL_SCHOOL_FIRE, SPELL_SCHOOL_NATURE); + + //AttackerSet m_attackers = master->getAttackers(); + //AttackerSet b_attackers = me->getAttackers(); + float dist = me->GetDistance(mytar); + + //spell reflections + if (IsSpellReady(EARTH_SHOCK_1, diff) && can_do_nature && HasRole(BOT_ROLE_DPS) && dist < 25 && CanRemoveReflectSpells(mytar, EARTH_SHOCK_1) && + doCast(mytar, EARTH_SHOCK_1)) + return; + + MoveBehind(mytar); + + //STORMSTRIKE + if (IsSpellReady(STORMSTRIKE_1, diff) && can_do_nature && HasRole(BOT_ROLE_DPS) && IsMelee() && dist <= 5 && Rand() < 120) + { + if (doCast(mytar, GetSpell(STORMSTRIKE_1))) + return; + } + //SHOCKS + if (GetSpellCooldown(EARTH_SHOCK_1) <= diff && HasRole(BOT_ROLE_DPS) && + (GetSpell(FLAME_SHOCK_1) || GetSpell(EARTH_SHOCK_1) || GetSpell(FROST_SHOCK_1)) && + dist < 25 && Rand() < 70) + { + if (GetSpell(FLAME_SHOCK_1) && can_do_fire) + { + AuraEffect const* fsh = mytar->GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_SHAMAN, 0x10000000, 0x0, 0x0, me->GetGUID()); + if (!fsh || fsh->GetBase()->GetDuration() < 3000) + { + if (doCast(mytar, GetSpell(FLAME_SHOCK_1))) + return; + } + } + + uint32 SHOCK = can_do_frost ? GetSpell(FROST_SHOCK_1) : 0; + if (!SHOCK && can_do_nature) + SHOCK = GetSpell(EARTH_SHOCK_1); + + if (SHOCK) + { + if (doCast(mytar, SHOCK)) + return; + } + } + + //Feral Spirit + if (IsSpellReady(FERAL_SPIRIT_1, diff) && HasRole(BOT_ROLE_DPS) && Rand() < 40 && dist < 5) + { + SummonBotPet(mytar); + SetSpellCooldown(FERAL_SPIRIT_1, 180000); + return; + } + + //LAVA BURST + if (IsSpellReady(LAVA_BURST_1, diff) && can_do_fire && HasRole(BOT_ROLE_DPS) && + (GetSpec() == BOT_SPEC_SHAMAN_ELEMENTAL || (IsRanged() && (!can_do_nature || !GetSpell(LIGHTNING_BOLT_1)))) && + dist < CalcSpellMaxRange(LAVA_BURST_1) && Rand() < 60 && + (me->getAttackers().empty() || dist > 10)) + { + if (doCast(mytar, GetSpell(LAVA_BURST_1))) + return; + } + + if (((MaelstromCount < 5 || MaelstromTimer == 0 || me->GetLevel() < 55) && IsMelee()) || + (HasRole(BOT_ROLE_HEAL) && GetManaPCT(me) < 25)) + return; + + //CHAIN LIGHTNING + if (IsSpellReady(CHAIN_LIGHTNING_1, diff) && can_do_nature && HasRole(BOT_ROLE_DPS) && dist < CalcSpellMaxRange(CHAIN_LIGHTNING_1) && Rand() < 80) + { + Unit* u = FindSplashTarget(35.f, mytar, 5.f); + if (u && doCast(mytar, GetSpell(CHAIN_LIGHTNING_1))) + return; + } + //LIGHTNING BOLT + if (IsSpellReady(LIGHTNING_BOLT_1, diff) && can_do_nature && HasRole(BOT_ROLE_DPS) && dist < CalcSpellMaxRange(LIGHTNING_BOLT_1)) + { + uint32 LIGHTNING_BOLT = GetSpell(LIGHTNING_BOLT_1); + if (doCast(mytar, LIGHTNING_BOLT)) + return; + } + } + + void CheckHexy(uint32 diff) + { + if (HexyCheckTimer > diff) + return; + + HexyCheckTimer = 2000; + Hexy = FindAffectedTarget(GetSpell(HEX_1), me->GetGUID()); + } + + void CheckHex(uint32 diff) + { + if (Hexy || !IsSpellReady(HEX_1, diff)) + return; + + if (Unit* target = FindPolyTarget(20)) + { + if (doCast(target, GetSpell(HEX_1))) + return; + } + } + + void CheckEarthy(uint32 diff) + { + if (EarthyCheckTimer > diff) + return; + + EarthyCheckTimer = 1000; + Unit const* u = FindAffectedTarget(GetSpell(EARTH_SHIELD_1), me->GetGUID(), 90.f, 3); + Earthy = (u && (IsTank(u) || u == master)); + } + + void CheckGhostWolf(uint32 diff) + { + if (!IsSpellReady(GHOST_WOLF_1, diff) || (!IAmFree() && !HasBotCommandState(BOT_COMMAND_FOLLOW)) || + Rand() > 35 || me->GetShapeshiftForm() != FORM_NONE || me->IsMounted() || !IsOutdoors() || IsCasting()) + return; + + if (IAmFree()) + { + InstanceTemplate const* instt = sObjectMgr->GetInstanceTemplate(me->GetMap()->GetId()); + bool map_allows_mount = (!me->GetMap()->IsDungeon() || me->GetMap()->IsBattlegroundOrArena()) && (!instt || instt->AllowMount); + if (me->HasUnitMovementFlag(MOVEMENTFLAG_FORWARD) && + (!me->GetVictim() ? + (me->IsInCombat() || !map_allows_mount || IsFlagCarrier(me)) : + !me->IsWithinDist(me->GetVictim(), 8.0f + (IsMelee() ? 5.0f : GetSpellAttackRange(true))))) + { + if (doCast(me, GetSpell(GHOST_WOLF_1))) + return; + } + + return; + } + + if (me->GetExactDist2d(master) > std::max(master->GetBotMgr()->GetBotFollowDist(), 30)) + { + if (doCast(me, GetSpell(GHOST_WOLF_1))) + return; + } + } + + void DoNonCombatActions(uint32 diff) + { + if (GC_Timer > diff || me->IsMounted() || IsCasting() || Rand() > 25) + return; + + ResurrectGroup(GetSpell(ANCESTRAL_SPIRIT_1)); + + if (mhEnchantExpireTimer > 0 && mhEnchantExpireTimer <= diff) + RemoveItemClassEnchantment(BOT_SLOT_MAINHAND); + if (ohEnchantExpireTimer > 0 && ohEnchantExpireTimer <= diff) + RemoveItemClassEnchantment(BOT_SLOT_OFFHAND); + + // Weapon Enchants + if (me->isMoving()) + return; + + Item* mhWeapon = GetEquips(BOT_SLOT_MAINHAND); + Item* ohWeapon = GetEquips(BOT_SLOT_OFFHAND); + //item must be non-standard, otherwise combat spells won't be rolled anyway + bool mhReady = mhWeapon && !mhWeapon->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT); + bool ohReady = ohWeapon && !ohWeapon->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT); + + if (!mhReady && !ohReady) + return; //no ecnhantable weapons + + //OK choose the enchants + //MH 1+ Rockbiter, 10+ Flametongue, 30+ Windfury/Earthliving + //OH 1+ Rockbiter, 10+ Flametongue, 20+ Frostbrand, 30+ Windfury/Earthliving + if (needChooseMHEnchant && mhReady) + mhEnchant = HasRole(BOT_ROLE_HEAL) ? (me->GetLevel() >= 30 ? EARTHLIVING_WEAPON_1 : + me->GetLevel() >= 10 ? FLAMETONGUE_WEAPON_1 : + 0/*ROCKBITER_WEAPON_1*/) : + HasRole(BOT_ROLE_RANGED) ? (me->GetLevel() >= 10 ? FLAMETONGUE_WEAPON_1 : + 0/*ROCKBITER_WEAPON_1*/) : + (me->GetLevel() >= 30 ? WINDFURY_WEAPON_1 : + //me->GetLevel() >= 20 ? FROSTBRAND_WEAPON_1 : + me->GetLevel() >= 10 ? FLAMETONGUE_WEAPON_1 : + 0/*ROCKBITER_WEAPON_1*/); + + if (needChooseOHEnchant && ohReady) //basically only lvl 40+ + ohEnchant = HasRole(BOT_ROLE_HEAL) ? (me->GetLevel() >= 30 ? EARTHLIVING_WEAPON_1 : + me->GetLevel() >= 10 ? FLAMETONGUE_WEAPON_1 : + 0/*ROCKBITER_WEAPON_1*/) : + HasRole(BOT_ROLE_RANGED) ? (me->GetLevel() >= 10 ? FLAMETONGUE_WEAPON_1 : + 0/*ROCKBITER_WEAPON_1*/) : + (me->GetLevel() >= 30 ? WINDFURY_WEAPON_1 : + me->GetLevel() >= 20 ? FROSTBRAND_WEAPON_1 : + me->GetLevel() >= 10 ? FLAMETONGUE_WEAPON_1 : + 0/*ROCKBITER_WEAPON_1*/); + + uint32 MhEnchant = !mhReady ? 0 : GetSpell(mhEnchant); + uint32 OhEnchant = !ohReady ? 0 : GetSpell(ohEnchant); + + SpellInfo const* MhEnchantInfo = mhReady && MhEnchant ? sSpellMgr->GetSpellInfo(MhEnchant) : nullptr; + SpellInfo const* OhEnchantInfo = ohReady && OhEnchant ? sSpellMgr->GetSpellInfo(OhEnchant) : nullptr; + + Item* targetWeapon = nullptr; + SpellInfo const* targetInfo = nullptr; + + if (mhReady && MhEnchant && mhWeapon->IsFitToSpellRequirements(MhEnchantInfo)) + { + targetWeapon = mhWeapon; + targetInfo = MhEnchantInfo; + } + if (!targetWeapon && ohReady && OhEnchant && ohWeapon->IsFitToSpellRequirements(OhEnchantInfo)) + { + targetWeapon = ohWeapon; + targetInfo = OhEnchantInfo; + } + if (targetWeapon) + { + Spell* spell = new Spell(me, targetInfo, TRIGGERED_NONE); + SpellCastTargets targets; + targets.SetItemTarget(targetWeapon); + spell->prepare(targets); + return; + } + } + + bool BuffTarget(Unit* target, uint32 /*diff*/) override + { + if (me->IsInCombat() && !master->GetMap()->IsRaid()) + return false; + + if (target->HasUnitMovementFlag(MOVEMENTFLAG_SWIMMING)) + { + //water walking breaks on any damage + if (GetSpell(WATER_WALKING_1) && target->getAttackers().empty() && + !target->HasAuraType(SPELL_AURA_PERIODIC_DAMAGE) && !target->HasAuraType(SPELL_AURA_WATER_WALK) && + doCast(target, GetSpell(WATER_WALKING_1))) + { + //GC_Timer = 1000; + return true; + } + //bots don't need water breathing + if (GetSpell(WATER_BREATHING_1) && target->GetTypeId() == TYPEID_PLAYER && + !target->HasAuraType(SPELL_AURA_WATER_BREATHING) && + doCast(target, GetSpell(WATER_BREATHING_1))) + { + //GC_Timer = 1000; + return true; + } + } + return false; + } + + void CheckEarthShield(uint32 diff) + { + if (!IsSpellReady(EARTH_SHIELD_1, diff) || Earthy == true || Rand() > (65 - 45 * me->IsInCombat())) + return; + + static const auto can_affect = [](WorldObject const* o, Unit const* unit) + { + if (!unit->IsAlive()) + return false; + AuraEffect const* eShield = unit->GetAuraEffect(SPELL_AURA_REDUCE_PUSHBACK, SPELLFAMILY_SHAMAN, 0x0, 0x400, 0x0); + return (!eShield || eShield->GetBase()->GetCharges() < 5 || eShield->GetBase()->GetDuration() < 30000) && o->GetDistance(unit) < 40 && (unit->IsInCombat() || !unit->isMoving()); + }; + + Group const* gr = !IAmFree() ? master->GetGroup() : GetGroup(); + if (!gr) + { + if (IsTank(master) && can_affect(me, master) && doCast(master, GetSpell(EARTH_SHIELD_1))) + return; + + if (!IAmFree()) + { + BotMap const* map = master->GetBotMgr()->GetBotMap(); + for (BotMap::const_iterator it = map->begin(); it != map->end(); ++it) + { + Unit* u = it->second; + if (!u || !u->IsInWorld() || me->GetMap() != u->FindMap() || !u->InSamePhase(me)) + continue; + if (IsTank(u) && can_affect(me, u) && doCast(u, GetSpell(EARTH_SHIELD_1))) + return; + } + } + } + else + { + std::set tanks; + for (Unit* member : BotMgr::GetAllGroupMembers(gr)) + { + if (me->GetMap() == member->FindMap() && member->IsAlive() && member->InSamePhase(me) && IsTank(member) && can_affect(me, member)) + tanks.insert(member); + } + + if (!tanks.empty()) + { + Unit* target = tanks.size() == 1 ? *tanks.begin() : Trinity::Containers::SelectRandomContainerElement(tanks); + if (doCast(target, GetSpell(EARTH_SHIELD_1))) + return; + } + } + + if (!IAmFree() && can_affect(me, master) && doCast(master, GetSpell(EARTH_SHIELD_1))) + return; + } + + void CheckDispel(uint32 diff) + { + if (!IsSpellReady(PURGE_1, diff) || IsCasting()) + return; + + Unit* target = FindHostileDispelTarget(CalcSpellMaxRange(PURGE_1)); + if (target && doCast(target, GetSpell(PURGE_1))) + return; + + SetSpellCooldown(PURGE_1, 500); //fail + } + + void CheckFireNova(uint32 diff) + { + if (!HasRole(BOT_ROLE_DPS) || _totems[T_FIRE].second._type == BOT_TOTEM_NONE || + !IsSpellReady(FIRE_NOVA_1, diff) || IsCasting() || Rand() > 25) + return; + + std::list targets; + GetNearbyTargetsList(targets, 9.f, 0, ObjectAccessor::GetUnit(*me, _totems[T_FIRE].first)); + if (targets.size() > 1 || (!targets.empty() && *(targets.begin()) == me->GetVictim())) + { + if (doCast(me, GetSpell(FIRE_NOVA_1))) + return; + } + } + + bool HealTarget(Unit* target, uint32 diff) override + { + if (!target || !target->IsAlive() || target->GetShapeshiftForm() == FORM_SPIRITOFREDEMPTION || me->GetDistance(target) > 40) + return false; + uint8 hp = GetHealthPCT(target); + if (hp > GetHealHpPctThreshold()) + return false; + bool pointed = IsPointedHealTarget(target); + if (hp > 90 && !(pointed && me->GetMap()->IsRaid()) && + (!target->IsInCombat() || target->getAttackers().empty() || !IsTank(target) || !me->GetMap()->IsRaid())) + return false; + + int32 hps = GetHPS(target); + int32 xphp = target->GetHealth() + hps * 2.5f; + int32 hppctps = int32(hps * 100.f / float(target->GetMaxHealth())); + int32 xphploss = xphp > int32(target->GetMaxHealth()) ? 0 : abs(int32(xphp - target->GetMaxHealth())); + int32 xppct = hp + hppctps * 2.5f; + if (xppct >= 95 && hp >= 25 && !pointed) + return false; + + if (IsSpellReady(NATURES_SWIFTNESS_1, diff, false) && Rand() < 80 && + (me->IsInCombat() || target->IsInCombat()) &&//may just revive + hp <= 20 && xppct <= 0 && xphploss > _heals[HEALING_WAVE_1] / 2 && + (target->GetTypeId() == TYPEID_PLAYER || IsTank(target) || target->IsInCombat() || !target->getAttackers().empty())) + { + me->InterruptNonMeleeSpells(false); + if (doCast(me, GetSpell(NATURES_SWIFTNESS_1))) + { + if (doCast(target, GetSpell(HEALING_WAVE_1))) + return true; + } + } + + if (IsCasting()) return false; + + Unit const* u = target->GetVictim(); + bool tanking = u && IsTank(target) && u->ToCreature() && u->ToCreature()->isWorldBoss(); + + if (IsSpellReady(HEALING_WAVE_1, diff) && + (xppct >= 15 || !GetSpell(LESSER_HEALING_WAVE_1)) && xphploss > _heals[HEALING_WAVE_1]) + { + if (doCast(target, GetSpell(HEALING_WAVE_1))) + return true; + } + //Riptide stacks from different casters + if (IsSpellReady(RIPTIDE_1, diff) && hp <= 85 && (tanking || hps < 0 || xphploss > _heals[RIPTIDE_1]) && + !target->GetAuraEffect(SPELL_AURA_PERIODIC_HEAL, SPELLFAMILY_SHAMAN, 0x0, 0x0, 0x10, me->GetGUID()) + /*!target->HasAura(GetSpell(RIPTIDE_1), me->GetGUID())*/) + { + if (doCast(target, GetSpell(RIPTIDE_1))) + return true; + } + if (IsSpellReady(CHAIN_HEAL_1, diff) && !IAmFree() && xppct > 35 && xphploss > _heals[CHAIN_HEAL_1] && + (!tanking || Rand() < 60 || target->GetAuraEffect(SPELL_AURA_PERIODIC_HEAL, SPELLFAMILY_SHAMAN, 0x0, 0x0, 0x10, me->GetGUID()))) + { + if (doCast(target, GetSpell(CHAIN_HEAL_1))) + return true; + } + + if (IsSpellReady(LESSER_HEALING_WAVE_1, diff) && xphploss > _heals[LESSER_HEALING_WAVE_1]) + { + if (doCast(target, GetSpell(LESSER_HEALING_WAVE_1))) + return true; + } + + return false; + } + + void ApplyClassSpellCritMultiplierAll(Unit const* /*victim*/, float& crit_chance, SpellInfo const* spellInfo, SpellSchoolMask schoolMask, WeaponAttackType /*attackType*/) const override + { + //if (spellInfo->DmgClass != SPELL_DAMAGE_CLASS_MAGIC) + // return; + + uint32 spellId = spellInfo->Id; + uint8 lvl = me->GetLevel(); + + //Call of Thunder: 5% additional critical chance for Lightning Bolt, Chain Lightning and Thunderstorm + if ((GetSpec() == BOT_SPEC_SHAMAN_ELEMENTAL) && lvl >= 30 && + (spellId == GetSpell(LIGHTNING_BOLT_1) || + spellId == GetSpell(CHAIN_LIGHTNING_1) || + spellId == GetSpell(THUNDERSTORM_1))) + crit_chance += 5.f; + //Tidal Mastery: 5% additional critical chance for lightning spells + if (lvl >= 25 && (SPELL_SCHOOL_MASK_NATURE & schoolMask)) + crit_chance += 5.f; + //Blessing of the Eternals: 4% additional critical chance for all spells + if ((GetSpec() == BOT_SPEC_SHAMAN_RESTORATION) && lvl >= 45) + crit_chance += 4.f; + //Tidal Waves (Lesser Healing Wave crit) + if (spellInfo->SpellFamilyFlags[0] & 0x80) + if (AuraEffect const* eff = me->GetAuraEffect(TIDAL_WAVES_BUFF, 1, me->GetGUID())) + if (eff->IsAffectingSpell(spellInfo)) + crit_chance += 25.f; + //Tidal Force + if (spellInfo->SpellFamilyFlags[0] & 0x1C0) + if (AuraEffect const* eff = me->GetAuraEffect(TIDAL_FORCE_BUFF, 0, me->GetGUID())) + if (eff->IsAffectingSpell(spellInfo)) + crit_chance += 20.f * eff->GetBase()->GetStackAmount(); + } + + void ApplyClassDamageMultiplierMeleeSpell(int32& damage, SpellNonMeleeDamage& /*damageinfo*/, SpellInfo const* spellInfo, WeaponAttackType /*attackType*/, bool iscrit) const override + { + uint32 spellId = spellInfo->Id; + uint8 lvl = me->GetLevel(); + float fdamage = float(damage); + + //apply bonus damage mods + float pctbonus = 0.0f; + if (iscrit) + { + //!!!Melee spell damage is not yet critical, all reduced by half + //Elemental Fury (part 2): 50% additional crit damage bonus for Nature, Fire and Frost (all) spells + if (lvl >= 21) + pctbonus += 0.25f; + } + + //SHAMAN_T8_ENCHANCEMENT_2P_BONUS: 20% bonus damage for Lava Lash and Stormstrike + if (lvl >= 60 && + (spellId == STORMSTRIKE_DAMAGE || spellId == STORMSTRIKE_DAMAGE_OFFHAND/* || spellId == LAVA_LASH*/)) + pctbonus += 0.2f; + + //custom bonus to make stormstrike useful + if (spellId == STORMSTRIKE_DAMAGE || spellId == STORMSTRIKE_DAMAGE_OFFHAND) + pctbonus += 1.0f; + + damage = int32(fdamage * (1.0f + pctbonus)); + } + + void ApplyClassDamageMultiplierSpell(int32& damage, SpellNonMeleeDamage& /*damageinfo*/, SpellInfo const* spellInfo, WeaponAttackType /*attackType*/, bool iscrit) const override + { + uint32 spellId = spellInfo->Id; + uint8 lvl = me->GetLevel(); + float fdamage = float(damage); + float flat_mod = 0.f; + + //2) apply bonus damage mods + float pctbonus = 0.0f; + if (iscrit) + { + //!!!spell damage is not yet critical and will be multiplied by 1.5 + //so we should put here bonus damage mult /1.5 + //Elemental Fury (part 2): 50% additional crit damage bonus for Nature, Fire and Frost spells + if (lvl >= 21 && + (spellInfo->GetSchoolMask() & (SPELL_SCHOOL_MASK_NATURE|SPELL_SCHOOL_MASK_FIRE|SPELL_SCHOOL_MASK_FROST))) + pctbonus += 0.333f; + //Lava Flows (part 1): 24% additional crit damage bonus for Lava Burst + if ((GetSpec() == BOT_SPEC_SHAMAN_ELEMENTAL) && lvl >= 50 && spellId == GetSpell(LAVA_BURST_1)) + pctbonus += 0.16f; + } + //Concussion: 5% bonus damage for Lightning Bolt, Chain Lightning, Thunderstorm, Lava Burst and Shocks + if (lvl >= 10 && + (spellId == GetSpell(LIGHTNING_BOLT_1) || + spellId == GetSpell(CHAIN_LIGHTNING_1) || + spellId == GetSpell(THUNDERSTORM_1) || + spellId == GetSpell(LAVA_BURST_1) || + spellId == GetSpell(EARTH_SHOCK_1) || + spellId == GetSpell(FROST_SHOCK_1) || + spellId == GetSpell(FLAME_SHOCK_1))) + pctbonus += 0.05f; + //Call of Flame (part 2): 6% bonus damage for Lava burst + if (lvl >= 15 && spellId == GetSpell(LAVA_BURST_1)) + pctbonus += 0.06f; + //Storm, Earth and fire (part 3): 60% bonus damage for Flame Shock (periodic damage in fact but who cares?) + if ((GetSpec() == BOT_SPEC_SHAMAN_ELEMENTAL) && lvl >= 40 && spellId == GetSpell(FLAME_SHOCK_1)) + pctbonus += 0.6f; + //Booming Echoes (part 2): 20% bonus damage for Flame Shock and Frost Shock (direct damage) + if ((GetSpec() == BOT_SPEC_SHAMAN_ELEMENTAL) && lvl >= 45 && + (spellId == GetSpell(FLAME_SHOCK_1) || + spellId == GetSpell(FROST_SHOCK_1))) + pctbonus += 0.2f; + //Improved Shields (part 1): 15% bonus damage for Lightning Shield orbs + if (lvl >= 15 && spellInfo->IsRankOf(sSpellMgr->GetSpellInfo(LIGHTNING_SHIELD_DAMAGE_1))) + pctbonus += 0.15f; + //Shamanism: +20/25% bonus from spp + if ((GetSpec() == BOT_SPEC_SHAMAN_ELEMENTAL) && lvl >= 45) + { + if (spellId == GetSpell(CHAIN_LIGHTNING_1) || spellId == GetSpell(LIGHTNING_BOLT_1)) + flat_mod += me->SpellBaseDamageBonusDone(SPELL_SCHOOL_MASK_MAGIC) * 0.2f * me->CalculateDefaultCoefficient(spellInfo, SPELL_DIRECT_DAMAGE) * me->CalculateSpellpowerCoefficientLevelPenalty(spellInfo); + else if (spellId == GetSpell(LAVA_BURST_1)) + flat_mod += me->SpellBaseDamageBonusDone(SPELL_SCHOOL_MASK_MAGIC) * 0.25f * me->CalculateDefaultCoefficient(spellInfo, SPELL_DIRECT_DAMAGE) * me->CalculateSpellpowerCoefficientLevelPenalty(spellInfo); + } + //Elemental Oath (part 1): 10% bonus damage + if ((GetSpec() == BOT_SPEC_SHAMAN_ELEMENTAL) && + lvl >= 45 && me->GetAuraEffect(ELEMENTAL_FOCUS_BUFF, 0, me->GetGUID())) + pctbonus += 0.1f; + //Elemental Weapons (part 1): 40% bonus damage + if (lvl >= 20 && (spellId == WINDFURY_ATTACK_MAINHAND || spellId == WINDFURY_ATTACK_OFFHAND)) + pctbonus += 0.4f; + + //Totemic Recall: bots have reduced base mana so increase mana gain here + //if (lvl >= 30 && spellId == GetSpell(TOTEMIC_RECALL_1)) + // pctbonus += 0.5f; + + damage = int32(fdamage * (1.0f + pctbonus) + flat_mod); + } + + void ApplyClassDamageMultiplierHeal(Unit const* /*victim*/, float& heal, SpellInfo const* spellInfo, DamageEffectType damagetype, uint32 stack) const override + { + uint32 spellId = spellInfo->Id; + uint8 lvl = me->GetLevel(); + float pctbonus = 0.0f; + float flat_mod = 0.0f; + + //Healing Way: 25% bonus healing for Healing Wave + if ((GetSpec() == BOT_SPEC_SHAMAN_RESTORATION) && lvl >= 30 && spellId == GetSpell(HEALING_WAVE_1)) + pctbonus += 0.25f; + //Purification: 10% bonus healing for all spells + if ((GetSpec() == BOT_SPEC_SHAMAN_RESTORATION) && lvl >= 35) + pctbonus += 0.1f; + //Nature's Blessing: 15% of Intellect to healing + if ((GetSpec() == BOT_SPEC_SHAMAN_RESTORATION) && lvl >= 45) + flat_mod += GetTotalBotStat(BOT_STAT_MOD_INTELLECT) * 0.15f * me->CalculateDefaultCoefficient(spellInfo, damagetype) * stack * 1.88f * me->CalculateSpellpowerCoefficientLevelPenalty(spellInfo) * stack; + //Improved Chain Heal: 20% bonus healing for Chain Heal + if ((GetSpec() == BOT_SPEC_SHAMAN_RESTORATION) && lvl >= 45 && spellId == GetSpell(CHAIN_HEAL_1)) + pctbonus += 0.2f; + //Improved Earth Shield: 10% bonus healing for Earth Shield + //Glyph of Earth Shield: 20% bonus healing for Earth Shield + if (lvl >= 50 && spellId == EARTH_SHIELD_HEAL) + pctbonus += (GetSpec() == BOT_SPEC_SHAMAN_RESTORATION) ? 0.3f : 0.2f; + //Improved Shields (part 3): 15% bonus healing for Earth Shield + if (lvl >= 15 && spellId == EARTH_SHIELD_HEAL) + pctbonus += 0.15f; + //Tidal Waves (part 2): 20% bonus (from spellpower) for Healing Wave and 10% bonus (from spellpower) for Lesser Healing Wave + if ((GetSpec() == BOT_SPEC_SHAMAN_RESTORATION) && lvl >= 55) + { + if (spellId == GetSpell(HEALING_WAVE_1)) + flat_mod += me->SpellBaseHealingBonusDone(SPELL_SCHOOL_MASK_MAGIC) * 0.2f * me->CalculateDefaultCoefficient(spellInfo, damagetype) * 1.88f * me->CalculateSpellpowerCoefficientLevelPenalty(spellInfo) * stack; + else if (spellId == GetSpell(LESSER_HEALING_WAVE_1)) + flat_mod += me->SpellBaseHealingBonusDone(SPELL_SCHOOL_MASK_MAGIC) * 0.1f * me->CalculateDefaultCoefficient(spellInfo, damagetype) * 1.88f * me->CalculateSpellpowerCoefficientLevelPenalty(spellInfo) * stack; + } + + heal = heal * (1.0f + pctbonus) + flat_mod; + } + + void ApplyClassSpellCostMods(SpellInfo const* spellInfo, int32& cost) const override + { + //uint32 spellId = spellInfo->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + uint8 lvl = me->GetLevel(); + float fcost = float(cost); + int32 flatbonus = 0; + float pctbonus = 0.0f; + + //percent mods + //Clearcasting: -40% mana cost + if (AuraEffect const* eff = me->GetAuraEffect(ELEMENTAL_FOCUS_BUFF, 0, me->GetGUID())) + if (eff->IsAffectingSpell(spellInfo)) + pctbonus += 0.4f; + //Convection + if (lvl >= 10 && ((spellInfo->SpellFamilyFlags[0] & 0x90100003) || (spellInfo->SpellFamilyFlags[1] & 0x8001000))) + pctbonus += 0.1f; + //Shamanistic Focus + if (lvl >= 20 && (spellInfo->SpellFamilyFlags[0] & 0x90100000)) + pctbonus += 0.45f; + //Mental Quickness: + if ((GetSpec() == BOT_SPEC_SHAMAN_ENHANCEMENT) && lvl >= 50 && !spellInfo->CalcCastTime()) + pctbonus += 0.06f; + //Totemic Focus: + if (lvl >= 10 && (spellInfo->AttributesEx7 & SPELL_ATTR7_SUMMON_PLAYER_TOTEM)) + pctbonus += 0.25f; + //Tidal Focus: + if (lvl >= 15 && + ((spellInfo->SpellFamilyFlags[0] & 0x1C0) || + (spellInfo->SpellFamilyFlags[1] & 0x400) || + (spellInfo->SpellFamilyFlags[2] & 0x10))) + pctbonus += 0.05f; + + //flat mods + //!1 rage = 10 pts! + ////Improved Heroic Strike: -3 rage cost for Heroic Strike + //if (lvl >= 10 && spellId == GetSpell(HEROIC_STRIKE_1)) + // flatbonus += 30; + + //cost can be < 0 + cost = int32(fcost * (1.0f - pctbonus)) - flatbonus; + } + + void ApplyClassSpellCastTimeMods(SpellInfo const* spellInfo, int32& casttime) const override + { + //casttime is in milliseconds + uint32 spellId = spellInfo->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + uint8 lvl = me->GetLevel(); + int32 timebonus = 0; + float pctbonus = 0.0f; + + //100% mods + //Nature's Swiftness: -100% cast time + if (AuraEffect const* eff = me->GetAuraEffect(NATURES_SWIFTNESS_1, 0, me->GetGUID())) + if (eff->IsAffectingSpell(spellInfo)) + pctbonus += 1.0f; + + //pct mods + if (spellId == GetSpell(LIGHTNING_BOLT_1) || spellId == GetSpell(CHAIN_LIGHTNING_1) || + spellId == GetSpell(HEALING_WAVE_1) || spellId == GetSpell(LESSER_HEALING_WAVE_1) || + spellId == GetSpell(CHAIN_HEAL_1) || spellId == GetSpell(HEX_1)) + { + Aura* maelstrom = me->GetAura(MAELSTROM_WEAPON_BUFF); + if (maelstrom) + { + pctbonus += 0.2f * maelstrom->GetStackAmount(); + maelUseUp = true; + } + } + //Tidal Waves (Healing Wave cast time) + if (spellInfo->SpellFamilyFlags[0] & 0x40) + if (AuraEffect const* eff = me->GetAuraEffect(TIDAL_WAVES_BUFF, 0, me->GetGUID())) + if (eff->IsAffectingSpell(spellInfo)) + pctbonus += 0.3f; + + //flat mods + //Improved Ghost Wolf: -2 sec + if (lvl >= 10 && spellId == GetSpell(GHOST_WOLF_1)) + timebonus += 2000; + //Improved Healing Wave: -0.5 sec + if (lvl >= 10 && spellId == GetSpell(HEALING_WAVE_1)) + timebonus += 500; + //Lightning Mastery: -0.5 sec + if ((GetSpec() == BOT_SPEC_SHAMAN_ELEMENTAL) && + lvl >= 35 && ((spellInfo->SpellFamilyFlags[0] & 0x3) || (spellInfo->SpellFamilyFlags[1] & 0x1000))) + timebonus += 500; + //Stormcaller Chain Heal Bonus (26122): -0.4 sec + if (lvl >= 60 && spellId == GetSpell(CHAIN_HEAL_1)) + timebonus += 400; + + casttime = std::max((float(casttime) * (1.0f - pctbonus)) - timebonus, 0); + } + + void ApplyClassSpellCooldownMods(SpellInfo const* spellInfo, uint32& cooldown) const override + { + //cooldown is in milliseconds + //uint32 spellId = spellInfo->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + uint8 lvl = me->GetLevel(); + int32 timebonus = 0; + float pctbonus = 0.0f; + + //pct mods + //Glyph of Rapid Charge: -7% cooldown for Charge + //if (lvl >= 40 && spellId == GetSpell(CHARGE_1)) + // pctbonus += 0.07f; + + //flat mods + //Reverberation + if (lvl >= 20 && ((spellInfo->SpellFamilyFlags[0] & 0x90100000) || (spellInfo->SpellFamilyFlags[1] & 0x8000000))) + timebonus += 1000; + //Booming Echoes (part 1) + if ((GetSpec() == BOT_SPEC_SHAMAN_ELEMENTAL) && lvl >= 45 && (spellInfo->SpellFamilyFlags[0] & 0x90000000)) + timebonus += 2000; + //Storm, Earth and Fire (part 1) + if ((GetSpec() == BOT_SPEC_SHAMAN_ELEMENTAL) && lvl >= 40 && (spellInfo->SpellFamilyFlags[0] & 0x2)) + timebonus += 2500; + //Improved Fire Nova (part 2) + if ((GetSpec() == BOT_SPEC_SHAMAN_ELEMENTAL) && lvl >= 25 && (spellInfo->SpellFamilyFlags[0] & 0x8000000)) + timebonus += 4000; + + cooldown = std::max((float(cooldown) * (1.0f - pctbonus)) - timebonus, 0); + } + + void ApplyClassSpellCategoryCooldownMods(SpellInfo const* spellInfo, uint32& cooldown) const override + { + //cooldown is in milliseconds + uint32 spellId = spellInfo->Id; + //uint8 lvl = me->GetLevel(); + int32 timebonus = 0; + float pctbonus = 0.0f; + + //Improved Reincarnation + Reduced Reincarnation Cooldown: -20 min cooldown for Reincarnation + if (spellId == GetSpell(REINCARNATION_1)) + timebonus += 1200000; + + cooldown = std::max((float(cooldown) * (1.0f - pctbonus)) - timebonus, 0); + } + + void ApplyClassSpellGlobalCooldownMods(SpellInfo const* spellInfo, float& cooldown) const override + { + //cooldown is in milliseconds + //uint32 spellId = spellInfo->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + uint8 lvl = me->GetLevel(); + float timebonus = 0.0f; + float pctbonus = 0.0f; + + //Glyph of Shocking: -0.5 sec global cooldown for Shocks + if (lvl >= 15 && ((spellInfo->SpellFamilyFlags[0] & 0x90100000) || (spellInfo->SpellFamilyFlags[1] & 0x8000000))) + timebonus += 500.f; + + cooldown = (cooldown * (1.0f - pctbonus)) - timebonus; + } + + void ApplyClassSpellRangeMods(SpellInfo const* spellInfo, float& maxrange) const override + { + //uint32 spellId = spellInfo->Id; + //uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + uint8 lvl = me->GetLevel(); + float flatbonus = 0.0f; + float pctbonus = 0.0f; + + //pct mods + //Grim Reach: +20% range for Affliction Spells + //if (lvl >= 25 && ((spellInfo->SpellFamilyFlags[0] & 0x8048C41A) || (spellInfo->SpellFamilyFlags[1] & 0x40713))) + // pctbonus += 0.2f; + + //flat mods + //Elemental Reach part 1: +6 yd + if ((GetSpec() == BOT_SPEC_SHAMAN_ELEMENTAL) && + lvl >= 30 && ((spellInfo->SpellFamilyFlags[0] & 0x8000003) || (spellInfo->SpellFamilyFlags[1] & 0x1000))) + flatbonus += 6.f; + //Elemental Reach part 2: +15 yd + if ((GetSpec() == BOT_SPEC_SHAMAN_ELEMENTAL) && + lvl >= 30 && (spellInfo->SpellFamilyFlags[0] & 0x10000000)) + flatbonus += 15.f; + + maxrange = maxrange * (1.0f + pctbonus) + flatbonus; + } + + void ApplyClassSpellMaxTargetsMods(SpellInfo const* spellInfo, uint32& targets) const override + { + uint32 bonusTargets = 0; + + //Glyph of Chain Heal + if (spellInfo->SpellFamilyFlags[0] & 0x100) + bonusTargets += 1; + //Glyph of Chain Lightning + if (spellInfo->SpellFamilyFlags[0] & 0x2) + bonusTargets += 1; + //Chain Healing Wave (23573) + if (me->GetLevel() >= 60 && spellInfo->SpellFamilyFlags[0] & 0x100) + bonusTargets += 2; + + targets = targets + bonusTargets; + } + + void OnClassSpellGo(SpellInfo const* spellInfo) override + { + //uint32 spellId = spellInfo->Id; + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + + //reincarnation: notify master + if (baseId == REINCARNATION_1 && !IAmFree()) + { + ReportSpellCast(baseId, LocalizedNpcText(master, BOT_TEXT__USED), master); + + //no spellHit trigger - do it here + SpellHit(me, spellInfo); + me->CastSpell(me, RESURRECTION_VISUAL_SPELL, true); + } + //manatide: notify + if (baseId == MANA_TIDE_TOTEM_1 && !IAmFree()) + { + ReportSpellCast(baseId, LocalizedNpcText(master, BOT_TEXT__USED), master); + } + //Nature's Swiftness: notify master + if (baseId == NATURES_SWIFTNESS_1 && !IAmFree()) + { + ReportSpellCast(baseId, LocalizedNpcText(master, BOT_TEXT__USED), master); + } + + //Handle Clearcasting + if (AuraEffect* eff = me->GetAuraEffect(ELEMENTAL_FOCUS_BUFF, 0, me->GetGUID())) + if (eff->IsAffectingSpell(spellInfo)) + eff->GetBase()->DropCharge(); + //Handle Tidal Focus + //Healing Wave (cast time): if full Maelstrom than don't use up charge + if (MaelstromCount < 5 && (spellInfo->SpellFamilyFlags[0] & 0x40)) + if (AuraEffect* eff = me->GetAuraEffect(TIDAL_WAVES_BUFF, 0, me->GetGUID())) + if (eff->IsAffectingSpell(spellInfo)) + eff->GetBase()->DropCharge(); + //Lesser Healing Wave (crit) + if (spellInfo->SpellFamilyFlags[0] & 0x80) + if (AuraEffect* eff = me->GetAuraEffect(TIDAL_WAVES_BUFF, 1, me->GetGUID())) + if (eff->IsAffectingSpell(spellInfo)) + eff->GetBase()->DropCharge(); + + //Nature's Swiftness + if (AuraEffect const* eff = me->GetAuraEffect(NATURES_SWIFTNESS_1, 0, me->GetGUID())) + { + if (eff->IsAffectingSpell(spellInfo)) + me->RemoveAurasDueToSpell(NATURES_SWIFTNESS_1); + } + + //Tidal Force: Handled in Unit::HandleDummyAuraProc(): case 55166: + //if (spellInfo->SpellFamilyFlags[0] & 0x1C0) + // if (AuraEffect const* eff = me->GetAuraEffect(TIDAL_FORCE_BUFF, 0, me->GetGUID())) + // if (eff->IsAffectingSpell(spellInfo)) + // me->RemoveAuraFromStack(TIDAL_FORCE_BUFF); + + //Shield cd + if (baseId == LIGHTNING_SHIELD_DAMAGE_1) + SetSpellCooldown(LIGHTNING_SHIELD_DAMAGE_1, 3000); //is that right? from spell_proc_event + + //autouse totems + if (baseId == EARTHBIND_TOTEM_1 || baseId == STRENGTH_OF_EARTH_TOTEM_1) + TotemTimer[T_EARTH] = 5000; + if (baseId == TREMOR_TOTEM_1) + TotemTimer[T_EARTH] = 12000; + if (baseId == MAGMA_TOTEM_1) + TotemTimer[T_FIRE] = 12000; + if (baseId == TOTEM_OF_WRATH_1 || baseId == FLAMETONGUE_TOTEM_1) + TotemTimer[T_FIRE] = 5000; + if (baseId == FROST_RESISTANCE_TOTEM_1) + TotemTimer[T_FIRE] = 120000; + if (baseId == MANA_TIDE_TOTEM_1) + TotemTimer[T_WATER] = 12000; + if (baseId == MANA_SPRING_TOTEM_1) + TotemTimer[T_WATER] = 5000; + if (baseId == FIRE_RESISTANCE_TOTEM_1) + TotemTimer[T_WATER] = 120000; + if (baseId == GROUNDING_TOTEM_1) + TotemTimer[T_AIR] = me->GetLevel() >= 15 ? 13000 : 15000; + if (baseId == WRATH_OF_AIR_TOTEM_1 || baseId == WINDFURY_TOTEM_1) + TotemTimer[T_AIR] = 5000; + if (baseId == NATURE_RESISTANCE_TOTEM_1) + TotemTimer[T_AIR] = 120000; + //other (manual use) + if (baseId == STONECLAW_TOTEM_1) + TotemTimer[T_EARTH] = 15000; + if (baseId == STONESKIN_TOTEM_1) + TotemTimer[T_EARTH] = 300000; + if (baseId == EARTH_ELEMENTAL_TOTEM_1) + TotemTimer[T_EARTH] = 120000; + if (baseId == SEARING_TOTEM_1) + TotemTimer[T_FIRE] = 60000; + if (baseId == FIRE_ELEMENTAL_TOTEM_1) + TotemTimer[T_FIRE] = 120000; + if (baseId == CLEANSING_TOTEM_1) + TotemTimer[T_WATER] = 300000; + if (baseId == HEALING_STREAM_TOTEM_1) + TotemTimer[T_WATER] = 300000; + + //Totemic Recall totems resummon helper + if (baseId == TOTEMIC_RECALL_1) + { + TotemsCheckTimer = GC_Timer; + for (uint8 i = 0; i != MAX_TOTEMS; ++i) + TotemTimer[i] = 0; + } + + if (maelUseUp) + { + if (baseId == LIGHTNING_BOLT_1 || baseId == CHAIN_LIGHTNING_1 || baseId == HEALING_WAVE_1 || + baseId == LESSER_HEALING_WAVE_1 || baseId == CHAIN_HEAL_1 || baseId == HEX_1) + { + MaelstromCount = 0; + me->RemoveAurasDueToSpell(MAELSTROM_WEAPON_BUFF); + } + } + + //Item enchant + //We don't know which item is targeted + //Actually it is mh, then oh + if (/*baseId == ROCKBITER_WEAPON_1 || */baseId == FLAMETONGUE_WEAPON_1 || baseId == FROSTBRAND_WEAPON_1 || + baseId == WINDFURY_WEAPON_1 || baseId == EARTHLIVING_WEAPON_1) + { + //We set duration to 2 seconds to prevent exploiting unequip mechanic + //to get enchanted weapons for player (for non-shaman bots it won't work) + uint32 slot = TEMP_ENCHANTMENT_SLOT; + uint32 duration = 2 * IN_MILLISECONDS; + uint32 charges = 0; + uint32 enchant_id = spellInfo->_effects[0].MiscValue; + //SpellItemEnchantmentEntry const* pEnchant = sSpellItemEnchantmentStore.LookupEntry(enchant_id); + Item* mh = GetEquips(BOT_SLOT_MAINHAND); + Item* oh = GetEquips(BOT_SLOT_OFFHAND); + Item* item = nullptr; + uint8 itemSlot = 0; + + if (mh && !mh->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT)/* && mh->IsFitToSpellRequirements(spellInfo)*/) + { + item = mh; + itemSlot = BOT_SLOT_MAINHAND; + } + else if (oh && !oh->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT)/* && oh->IsFitToSpellRequirements(spellInfo)*/) + { + item = oh; + itemSlot = BOT_SLOT_OFFHAND; + } + else + ASSERT(false, "shaman bot attempted to enchant his weapons but cannot find a weapon to apply it!"); + + if (!IAmFree()) + master->GetSession()->SendEnchantmentLog(me->GetGUID(), me->GetGUID(), item->GetEntry(), enchant_id); + + item->SetUInt32Value(ITEM_FIELD_ENCHANTMENT_1_1 + slot*MAX_ENCHANTMENT_OFFSET + ENCHANTMENT_ID_OFFSET, enchant_id); + item->SetUInt32Value(ITEM_FIELD_ENCHANTMENT_1_1 + slot*MAX_ENCHANTMENT_OFFSET + ENCHANTMENT_DURATION_OFFSET, duration); + item->SetUInt32Value(ITEM_FIELD_ENCHANTMENT_1_1 + slot*MAX_ENCHANTMENT_OFFSET + ENCHANTMENT_CHARGES_OFFSET, charges); + ApplyItemEnchantment(item, TEMP_ENCHANTMENT_SLOT, itemSlot); + if (itemSlot == BOT_SLOT_MAINHAND) + mhEnchantExpireTimer = ITEM_ENCHANTMENT_EXPIRE_TIMER; + else if (itemSlot == BOT_SLOT_OFFHAND) + ohEnchantExpireTimer = ITEM_ENCHANTMENT_EXPIRE_TIMER; + GC_Timer = 1500; //needed + } + } + + void SpellHit(WorldObject* wcaster, SpellInfo const* spell) override + { + Unit* caster = wcaster->ToUnit(); + if (!caster) + return; + + uint32 spellId = spell->Id; + + //Maelstrom Weapon helper + if (spellId == MAELSTROM_WEAPON_BUFF) + { + if (Aura const* mwb = me->GetAura(MAELSTROM_WEAPON_BUFF)) + MaelstromCount = mwb->GetStackAmount(); + + MaelstromTimer = 30000; //30 sec duration then reset + } + + OnSpellHit(caster, spell); + } + + void SpellHitTarget(WorldObject* wtarget, SpellInfo const* spell) override + { + Unit* target = wtarget->ToUnit(); + if (!target) + return; + + uint32 spellId = spell->Id; + uint32 baseId = spell->GetFirstRankSpell()->Id; + + if (baseId == EARTH_SHIELD_1) + Earthy = true; + + if (baseId == HEX_1) + { + Hexy = true; + HexyCheckTimer += 2000; + } + + //Earthen Power part 2 + if ((GetSpec() == BOT_SPEC_SHAMAN_ENHANCEMENT) && me->GetLevel() >= 50 && baseId == EARTH_SHOCK_1) + { + if (AuraEffect* eff = target->GetAuraEffect(spellId, 0, me->GetGUID())) + eff->ChangeAmount(eff->GetAmount() * 2); + } + + //Lightning Overload + if ((GetSpec() == BOT_SPEC_SHAMAN_ELEMENTAL) && + me->GetLevel() >= 45 && (baseId == LIGHTNING_BOLT_1 || baseId == CHAIN_LIGHTNING_1) && + urand(1,100) <= 33) + { + uint32 procId = 0; + switch (spellId) + { + // Lightning Bolt + case 403: procId = 45284; break; // Rank 1 + case 529: procId = 45286; break; // Rank 2 + case 548: procId = 45287; break; // Rank 3 + case 915: procId = 45288; break; // Rank 4 + case 943: procId = 45289; break; // Rank 5 + case 6041: procId = 45290; break; // Rank 6 + case 10391: procId = 45291; break; // Rank 7 + case 10392: procId = 45292; break; // Rank 8 + case 15207: procId = 45293; break; // Rank 9 + case 15208: procId = 45294; break; // Rank 10 + case 25448: procId = 45295; break; // Rank 11 + case 25449: procId = 45296; break; // Rank 12 + case 49237: procId = 49239; break; // Rank 13 + case 49238: procId = 49240; break; // Rank 14 + // Chain Lightning + case 421: procId = 45297; break; // Rank 1 + case 930: procId = 45298; break; // Rank 2 + case 2860: procId = 45299; break; // Rank 3 + case 10605: procId = 45300; break; // Rank 4 + case 25439: procId = 45301; break; // Rank 5 + case 25442: procId = 45302; break; // Rank 6 + case 49270: procId = 49268; break; // Rank 7 + case 49271: procId = 49269; break; // Rank 8 + default: break; + } + + if (procId) + { + if (baseId == LIGHTNING_BOLT_1) + me->CastSpell(target, procId, true); + if (baseId == CHAIN_LIGHTNING_1) + { + //Normalize chance + if (urand(1,100) <= (100.f / spell->_effects[0].ChainTarget)) + me->CastSpell(target, procId, true); + } + } + } + + //Shields improvement, replaces Static Shock (part 2) and Improved Earth Shield (part 1) + if (baseId == LIGHTNING_SHIELD_1 || baseId == EARTH_SHIELD_1 || baseId == WATER_SHIELD_1) + { + if (Aura* shield = target->GetAura(spellId, me->GetGUID())) + { + shield->SetCharges(shield->GetCharges() + 6); + } + } + + OnSpellHitTarget(target, spell); + } + + void DamageDealt(Unit* victim, uint32& damage, DamageEffectType damageType) override + { + bot_ai::DamageDealt(victim, damage, damageType); + } + + void DamageTaken(Unit* u, uint32& /*damage*/, DamageEffectType /*damageType*/, SpellInfo const* /*spellInfo*/) override + { + if (!u) + return; + if (!u->IsInCombat() && !me->IsInCombat()) + return; + OnOwnerDamagedBy(u); + } + + void OwnerAttackedBy(Unit* u) override + { + if (!u) + return; + OnOwnerDamagedBy(u); + } + + uint8 GetPetPositionNumber(Creature const* summon) const override + { + for (uint8 i = 0; i != MAX_WOLVES; ++i) + if (_wolves[i] == summon->GetGUID()) + return i; + + return 0; + } + + void SummonBotPet(Unit* target) + { + UnsummonWolves(); + + uint32 entry = BOT_PET_SPIRIT_WOLF; + + for (uint8 i = 0; i != MAX_WOLVES; ++i) + { + //Position pos; + + //45 sec duration + Creature* myPet = me->SummonCreature(entry, *me, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 5s); + //me->GetNearPoint(myPet, pos.m_positionX, pos.m_positionY, pos.m_positionZ, 0, 2, me->GetOrientation()); + //myPet->GetMotionMaster()->MovePoint(me->GetMapId(), pos); + myPet->SetCreator(master); + myPet->SetOwnerGUID(me->GetGUID()); + myPet->SetFaction(master->GetFaction()); + myPet->SetControlledByPlayer(!IAmFree()); + myPet->SetPvP(me->IsPvP()); + myPet->SetByteValue(UNIT_FIELD_BYTES_2, 1, master->GetByteValue(UNIT_FIELD_BYTES_2, 1)); + myPet->SetUInt32Value(UNIT_CREATED_BY_SPELL, FERAL_SPIRIT_1); + + //botPet = myPet; + + myPet->Attack(target, true); + if (!HasBotCommandState(BOT_COMMAND_MASK_UNCHASE)) + myPet->GetMotionMaster()->MoveChase(target); + } + } + + void JustSummoned(Creature* summon) override + { + if (summon->GetEntry() == BOT_PET_SPIRIT_WOLF) + { + bool found = false; + for (uint8 i = 0; i != MAX_WOLVES; ++i) + { + if (!_wolves[i]) + { + _wolves[i] = summon->GetGUID(); + found = true; + break; + } + } + if (!found) + { + TC_LOG_ERROR("entities.unit", "Shaman_bot:JustSummoned() wolves array is full"); + ASSERT(false); + } + } + } + + void SummonedCreatureDespawn(Creature* summon) override + { + //TC_LOG_ERROR("entities.unit", "SummonedCreatureDespawn: {}'s {}", me->GetName(), summon->GetName()); + //if (summon == botPet) + // botPet = nullptr; + if (summon->GetEntry() == BOT_PET_SPIRIT_WOLF) + { + //bool found = false; + for (uint8 i = 0; i != MAX_WOLVES; ++i) + { + if (_wolves[i] == summon->GetGUID()) + { + _wolves[i] = ObjectGuid::Empty; + //found = true; + break; + } + } + //if (!found) + //{ + // TC_LOG_ERROR("entities.unit", "Shaman_bot:SummonedCreatureDespawn() wolf is not found in array"); + // ASSERT(false); + //} + } + } + + void UnsummonWolves() + { + for (uint8 i = 0; i != MAX_WOLVES; ++i) + { + if (_wolves[i]) + { + if (Unit* wo = ObjectAccessor::GetUnit(*me, _wolves[i])) + wo->ToTempSummon()->UnSummon(); + else + _wolves[i] = ObjectGuid::Empty; + } + } + } + + void UnsummonAll(bool /*savePets*/ = true) override + { + UnsummonWolves(); + + for (uint8 i = 0; i != MAX_TOTEMS; ++i) + { + if (_totems[i].first != ObjectGuid::Empty) + { + Unit* to = ObjectAccessor::GetUnit(*me, _totems[i].first); + if (!to) + { + //TC_LOG_ERROR("entities.player", "{} has no totem in slot {} during remove!", me->GetName(), i); + continue; + } + to->ToTotem()->UnSummon(); + } + } + } + + void OnBotDespawn(Creature* summon) override + { + if (!summon) + { + TC_LOG_ERROR("entities.player", "OnBotDespawn(): Shaman bot {} received NULL", me->GetName()); + ASSERT(false); + //UnsummonAll(false); + return; + } + + TempSummon const* totem = summon->ToTempSummon(); + if (!totem || !totem->IsTotem()) + { + //TC_LOG_ERROR("entities.player", "OnBotDespawn(): Shaman bot {} has despawned summon {} which is not a temp summon or not a totem...", me->GetName(), summon->GetName()); + return; + } + + int8 slot = -1; + switch (totem->m_Properties->Slot) + { + case SUMMON_SLOT_TOTEM_FIRE: slot = T_FIRE; break; + case SUMMON_SLOT_TOTEM_EARTH: slot = T_EARTH; break; + case SUMMON_SLOT_TOTEM_WATER: slot = T_WATER; break; + case SUMMON_SLOT_TOTEM_AIR: slot = T_AIR; break; + default: + TC_LOG_ERROR("entities.player", "OnBotDespawn(): Shaman bot {} has despawned totem {} in unknown slot {}", me->GetName(), summon->GetName(), totem->m_Properties->ID); + return; + } + + if (_totems[slot].first == ObjectGuid::Empty) + TC_LOG_ERROR("entities.player", "OnBotDespawn(): Shaman bot {} has despawned totem {} while not having it registered!", me->GetName(), summon->GetName()); + else if (_totems[slot].second._type == BOT_TOTEM_NONE || _totems[slot].second._type >= BOT_TOTEM_END) + TC_LOG_ERROR("entities.player", "OnBotDespawn(): Shaman bot {} has despawned totem {} with no type assigned!", me->GetName(), summon->GetName()); + + //here we reset totem category cd (not totem spell cd) if totem is destroyed + //TC_LOG_ERROR("entities.player", "OnBotDespawn(): {} despawned ({} : {})", summon->GetName(), summon->IsAlive() ? "alive" : summon->isDying() ? "justdied" : "unk", (uint32)summon->getDeathState()); + if (!summon->IsAlive()) // alive here means totem is being replaced or unsummoned through other means + TotemTimer[slot] = 0; + + _totems[slot].first = ObjectGuid::Empty; + _totems[slot].second._type = BOT_TOTEM_NONE; + me->m_SummonSlot[slot+1] = ObjectGuid::Empty; + } + + void OnBotSummon(Creature* summon) override + { + TempSummon const* totem = summon->ToTempSummon(); + if (!totem || !totem->IsTotem()) + { + //TC_LOG_ERROR("entities.player", "OnBotSummon(): Shaman bot {} has summoned creature {} which is not a temp summon or not a totem...", me->GetName(), summon->GetName()); + return; + } + + int8 slot = -1; + switch (totem->m_Properties->Slot) + { + case SUMMON_SLOT_TOTEM_FIRE: slot = T_FIRE; break; + case SUMMON_SLOT_TOTEM_EARTH: slot = T_EARTH; break; + case SUMMON_SLOT_TOTEM_WATER: slot = T_WATER; break; + case SUMMON_SLOT_TOTEM_AIR: slot = T_AIR; break; + default: + TC_LOG_ERROR("entities.player", "OnBotSummon(): Shaman bot {} has summoned totem {} with unknown type {}", me->GetName(), summon->GetName(), totem->m_Properties->ID); + return; + } + + //Unsummon current totem + ObjectGuid curTotemGUID = _totems[slot].first; + if (curTotemGUID) + { + Unit* curTotem = ObjectAccessor::GetUnit(*me, curTotemGUID); + if (curTotem) + curTotem->ToTotem()->UnSummon(); + } + + float radius = 30.f; + if (SpellInfo const* info = sSpellMgr->GetSpellInfo(summon->m_spells[0])) + if (SpellRadiusEntry const* entry = info->_effects[0].RadiusEntry) + radius = std::max(entry->RadiusMax, radius); + + uint32 createSpell = summon->GetUInt32Value(UNIT_CREATED_BY_SPELL); + if (createSpell) + createSpell = sSpellMgr->GetSpellInfo(createSpell)->GetFirstRankSpell()->Id; + //DEBUG + //if (!IAmFree()) + //{ + // std::ostringstream msg; + // msg << "Summoned " << summon->GetName() << " by basespell: " << createSpell; + // BotWhisper(msg.str().c_str()); + //} + uint32 btype; + switch (createSpell) + { + case SEARING_TOTEM_1: btype = BOT_TOTEM_SEARING; break; + case STONECLAW_TOTEM_1: btype = BOT_TOTEM_STONECLAW; break; + case SENTRY_TOTEM_1: btype = BOT_TOTEM_SENTRY; break; + case STONESKIN_TOTEM_1: btype = BOT_TOTEM_STONESKIN; break; + case HEALING_STREAM_TOTEM_1: btype = BOT_TOTEM_HEALING_STREAM; break; + case EARTHBIND_TOTEM_1: btype = BOT_TOTEM_EARTHBIND; break; + case EARTH_ELEMENTAL_TOTEM_1: btype = BOT_TOTEM_ELEMENTAL_EARTH; break; + case FIRE_ELEMENTAL_TOTEM_1: btype = BOT_TOTEM_ELEMENTAL_FIRE; break; + case MAGMA_TOTEM_1: btype = BOT_TOTEM_MAGMA; break; + case FLAMETONGUE_TOTEM_1: btype = BOT_TOTEM_FLAMETONGUE; break; + case FROST_RESISTANCE_TOTEM_1: btype = BOT_TOTEM_RESISTANCE_FROST; break; + case FIRE_RESISTANCE_TOTEM_1: btype = BOT_TOTEM_RESISTANCE_FIRE; break; + case NATURE_RESISTANCE_TOTEM_1: btype = BOT_TOTEM_RESISTANCE_NATURE; break; + case GROUNDING_TOTEM_1: btype = BOT_TOTEM_GROUNDING; break; + case STRENGTH_OF_EARTH_TOTEM_1: btype = BOT_TOTEM_STRENGTH_OF_EARTH; break; + case WINDFURY_TOTEM_1: btype = BOT_TOTEM_WINDFURY; break; + case WRATH_OF_AIR_TOTEM_1: btype = BOT_TOTEM_WRATH_OF_AIR; break; + case CLEANSING_TOTEM_1: btype = BOT_TOTEM_CLEANSING; break; + case MANA_SPRING_TOTEM_1: btype = BOT_TOTEM_MANA_SPRING; break; + case MANA_TIDE_TOTEM_1: btype = BOT_TOTEM_MANA_TIDE; break; + case TREMOR_TOTEM_1: btype = BOT_TOTEM_TREMOR; break; + case TOTEM_OF_WRATH_1: btype = BOT_TOTEM_WRATH; break; + default: + { + TC_LOG_ERROR("scripts", "Unknown totem create spell {}!", createSpell); + btype = BOT_TOTEM_NONE; + break; + } + } + _totems[slot].first = summon->GetGUID(); + _totems[slot].second._pos.Relocate(*summon); + _totems[slot].second._effradius = !((1<m_SummonSlot[slot+1] = _totems[slot].first; //needed for scripts handlers + + //TC_LOG_ERROR("entities.player", "shaman bot: summoned {} (type {}) at x={}, y={}, z={}", + // summon->GetName(), slot + 1, _totems[slot].second.pos.GetPositionX(), _totems[slot].second.pos.GetPositionY(), _totems[slot].second.pos.GetPositionZ()); + + //TODO: gets overriden in Spell::EffectSummonType (end) + //Without setting creator correctly it will be impossible to use summon X elemental totems + summon->SetCreator(me); + summon->SetDisplayId(sObjectMgr->GetModelForTotem(SummonSlot(slot+1), Races(me->GetRace()))); + summon->SetFaction(me->GetFaction()); + summon->SetPvP(me->IsPvP()); + summon->SetOwnerGUID(master->GetGUID()); + summon->SetControlledByPlayer(!IAmFree()); + //summon->SetUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED); + // totem will claim master's summon slot + // free it to avoid conflicts with other shaman bots and master + // if master is a shaman his totem will despawn + // fixed in summoning sequence + //master->m_SummonSlot[++slot] = 0; + + //After summon effects which are not handled for bot totems + //check by btype + + // Storm, Earth and Fire: Earthbind totem AoE root + if ((GetSpec() == BOT_SPEC_SHAMAN_ELEMENTAL) && btype == BOT_TOTEM_EARTHBIND && me->GetLevel() >= 40) + { + //master's talent will be found so do not cast earthgrab twice, instead let spell script roll the chance + //see spell_shaman.cpp + if (!master->GetAuraEffect(SPELL_AURA_DUMMY, SPELLFAMILY_SHAMAN, 3063, EFFECT_1)) + summon->CastSpell(summon, STORMEARTHANDFIRE_EARTHGRAB, false); + } + } + + float GetSpellAttackRange(bool longRange) const override + { + return longRange ? CalcSpellMaxRange(LIGHTNING_BOLT_1) : 20.f; + } + + uint32 GetAIMiscValue(uint32 data) const override + { + switch (data) + { + case BOTAI_MISC_ENCHANT_IS_AUTO_MH: + return needChooseMHEnchant; + case BOTAI_MISC_ENCHANT_IS_AUTO_OH: + return needChooseOHEnchant; + case BOTAI_MISC_ENCHANT_CAN_EXPIRE_MH: + return mhEnchantExpireTimer; + case BOTAI_MISC_ENCHANT_CAN_EXPIRE_OH: + return ohEnchantExpireTimer; + case BOTAI_MISC_ENCHANT_CURRENT_MH: + return mhEnchant; + case BOTAI_MISC_ENCHANT_CURRENT_OH: + return ohEnchant; + case BOTAI_MISC_ENCHANT_AVAILABLE_1: + return /*GetSpell(ROCKBITER_WEAPON_1) ? ROCKBITER_WEAPON_1 : */0; + case BOTAI_MISC_ENCHANT_AVAILABLE_2: + return GetSpell(FLAMETONGUE_WEAPON_1) ? FLAMETONGUE_WEAPON_1 : 0; + case BOTAI_MISC_ENCHANT_AVAILABLE_3: + return GetSpell(FROSTBRAND_WEAPON_1) ? FROSTBRAND_WEAPON_1 : 0; + case BOTAI_MISC_ENCHANT_AVAILABLE_4: + return GetSpell(WINDFURY_WEAPON_1) ? WINDFURY_WEAPON_1 : 0; + case BOTAI_MISC_ENCHANT_AVAILABLE_5: + return GetSpell(EARTHLIVING_WEAPON_1) ? EARTHLIVING_WEAPON_1 : 0; + case BOTAI_MISC_PET_TYPE: + return BOT_PET_SPIRIT_WOLF; + default: + return 0; + } + } + + void SetAIMiscValue(uint32 data, uint32 value) override + { + switch (data) + { + case BOTAI_MISC_ENCHANT_CAN_EXPIRE_MH: + if (value) + mhEnchantExpireTimer = 0; + break; + case BOTAI_MISC_ENCHANT_CAN_EXPIRE_OH: + if (value) + ohEnchantExpireTimer = 0; + break; + case BOTAI_MISC_ENCHANT_CURRENT_MH: + mhEnchant = value; + needChooseMHEnchant = value ? false : true; + break; + case BOTAI_MISC_ENCHANT_CURRENT_OH: + ohEnchant = value; + needChooseOHEnchant = value ? false : true; + break; + default: + break; + } + } + + void Reset() override + { + UnsummonAll(false); + for (uint8 i = 0; i != MAX_WOLVES; ++i) + _wolves[i] = ObjectGuid::Empty; + for (uint8 i = 0; i != MAX_TOTEMS; ++i) + TotemTimer[i] = 0; + + removeShapeshiftForm(); + + TotemsCheckTimer = 1500; + + HexyCheckTimer = 3000; + EarthyCheckTimer = 2000; + ShieldCheckTimer = 2000; + BloodlustCheckTimer = 5000; + MaelstromTimer = 0; + + MaelstromCount = 0; + + Hexy = false; + Earthy = false; + maelUseUp = false; + + mhEnchantExpireTimer = 1; + ohEnchantExpireTimer = 1; + + DefaultInit(); + + mhEnchant = 0; + ohEnchant = 0; + needChooseMHEnchant = true; + needChooseOHEnchant = true; + } + + void ReduceCD(uint32 diff) override + { + for (uint8 i = 0; i != MAX_TOTEMS; ++i) + if (TotemTimer[i] > diff) + TotemTimer[i] -= diff; + + if (TotemsCheckTimer > diff) TotemsCheckTimer -= diff; + + if (HexyCheckTimer > diff) HexyCheckTimer -= diff; + if (EarthyCheckTimer > diff) EarthyCheckTimer -= diff; + if (ShieldCheckTimer > diff) ShieldCheckTimer -= diff; + if (BloodlustCheckTimer > diff) BloodlustCheckTimer -= diff; + + if (MaelstromTimer > diff) MaelstromTimer -= diff; + else if (MaelstromCount) { MaelstromCount = 0; maelUseUp = false; } + + if (mhEnchantExpireTimer > diff) mhEnchantExpireTimer -= diff; + if (ohEnchantExpireTimer > diff) ohEnchantExpireTimer -= diff; + } + + void InitPowers() override + { + me->SetPowerType(POWER_MANA); + } + + void InitSpells() override + { + uint8 lvl = me->GetLevel(); + bool isElem = GetSpec() == BOT_SPEC_SHAMAN_ELEMENTAL; + bool isEnha = GetSpec() == BOT_SPEC_SHAMAN_ENHANCEMENT; + bool isRest = GetSpec() == BOT_SPEC_SHAMAN_RESTORATION; + + InitSpellMap(HEALING_WAVE_1); + InitSpellMap(CHAIN_HEAL_1); + InitSpellMap(LESSER_HEALING_WAVE_1); + InitSpellMap(ANCESTRAL_SPIRIT_1); + InitSpellMap(CURE_TOXINS_1); + InitSpellMap(FLAME_SHOCK_1); + InitSpellMap(EARTH_SHOCK_1); + InitSpellMap(FROST_SHOCK_1); + InitSpellMap(LIGHTNING_BOLT_1); + InitSpellMap(CHAIN_LIGHTNING_1); + InitSpellMap(LAVA_BURST_1); + InitSpellMap(LIGHTNING_SHIELD_1); + InitSpellMap(WATER_SHIELD_1); + InitSpellMap(WATER_BREATHING_1); + InitSpellMap(WATER_WALKING_1); + InitSpellMap(PURGE_1); + InitSpellMap(WIND_SHEAR_1); + InitSpellMap(HEX_1); + InitSpellMap((me->GetRaceMask() & RACEMASK_ALLIANCE) ? HEROISM_1 : BLOODLUST_1); //at least race is constant + + InitSpellMap(GHOST_WOLF_1); + + InitSpellMap(FIRE_NOVA_1); + //totems + InitSpellMap(EARTHBIND_TOTEM_1); + InitSpellMap(MAGMA_TOTEM_1); + //InitSpellMap(SEARING_TOTEM_1); + //InitSpellMap(STONECLAW_TOTEM_1); + InitSpellMap(FIRE_ELEMENTAL_TOTEM_1); + InitSpellMap(EARTH_ELEMENTAL_TOTEM_1); + InitSpellMap(FIRE_RESISTANCE_TOTEM_1); + InitSpellMap(FROST_RESISTANCE_TOTEM_1); + InitSpellMap(NATURE_RESISTANCE_TOTEM_1); + InitSpellMap(FLAMETONGUE_TOTEM_1); + InitSpellMap(GROUNDING_TOTEM_1); + //InitSpellMap(SENTRY_TOTEM_1); + //InitSpellMap(STONESKIN_TOTEM_1); + InitSpellMap(STRENGTH_OF_EARTH_TOTEM_1); + InitSpellMap(WINDFURY_TOTEM_1); + InitSpellMap(WRATH_OF_AIR_TOTEM_1); + InitSpellMap(CLEANSING_TOTEM_1); + //InitSpellMap(HEALING_STREAM_TOTEM_1); + InitSpellMap(MANA_SPRING_TOTEM_1); + InitSpellMap(TREMOR_TOTEM_1); + + InitSpellMap(TOTEMIC_RECALL_1); + + InitSpellMap(REINCARNATION_1); //base lvl 30, 30 min cd + + /*Talent*///lvl >= 40 && isElem ? InitSpellMap(ELEMENTAL_MASTERY_1) : RemoveSpell(ELEMENTAL_MASTERY_1); + /*Talent*/lvl >= 60 && isElem ? InitSpellMap(THUNDERSTORM_1) : RemoveSpell(THUNDERSTORM_1); + + /*Talent*/lvl >= 40 && isEnha ? InitSpellMap(STORMSTRIKE_1) : RemoveSpell(STORMSTRIKE_1); + /*Talent*/lvl >= 50 && isEnha ? InitSpellMap(SHAMANISTIC_RAGE_1) : RemoveSpell(SHAMANISTIC_RAGE_1); + /*Talent*/lvl >= 60 && isEnha ? InitSpellMap(FERAL_SPIRIT_1) : RemoveSpell(FERAL_SPIRIT_1); //not casted + + /*Talent*/lvl >= 20 && isRest ? InitSpellMap(TIDAL_FORCE_1) : RemoveSpell(TIDAL_FORCE_1); + /*Talent*/lvl >= 30 && isRest ? InitSpellMap(NATURES_SWIFTNESS_1) : RemoveSpell(NATURES_SWIFTNESS_1); + /*Talent*/lvl >= 50 && isRest ? InitSpellMap(EARTH_SHIELD_1) : RemoveSpell(EARTH_SHIELD_1); + /*Talent*/lvl >= 60 && isRest ? InitSpellMap(RIPTIDE_1) : RemoveSpell(RIPTIDE_1); + + /*Talent*/lvl >= 50 && isElem ? InitSpellMap(TOTEM_OF_WRATH_1) : RemoveSpell(TOTEM_OF_WRATH_1); + + /*Talent*/lvl >= 40 && isRest ? InitSpellMap(MANA_TIDE_TOTEM_1) : RemoveSpell(MANA_TIDE_TOTEM_1); + + CURE_TOXINS = InitSpell(me, CLEANSE_SPIRIT_1) ? CLEANSE_SPIRIT_1 : CURE_TOXINS_1; + RemoveSpell(CLEANSE_SPIRIT_1); + RemoveSpell(CURE_TOXINS_1); + InitSpellMap(CURE_TOXINS); + + //InitSpellMap(ROCKBITER_WEAPON_1); //lvl 1 + InitSpellMap(FLAMETONGUE_WEAPON_1); //lvl 10 + InitSpellMap(FROSTBRAND_WEAPON_1); //lvl 20 + InitSpellMap(WINDFURY_WEAPON_1); //lvl 30 + InitSpellMap(EARTHLIVING_WEAPON_1); //lvl 30 + } + + void ApplyClassPassives() const override + { + uint8 level = master->GetLevel(); + bool isElem = GetSpec() == BOT_SPEC_SHAMAN_ELEMENTAL; + bool isEnha = GetSpec() == BOT_SPEC_SHAMAN_ENHANCEMENT; + bool isRest = GetSpec() == BOT_SPEC_SHAMAN_RESTORATION; + + RefreshAura(ELEMENTAL_DEVASTATION3, isEnha && level >= 18 ? 1 : 0); + RefreshAura(ELEMENTAL_DEVASTATION2, isEnha && level >= 15 && level < 18 ? 1 : 0); + RefreshAura(ELEMENTAL_DEVASTATION1, isEnha && level >= 12 && level < 15 ? 1 : 0); + RefreshAura(ELEMENTAL_FOCUS, isElem && level >= 20 ? 1 : 0); + RefreshAura(ELEMENTAL_OATH, !IAmFree() && isElem && level >= 40 ? 1 : 0); + //RefreshAura(STORM_EARTH_AND_FIRE, isElem && level >= 45 ? 1 : 0); + + RefreshAura(TOUGHNESS, level >= 25 ? 1 : 0); + RefreshAura(FLURRY5, isEnha && level >= 29 ? 1 : 0); + RefreshAura(FLURRY4, isEnha && level >= 28 && level < 29 ? 1 : 0); + RefreshAura(FLURRY3, isEnha && level >= 27 && level < 28 ? 1 : 0); + RefreshAura(FLURRY2, isEnha && level >= 26 && level < 27 ? 1 : 0); + RefreshAura(FLURRY1, isEnha && level >= 25 && level < 26 ? 1 : 0); + RefreshAura(WEAPON_MASTERY, isEnha && level >= 30 ? 1 : 0); + RefreshAura(UNLEASHED_RAGE, !IAmFree() && isEnha && level >= 35 ? 1 : 0); + RefreshAura(IMPROVED_STORMSTRIKE, isEnha && level >= 40 ? 1 : 0); + RefreshAura(STATIC_SHOCK, isEnha && level >= 41 ? 1 : 0); + RefreshAura(EARTHEN_POWER, isEnha && level >= 50 ? 1 : 0); + RefreshAura(MAELSTROM_WEAPON5, isEnha && level >= 59 ? 1 : 0); + RefreshAura(MAELSTROM_WEAPON4, isEnha && level >= 58 && level < 59 ? 1 : 0); + RefreshAura(MAELSTROM_WEAPON3, isEnha && level >= 57 && level < 58 ? 1 : 0); + RefreshAura(MAELSTROM_WEAPON2, isEnha && level >= 56 && level < 57 ? 1 : 0); + RefreshAura(MAELSTROM_WEAPON1, isEnha && level >= 55 && level < 56 ? 1 : 0); + + RefreshAura(IMPROVED_WATER_SHIELD, isRest && level >= 20 ? 1 : 0); + RefreshAura(ANCESTRAL_HEALING, isRest && level >= 20 ? 1 : 0); + RefreshAura(ANCESTRAL_AWAKENING, isRest && level >= 50 ? 1 : 0); + RefreshAura(TIDAL_WAVES, isRest && level >= 55 ? 1 : 0); + + RefreshAura(SHAMAN_FLAME_SHOCK_PASSIVE); + + RefreshAura(SHAMAN_T10_RESTO_4P, level >= 70 ? 1 : 0); + + RefreshAura(GLYPH_THUNDERSTORM, GetSpell(THUNDERSTORM_1) ? 1 : 0); + RefreshAura(GLYPH_TOTEM_OF_WRATH, GetSpell(TOTEM_OF_WRATH_1) ? 1 : 0); + } + + bool CanUseManually(uint32 basespell) const override + { + switch (basespell) + { + case HEALING_WAVE_1: + case CHAIN_HEAL_1: + case LESSER_HEALING_WAVE_1: + case RIPTIDE_1: + case CLEANSE_SPIRIT_1: + case WATER_WALKING_1: + case WATER_BREATHING_1: + case BLOODLUST_1: + case HEROISM_1: + case SHAMANISTIC_RAGE_1: + //case ELEMENTAL_MASTERY_1: + case TIDAL_FORCE_1: + //Totems + //case EARTHBIND_TOTEM_1: + //case MAGMA_TOTEM_1: + //case SEARING_TOTEM_1: + //case STONECLAW_TOTEM_1: + //case FIRE_ELEMENTAL_TOTEM_1: + //case EARTH_ELEMENTAL_TOTEM_1: + //case FIRE_RESISTANCE_TOTEM_1: + //case FROST_RESISTANCE_TOTEM_1: + //case NATURE_RESISTANCE_TOTEM_1: + //case FLAMETONGUE_TOTEM_1: + case GROUNDING_TOTEM_1: + //case SENTRY_TOTEM_1: + //case STONESKIN_TOTEM_1: + //case STRENGTH_OF_EARTH_TOTEM_1: + //case WINDFURY_TOTEM_1: + //case WRATH_OF_AIR_TOTEM_1: + case CLEANSING_TOTEM_1: + //case HEALING_STREAM_TOTEM_1: + //case MANA_SPRING_TOTEM_1: + //case TOTEM_OF_WRATH_1: + case MANA_TIDE_TOTEM_1: + case TREMOR_TOTEM_1: + case TOTEMIC_RECALL_1: + return true; + case CURE_TOXINS_1: + return !GetSpell(CLEANSE_SPIRIT_1); + case FIRE_NOVA_1: + return _totems[T_FIRE].second._type != BOT_TOTEM_NONE; + default: + return false; + } + } + + bool HasAbilitiesSpecifics() const override { return true; } + void FillAbilitiesSpecifics(Player const* player, std::list &specList) override + { + uint32 textId1, textId2; + switch (mhEnchant) + { + //case ROCKBITER_WEAPON_1: textId1 = BOT_TEXT_"Rockbiter"; break; + case FLAMETONGUE_WEAPON_1: textId1 = BOT_TEXT_FLAMETONGUE; break; + case FROSTBRAND_WEAPON_1: textId1 = BOT_TEXT_FROSTBRAND; break; + case WINDFURY_WEAPON_1: textId1 = BOT_TEXT_WINDFURY; break; + case EARTHLIVING_WEAPON_1: textId1 = BOT_TEXT_EARTHLIVING; break; + default: textId1 = BOT_TEXT_NOTHING_C; break; + } + switch (ohEnchant) + { + //case ROCKBITER_WEAPON_1: textId2 = BOT_TEXT_"Rockbiter"; break; + case FLAMETONGUE_WEAPON_1: textId2 = BOT_TEXT_FLAMETONGUE; break; + case FROSTBRAND_WEAPON_1: textId2 = BOT_TEXT_FROSTBRAND; break; + case WINDFURY_WEAPON_1: textId2 = BOT_TEXT_WINDFURY; break; + case EARTHLIVING_WEAPON_1: textId2 = BOT_TEXT_EARTHLIVING; break; + default: textId2 = BOT_TEXT_NOTHING_C; break; + } + specList.push_back(LocalizedNpcText(player, BOT_TEXT_SLOT_MH) + ": " + LocalizedNpcText(player, textId1)); + specList.push_back(LocalizedNpcText(player, BOT_TEXT_SLOT_OH) + ": " + LocalizedNpcText(player, textId2)); + } + + std::vector const* GetDamagingSpellsList() const override + { + return &Shaman_spells_damage; + } + std::vector const* GetCCSpellsList() const override + { + return &Shaman_spells_cc; + } + std::vector const* GetHealingSpellsList() const override + { + return &Shaman_spells_heal; + } + std::vector const* GetSupportSpellsList() const override + { + return &Shaman_spells_support; + } + + void InitHeals() override + { + SpellInfo const* spellInfo; + if (InitSpell(me, HEALING_WAVE_1)) + { + spellInfo = sSpellMgr->GetSpellInfo(InitSpell(me, HEALING_WAVE_1)); + _heals[HEALING_WAVE_1] = me->SpellHealingBonusDone(me, spellInfo, spellInfo->_effects[0].CalcValue(me), HEAL, spellInfo->GetEffect(EFFECT_0), {}); + } + else + _heals[HEALING_WAVE_1] = 0; + + if (InitSpell(me, LESSER_HEALING_WAVE_1)) + { + spellInfo = sSpellMgr->GetSpellInfo(InitSpell(me, LESSER_HEALING_WAVE_1)); + _heals[LESSER_HEALING_WAVE_1] = me->SpellHealingBonusDone(me, spellInfo, spellInfo->_effects[0].CalcValue(me), HEAL, spellInfo->GetEffect(EFFECT_0), {}); + } + else + _heals[LESSER_HEALING_WAVE_1] = 0; + + if (InitSpell(me, RIPTIDE_1)) + { + spellInfo = sSpellMgr->GetSpellInfo(InitSpell(me, RIPTIDE_1)); + _heals[RIPTIDE_1] = me->SpellHealingBonusDone(me, spellInfo, spellInfo->_effects[0].CalcValue(me), HEAL, spellInfo->GetEffect(EFFECT_0), {}); + } + else + _heals[RIPTIDE_1] = 0; + + if (InitSpell(me, CHAIN_HEAL_1)) + { + spellInfo = sSpellMgr->GetSpellInfo(InitSpell(me, CHAIN_HEAL_1)); + _heals[CHAIN_HEAL_1] = me->SpellHealingBonusDone(me, spellInfo, spellInfo->_effects[0].CalcValue(me), HEAL, spellInfo->GetEffect(EFFECT_0), {}); + } + else + _heals[CHAIN_HEAL_1] = 0; + } + + private: + //Totem system + struct BotTotemParam + { + BotTotemParam() : _effradius(0.f) {} + uint32 _type; + Position _pos; + float _effradius; + }; + + typedef std::pair BotTotem; + BotTotem _totems[MAX_TOTEMS]; + uint32 TotemTimer[MAX_TOTEMS]; + //Wolves + ObjectGuid _wolves[MAX_WOLVES]; + //Spells + uint32 CURE_TOXINS; + //Timers + uint32 TotemsCheckTimer; + uint32 HexyCheckTimer, EarthyCheckTimer, ShieldCheckTimer, BloodlustCheckTimer, MaelstromTimer; + uint8 MaelstromCount; + bool Hexy, Earthy; + mutable bool maelUseUp; + uint32 mhEnchantExpireTimer, ohEnchantExpireTimer; + uint32 mhEnchant, ohEnchant; + bool needChooseMHEnchant, needChooseOHEnchant; + + bool canTremor; + + typedef std::unordered_map HealMap; + HealMap _heals; + + uint32 _getTotemsMask(std::map& idMap) const + { + uint32 mask = 0; + + Unit* cre; + uint32 sumonSpell; + uint32 baseId; + bool isTotem; + int8 mytype = -1; + Unit::AuraApplicationMap const& aurapps = me->GetAppliedAuras(); + for (Unit::AuraApplicationMap::const_iterator itr = aurapps.begin(); itr != aurapps.end(); ++itr) + { + //from totem aura extract base spell it could be summoned by + if (itr->second->GetBase()->GetType() != UNIT_AURA_TYPE) continue; + cre = itr->second->GetBase()->GetUnitOwner(); + //DEBUG + //uint32 base = cre->GetUInt32Value(UNIT_CREATED_BY_SPELL); + //if (base) + // baseId = sSpellMgr->GetSpellInfo(base)->GetFirstRankSpell()->Id; + //if (target->GetEntry() == 70025 && cre->GetGUID() != me->GetGUID()) + //{ + // TC_LOG_ERROR("spells","totemMask: unit {}, {} ({}), owner {} (crSp {}, base {}), istotem {}", target->GetName(), + // itr->second->GetBase()->GetSpellInfo()->SpellName[0], itr->second->GetBase()->GetId(), + // cre ? cre->GetName() : "unk", base, baseId, uint32(cre->IsTotem())); + //} + sumonSpell = cre ? cre->GetUInt32Value(UNIT_CREATED_BY_SPELL) : 0; + if (!sumonSpell || !cre->IsTotem()) + continue; + + isTotem = true; + baseId = sSpellMgr->GetSpellInfo(sumonSpell)->GetFirstRankSpell()->Id; + switch (baseId) + { + case FIRE_RESISTANCE_TOTEM_1: + mask |= BOT_TOTEM_MASK_RESISTANCE_FIRE; mytype = T_WATER; break; + case FROST_RESISTANCE_TOTEM_1: + mask |= BOT_TOTEM_MASK_RESISTANCE_FROST; mytype = T_FIRE; break; + case NATURE_RESISTANCE_TOTEM_1: + mask |= BOT_TOTEM_MASK_RESISTANCE_NATURE; mytype = T_AIR; break; + case FLAMETONGUE_TOTEM_1: + mask |= BOT_TOTEM_MASK_FLAMETONGUE; mytype = T_FIRE; break; + case GROUNDING_TOTEM_1: //no ranking + mask |= BOT_TOTEM_MASK_GROUNDING; mytype = T_AIR; break; + //case STONESKIN_TOTEM_1: + // mask |= BOT_TOTEM_MASK_STONESKIN; mytype = T_EARTH; break; + case STRENGTH_OF_EARTH_TOTEM_1: + mask |= BOT_TOTEM_MASK_STRENGTH_OF_EARTH; mytype = T_EARTH; break; + case WINDFURY_TOTEM_1: //no ranking + mask |= BOT_TOTEM_MASK_WINDFURY; mytype = T_AIR; break; + case WRATH_OF_AIR_TOTEM_1: //no ranking + mask |= BOT_TOTEM_MASK_WRATH_OF_AIR; mytype = T_AIR; break; + case MANA_SPRING_TOTEM_1: + mask |= BOT_TOTEM_MASK_MANA_SPRING; mytype = T_WATER; break; + case TOTEM_OF_WRATH_1: + mask |= BOT_TOTEM_MASK_WRATH; mytype = T_FIRE; break; + default: + isTotem = false; //next aura + break; + } + + if (isTotem) + { + idMap[baseId] = sumonSpell; + for (uint8 i = 0; i != MAX_TOTEMS; ++i) + { + if (itr->second->GetBase()->GetCasterGUID() == me->m_SummonSlot[i+1]) + { + //mask |= BOT_TOTEM_MASK_MY_TOTEM; + switch (mytype) + { + case T_FIRE: mask |= BOT_TOTEM_MASK_MY_TOTEM_FIRE; break; + case T_EARTH: mask |= BOT_TOTEM_MASK_MY_TOTEM_EARTH; break; + case T_WATER: mask |= BOT_TOTEM_MASK_MY_TOTEM_WATER; break; + case T_AIR: mask |= BOT_TOTEM_MASK_MY_TOTEM_AIR; break; + default: break; + } + break; + } + } + } + } + + return mask; + } + }; +}; + +void AddSC_shaman_bot() +{ + new shaman_bot(); +} diff --git a/src/server/game/AI/NpcBots/bot_spellbreaker_ai.cpp b/src/server/game/AI/NpcBots/bot_spellbreaker_ai.cpp new file mode 100644 index 000000000..c80270d8a --- /dev/null +++ b/src/server/game/AI/NpcBots/bot_spellbreaker_ai.cpp @@ -0,0 +1,612 @@ +#include "bot_ai.h" +#include "bot_GridNotifiers.h" +#include "botspell.h" +#include "Containers.h" +#include "Creature.h" +//#include "GridNotifiers.h" +#include "ScriptMgr.h" +#include "SpellAuraEffects.h" +/* +Spell Breaker NpcBot (by Trickerer onlysuffering@gmail.com) +Description: +Spellbreaker (Warcraft III tribute) +Abilities: +1) Steal Magic (Spellsteal). steals a benefical spell from an enemy and applies it to a nearby ally or removes a +negative spell from an ally and applies it to a nearby enemy, affects magic and curse effects, 3 seconds cooldown. +2) Control Magic NIY (no substitute for spell) +3) Feedback (passive). Successful melee attacks burn target's mana equal to damage caused, dealing arcane damage +Complete - 67% +TODO: +*/ + +enum SpellbreakerBaseSpells +{ + SPELLSTEAL_1 = SPELL_STEAL_MAGIC +}; +enum SpellbreakerPassives +{ +}; +enum SpellbreakerSpecial +{ + SPELLSTEAL_COST = 75 * 5, + + FEEDBACK_EFFECT = SPELL_FEEDBACK, + + MH_ATTACK_VISUAL = SPELL_ATTACK_MELEE_1H, + SPELLSTEAL_VISUAL_1 = 34396,// Zap selfcast + SPELLSTEAL_VISUAL_2 = SPELL_STEAL_MAGIC_VISUAL, + ENERGY_SYPHON_ENERGIZE = 27287 // Only for combat log spell message +}; + +static const uint32 Spellbreaker_spells_support_arr[] = +{ SPELLSTEAL_1 }; + +static const std::vector Spellbreaker_spells_support(FROM_ARRAY(Spellbreaker_spells_support_arr)); + +class spellbreaker_bot : public CreatureScript +{ +public: + spellbreaker_bot() : CreatureScript("spellbreaker_bot") { } + + CreatureAI* GetAI(Creature* creature) const override + { + return new spellbreaker_botAI(creature); + } +/* + bool OnGossipHello(Player* player, Creature* creature) + { + return creature->GetBotAI()->OnGossipHello(player, 0); + } + + bool OnGossipSelect(Player* player, Creature* creature, uint32 sender, uint32 action) + { + if (bot_ai* ai = creature->GetBotAI()) + return ai->OnGossipSelect(player, creature, sender, action); + return true; + } + + bool OnGossipSelectCode(Player* player, Creature* creature, uint32 sender, uint32 action, char const* code) + { + if (bot_ai* ai = creature->GetBotAI()) + return ai->OnGossipSelectCode(player, creature, sender, action, code); + return true; + } +*/ + struct spellbreaker_botAI : public bot_ai + { + spellbreaker_botAI(Creature* creature) : bot_ai(creature) + { + _botclass = BOT_CLASS_SPELLBREAKER; + + InitUnitFlags(); + + //spellbreaker immunities + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_POSSESS, true); + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_CHARM, true); + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_SHAPESHIFT, true); + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_TRANSFORM, true); + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_DISARM, true); + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_DISARM_OFFHAND, true); + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_FEAR, true); + me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_DISARM, true); + me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_POLYMORPH, true); + me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_FEAR, true); + me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_HORROR, true); + me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_TURN, true); + me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_SLEEP, true); + me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_SILENCE, true); + me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_SNARE, true); + } + + bool doCast(Unit* victim, uint32 spellId) + { + if (CheckBotCast(victim, spellId) != SPELL_CAST_OK) + return false; + return bot_ai::doCast(victim, spellId); + } + + void StartAttack(Unit* u, bool force = false) + { + if (!bot_ai::StartAttack(u, force)) + return; + GetInPosition(force, u); + } + + void JustEnteredCombat(Unit* u) override { bot_ai::JustEnteredCombat(u); } + void KilledUnit(Unit* u) override { bot_ai::KilledUnit(u); } + void EnterEvadeMode(EvadeReason why = EVADE_REASON_OTHER) override { bot_ai::EnterEvadeMode(why); } + void MoveInLineOfSight(Unit* u) override { bot_ai::MoveInLineOfSight(u); } + void JustDied(Unit* u) override { /*UnsummonAll(false);*/ bot_ai::JustDied(u); } + void DoNonCombatActions(uint32 /*diff*/) { } + + void UpdateAI(uint32 diff) override + { + if (!GlobalUpdate(diff)) + return; + + DoVehicleActions(diff); + if (!CanBotAttackOnVehicle()) + return; + + if (IsPotionReady()) + { + if (me->GetPower(POWER_MANA) < SPELLSTEAL_COST) + DrinkPotion(true); + else if (GetHealthPCT(me) < 50) + DrinkPotion(false); + } + + if ((me->GetVictim() || Rand() < 15) && IsSpellReady(SPELLSTEAL_1, diff)) + CureGroup(GetSpell(SPELLSTEAL_1), diff); + + if (master->IsInCombat() || me->IsInCombat()) + CheckDispel(diff); + + if (ProcessImmediateNonAttackTarget()) + return; + + if (!CheckAttackTarget()) + return; + + if (IsCasting()) + return; + + CheckUsableItems(diff); + + Attack(diff); + } + + void Attack(uint32 /*diff*/) + { + Unit* mytar = opponent ? opponent : disttarget ? disttarget : nullptr; + if (!mytar) + return; + + StartAttack(mytar, IsMelee()); + + CheckAttackState(); + if (!me->IsAlive() || !mytar->IsAlive()) + return; + + MoveBehind(mytar); + } + + void CheckDispel(uint32 diff) + { + if (!IsSpellReady(SPELLSTEAL_1, diff) || IsCasting()) + return; + + Unit* target = FindHostileDispelTarget(CalcSpellMaxRange(SPELLSTEAL_1)); + if (target && doCast(target, GetSpell(SPELLSTEAL_1))) + return; + + SetSpellCooldown(SPELLSTEAL_1, 500); //fail + } + + void ApplyClassDamageMultiplierMelee(uint32& /*damage*/, CalcDamageInfo& damageinfo) const override + { + float pctbonus = 1.0f; + + if (damageinfo.Target && damageinfo.Target->GetPowerType() == POWER_MANA && damageinfo.Target->GetMaxPower(POWER_MANA) > 1 && + damageinfo.Target->GetPower(POWER_MANA) < me->GetWeaponDamageRange(BASE_ATTACK, MAXDAMAGE)) + { + pctbonus *= 3.f; + if (_doCrit == false && urand(1,100) < 2 * GetBotCritChance()) + _doCrit = true; + } + else if (_doCrit == true) + _doCrit = false; + + damageinfo.Damages[0].Damage *= pctbonus; + } + + void ApplyClassEffectMods(SpellInfo const* spellInfo, uint8 effIndex, float& value) const override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + float pctbonus = 1.0f; + + //Feedback scaling: 50% + if (baseId == FEEDBACK_EFFECT && effIndex == EFFECT_0) + value += me->SpellBaseDamageBonusDone(SPELL_SCHOOL_MASK_MAGIC) * 0.5f * me->CalculateDefaultCoefficient(spellInfo, SPELL_DIRECT_DAMAGE) * me->CalculateSpellpowerCoefficientLevelPenalty(spellInfo); + + value = value * pctbonus; + } + + MeleeHitOutcome GetNextAttackMeleeOutCome() const override + { + return _doCrit ? MELEE_HIT_CRIT : bot_ai::GetNextAttackMeleeOutCome(); + } + + void OnClassSpellGo(SpellInfo const* spellInfo) override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + + if (baseId == FEEDBACK_EFFECT) + me->CastSpell(me, MH_ATTACK_VISUAL, true); + } + + void SpellHitTarget(WorldObject* wtarget, SpellInfo const* spell) override + { + Unit* target = wtarget->ToUnit(); + if (!target) + return; + + uint32 baseId = spell->GetFirstRankSpell()->Id; + + if (baseId == SPELLSTEAL_1) + ProcessSpellsteal(target); + + OnSpellHitTarget(target, spell); + } + + void SpellHit(WorldObject* wcaster, SpellInfo const* spell) override + { + Unit* caster = wcaster->ToUnit(); + if (!caster) + return; + + OnSpellHit(caster, spell); + } + + void DamageDealt(Unit* victim, uint32& damage, DamageEffectType damageType) override + { + //Feedback + if (damage && victim != me && damageType == DIRECT_DAMAGE) + { + if (victim->GetPowerType() == POWER_MANA && victim->GetMaxPower(POWER_MANA) > 1) + { + if (uint32 burned = std::min(victim->GetPower(POWER_MANA), damage + me->GetLevel() * 2)) + { + int32 basepoints = int32(burned); + //reduce amount againts ex bots + if (victim->GetTypeId() == TYPEID_UNIT && victim->ToCreature()->GetBotClass() >= BOT_CLASS_EX_START) + basepoints /= 10; + + CastSpellExtraArgs args(true); + args.AddSpellBP0(basepoints); + me->CastSpell(victim, FEEDBACK_EFFECT, args); + } + } + else + { + me->EnergizeBySpell(me, ENERGY_SYPHON_ENERGIZE, int32(damage / 4), POWER_MANA); + me->SendPlaySpellVisual(524); //mana gain visual + } + } + + bot_ai::DamageDealt(victim, damage, damageType); + } + + void DamageTaken(Unit* u, uint32& /*damage*/, DamageEffectType /*damageType*/, SpellInfo const* /*spellInfo*/) override + { + if (!u) + return; + if (!u->IsInCombat() && !me->IsInCombat()) + return; + OnOwnerDamagedBy(u); + } + + void OwnerAttackedBy(Unit* u) override + { + if (!u) + return; + OnOwnerDamagedBy(u); + } + + void Reset() override + { + _doCrit = false; + + DefaultInit(); + } + + void ReduceCD(uint32 /*diff*/) override + { + } + + void InitPowers() override + { + me->SetPowerType(POWER_MANA); + } + + void InitSpells() override + { + InitSpellMap(SPELLSTEAL_1, true, false); + } + + void ApplyClassPassives() const override + { + } + + bool CanUseManually(uint32 basespell) const override + { + switch (basespell) + { + case SPELLSTEAL_1: + return true; + default: + return false; + } + } + + //std::vector const* GetDamagingSpellsList() const override + //{ + // return &Spellbreaker_spells_damage; + //} + //std::vector const* GetCCSpellsList() const override + //{ + // return &Spellbreaker_spells_cc; + //} + //std::vector const* GetHealingSpellsList() const override + //{ + // return &Spellbreaker_spells_heal; + //} + std::vector const* GetSupportSpellsList() const override + { + return &Spellbreaker_spells_support; + } + + private: + + mutable bool _doCrit; + + void ProcessSpellsteal(Unit* target) + { + DispelChargesList steal_list; + + bool const isFriend = IsInBotParty(target) || target->IsFriendlyTo(me); + static const uint32 sbDispelMask = (1<GetName(), uint32(isFriend)); + + Unit::AuraMap const& auras = target->GetOwnedAuras(); + for (Unit::AuraMap::const_iterator itr = auras.begin(); itr != auras.end(); ++itr) + { + Aura* aura = itr->second; + + if (aura->IsPassive() || !(aura->GetSpellInfo()->GetDispelMask() & sbDispelMask) || + (aura->GetSpellInfo()->AttributesEx4 & SPELL_ATTR4_NOT_STEALABLE)) + continue; + + AuraApplication const* aurApp = aura->GetApplicationOfTarget(target->GetGUID()); + if (!aurApp) + continue; + + //do not dispel positive auras from enemies and negative ones from friends + if (aurApp->IsPositive() == isFriend) + continue; + + int32 chance = aura->CalcDispelChance(target, !isFriend); + if (!chance) + continue; + //TC_LOG_ERROR("entities.unit", "{}", aura->GetSpellInfo()->SpellName[0]); + + // The charges / stack amounts don't count towards the total number of auras that can be dispelled. + // Ie: A dispel on a target with 5 stacks of Winters Chill and a Polymorph has 1 / (1 + 1) -> 50% chance to dispell + // Polymorph instead of 1 / (5 + 1) -> 16%. + bool dispel_charges = (aura->GetSpellInfo()->AttributesEx7 & SPELL_ATTR7_DISPEL_CHARGES); + uint8 charges = dispel_charges ? aura->GetCharges() : aura->GetStackAmount(); + if (charges > 0) + steal_list.emplace_back(aura, chance, charges); + } + + if (steal_list.empty()) + return; + + //TC_LOG_ERROR("entities.unit", "failcount..."); + + size_t remaining = steal_list.size(); + uint32 failCount = 0; + DispelChargesList success_list; + success_list.reserve(max_dispelled); + WorldPacket dataFail(SMSG_DISPEL_FAILED, 8+8+4+4+max_dispelled*4); + // dispel N = damage buffs (or while exist buffs for dispel) + for (uint8 count = 0; count < max_dispelled && remaining > 0;) + { + // Random select buff for dispel + DispelChargesList::iterator itr = steal_list.begin(); + std::advance(itr, urand(0, steal_list.size() - 1)); + + //int32 chance = itr->RollDispel();//itr->first->CalcDispelChance(target, !isFriend); + // 2.4.3 Patch Notes: "Dispel effects will no longer attempt to remove effects that have 100% dispel resistance." + //if (!chance) + //{ + // steal_list.erase(itr); + // continue; + //} + //else + bool chance = itr->RollDispel(); + { + if (chance/*roll_chance_i(chance)*/) + { + auto successItr = std::find_if(success_list.begin(), success_list.end(), [&itr](DispelableAura& dispelAura) -> bool + { + if (dispelAura.GetAura()->GetId() == itr->GetAura()->GetId() && dispelAura.GetAura()->GetCaster() == itr->GetAura()->GetCaster()) + return true; + return false; + }); + + if (successItr == success_list.end()) + success_list.emplace_back(itr->GetAura(), 0, 1); + else + successItr->IncrementCharges(); + + if (!itr->DecrementCharge()) + { + --remaining; + std::swap(*itr, steal_list[remaining]); + } + //success_list.push_back(std::make_pair(itr->first->GetId(), itr->first->GetCasterGUID())); + //--itr->second; + //if (itr->second <= 0) + // steal_list.erase(itr); + } + else + { + if (!failCount) + { + // Failed to dispell + dataFail << uint64(me->GetGUID()); // Caster GUID + dataFail << uint64(target->GetGUID()); // Victim GUID + dataFail << uint32(SPELLSTEAL_1); // dispel spell id + } + ++failCount; + dataFail << uint32(itr->GetAura()->GetId()); // Spell Id + } + ++count; + } + } + + if (failCount) + me->SendMessageToSet(&dataFail, true); + + if (success_list.empty()) + return; + + //TC_LOG_ERROR("entities.unit", "logs and auras"); + + WorldPacket dataSuccess(SMSG_SPELLSTEALLOG, 8+8+4+1+4+max_dispelled*5); + dataSuccess << target->GetPackGUID(); // Victim GUID + dataSuccess << me->GetPackGUID(); // Caster GUID + dataSuccess << uint32(SPELLSTEAL_1); // dispel spell id + dataSuccess << uint8(0); // not used + dataSuccess << uint32(success_list.size()); // count + + Unit* randomTarget = nullptr; + + std::list targets; + + if (isFriend) //negative spell from friend to enemy + { + GetNearbyTargetsList(targets, 50, 0); + if (Unit* u = me->GetVictim()) + { + if (!u->GetVictim() && me->IsWithinDistInMap(u, 50) && !me->IsFriendlyTo(u) && + me->IsValidAttackTarget(u) && u->GetCreatureType() != CREATURE_TYPE_CRITTER && !u->IsTotem() && + me->CanSeeOrDetect(u)) + targets.push_back(u); + } + //Trinity::AnyUnfriendlyAttackableVisibleUnitInObjectRangeCheck check(me, 50.f); + //Trinity::UnitListSearcher searcher(me, targets, check); + //me->VisitNearbyObject(50.f, searcher); + } + else + { + GetNearbyFriendlyTargetsList(targets, 50); //not self + targets.push_back(me); //add self + } + + if (!targets.empty()) + { + //if target has stealed aura we should skip him if possible + std::list targetsCopy = targets; + targets.remove_if(BOTAI_PRED::AuraedTargetExclude(success_list.front().GetAura()->GetId())); + + randomTarget = Trinity::Containers::SelectRandomContainerElement(!targets.empty() ? targets : targetsCopy); + } + + for (DispelChargesList::iterator itr = success_list.begin(); itr != success_list.end(); ++itr) + { + dataSuccess << uint32(itr->GetAura()->GetId()); // Spell Id + dataSuccess << uint8(0); // 0 - steals !=0 transfers + + if (randomTarget) + { + //target->RemoveAurasDueToSpellBySteal(itr->first, itr->second, randomTarget); + TransferAura(itr->GetAura()->GetId(), itr->GetAura()->GetCasterGUID(), target, randomTarget); + randomTarget->CastSpell(randomTarget, SPELLSTEAL_VISUAL_1, true); + } + else + target->RemoveAurasDueToSpellByDispel(itr->GetAura()->GetId(), SPELLSTEAL_1, itr->GetAura()->GetCasterGUID(), me, uint8(-1)); + } + me->CastSpell(target, SPELLSTEAL_VISUAL_2, true); + + me->SendMessageToSet(&dataSuccess, true); + } + + void TransferAura(uint32 spellId, ObjectGuid casterGUID, Unit* target, Unit* newTarget) + { + //Copied from Unit::RemoveAurasDueToSpellBySteal with modifications + Unit::AuraMapBoundsNonConst range = target->GetOwnedAuras().equal_range(spellId); + for (Unit::AuraMap::iterator iter = range.first; iter != range.second;) + { + Aura* aura = iter->second; + if (aura->GetCasterGUID() == casterGUID) + { + int32 damage[MAX_SPELL_EFFECTS]; + int32 baseDamage[MAX_SPELL_EFFECTS]; + uint8 effMask = 0; + uint8 recalculateMask = 0; + Unit* caster = aura->GetCaster(); + for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) + { + if (aura->GetEffect(i)) + { + baseDamage[i] = aura->GetEffect(i)->GetBaseAmount(); + damage[i] = aura->GetEffect(i)->GetAmount(); + effMask |= (1<GetEffect(i)->CanBeRecalculated()) + recalculateMask |= (1<GetSpellInfo()->AttributesEx7 & SPELL_ATTR7_DISPEL_CHARGES); + // Limit max duration to 5 minutes + int32 dur = std::min(int32(5 * MINUTE * IN_MILLISECONDS), aura->GetDuration()); + // But at least for 5 seconds + dur = std::max(dur, 5 * IN_MILLISECONDS); + + if (Aura* oldAura = newTarget->GetAura(aura->GetId(), aura->GetCasterGUID())) + { + if (stealCharge) + oldAura->ModCharges(aura->GetCharges()); + else + oldAura->ModStackAmount(aura->GetStackAmount()); + oldAura->SetDuration(dur); + } + else + { + // single target state must be removed before aura creation to preserve existing single target aura + if (aura->IsSingleTarget()) + aura->UnregisterSingleTarget(); + + AuraCreateInfo createInfo(aura->GetSpellInfo(), effMask, newTarget); + createInfo.SetCasterGUID(aura->GetCasterGUID()); + createInfo.SetBaseAmount(baseDamage); + //Auras created by scripts may have no caster, prevent crash in Aura::TryRefreshStackOrCreate() + if (!createInfo.CasterGUID) + createInfo.SetCasterGUID(me->GetGUID()); + if (Aura* newAura = Aura::TryRefreshStackOrCreate(createInfo)) + { + // created aura must not be single target aura,, so stealer won't loose it on recast + if (newAura->IsSingleTarget()) + { + newAura->UnregisterSingleTarget(); + aura->SetIsSingleTarget(true); + caster->GetSingleCastAuras().push_back(aura); + } + newAura->SetLoadedState(aura->GetMaxDuration(), dur, aura->GetCharges(), aura->GetStackAmount(), recalculateMask, aura->GetCritChance(), aura->CanApplyResilience(), &damage[0]); + newAura->ApplyForTargets(); + } + } + + aura->Remove(AURA_REMOVE_BY_ENEMY_SPELL); + return; + } + else + ++iter; + } + } + }; +}; + +void AddSC_spellbreaker_bot() +{ + new spellbreaker_bot(); +} diff --git a/src/server/game/AI/NpcBots/bot_sphynx_ai.cpp b/src/server/game/AI/NpcBots/bot_sphynx_ai.cpp new file mode 100644 index 000000000..8714ec1bb --- /dev/null +++ b/src/server/game/AI/NpcBots/bot_sphynx_ai.cpp @@ -0,0 +1,569 @@ +#include "bot_ai.h" +#include "bot_GridNotifiers.h" +#include "botmgr.h" +#include "botspell.h" +#include "bottraits.h" +#include "Containers.h" +#include "Creature.h" +#include "ScriptMgr.h" +/* +Obsidian Destroyer NpcBot (by Trickerer onlysuffering@gmail.com) +Description: +Obsidian winged monstrocity with unsatable hunger for magic (Warcraft III tribute) +Specifics: +High armor, very high resistances, partially immune to magic, negative mana regeneration (-2%/sec, cannot be changed), +any armor, dual-wielding wands, no physical attack, spellpower = 50% attack power + 200% intellect +Abilities: +1) Devour Magic: dispel up to 2 magic effects from enemies, up to 2 magic effects and up to 2 curses from allies +and damaging summoned units in 20 yards area, every dispelled effect restores 20% mana and 5% health, 7 seconds cooldown +2) Shadow Bolt: main attack, single target, no mana cost +3) Shadow Blast: 125 mana empowered attack, splash damage (6.25% of base mana) +4) Shadow Armor (passive, custom): restores mana equal to a percentage of damage taken +5) Drain Mana: leech all mana from a friendly target (up to 100% of od's max mana) +6) Replenish Mana: restores 2% of max mana to up to 10 surrounding allies within 25yds at cost of all mana +7) Replenish Life: restores 3% of max hp to up to 10 surrounding allies within 25yds at cost of all mana +Complete - 100% +TODO: +*/ + +enum SphynxBaseSpells +{ + MAIN_ATTACK_1 = SPELL_SHADOW_BOLT1, + SPLASH_ATTACK_1 = SPELL_SHADOW_BLAST, + DEVOUR_MAGIC_1 = SPELL_DEVOUR_MAGIC, + DRAIN_MANA_1 = SPELL_DRAIN_MANA, + + REPLENISH_MANA_1 = SPELL_REPLENISH_MANA, + REPLENISH_HEALTH_1 = SPELL_REPLENISH_HEALTH +}; +enum SphynxPassives +{ +}; +enum SphynxSpecial +{ + MH_ATTACK_ANIM = SPELL_ATTACK_MELEE_1H, + MH_OH_ATTACK_ANIM = SPELL_ATTACK_MELEE_RANDOM, + SPELL_ENERGIZE = 34424,//"Shadow Armor" + + SPLASH_ATTACK_COST = BASE_MANA_SPHYNX/16//6.25% +}; + +static const uint32 Sphynx_spells_damage_arr[] = +{ /*MAIN_ATTACK_1, */SPLASH_ATTACK_1 }; + +static const uint32 Sphynx_spells_heal_arr[] = +{ REPLENISH_HEALTH_1 }; + +static const uint32 Sphynx_spells_support_arr[] = +{ DEVOUR_MAGIC_1, /*DRAIN_MANA_1, */REPLENISH_HEALTH_1, REPLENISH_MANA_1 }; + +static const std::vector Sphynx_spells_damage(FROM_ARRAY(Sphynx_spells_damage_arr)); +static const std::vector Sphynx_spells_heal(FROM_ARRAY(Sphynx_spells_heal_arr)); +static const std::vector Sphynx_spells_support(FROM_ARRAY(Sphynx_spells_support_arr)); + +class sphynx_bot : public CreatureScript +{ +public: + sphynx_bot() : CreatureScript("sphynx_bot") { } + + CreatureAI* GetAI(Creature* creature) const override + { + return new sphynx_botAI(creature); + } +/* + bool OnGossipHello(Player* player, Creature* creature) + { + return creature->GetBotAI()->OnGossipHello(player, 0); + } + + bool OnGossipSelect(Player* player, Creature* creature, uint32 sender, uint32 action) + { + if (bot_ai* ai = creature->GetBotAI()) + return ai->OnGossipSelect(player, creature, sender, action); + return true; + } + + bool OnGossipSelectCode(Player* player, Creature* creature, uint32 sender, uint32 action, char const* code) + { + if (bot_ai* ai = creature->GetBotAI()) + return ai->OnGossipSelectCode(player, creature, sender, action, code); + return true; + } +*/ + struct sphynx_botAI : public bot_ai + { + sphynx_botAI(Creature* creature) : bot_ai(creature) + { + _botclass = BOT_CLASS_SPHYNX; + + InitUnitFlags(); + + //sphynx immunities + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_POSSESS, true); + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_CHARM, true); + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_SILENCE, true); + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_SHAPESHIFT, true); + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_TRANSFORM, true); + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_BLOCK_SPELL_FAMILY, true); + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_DISARM, true); + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_DISARM_OFFHAND, true); + me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_FEAR, true); + me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_DISARM, true); + me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_BLEED, true); + me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_POLYMORPH, true); + me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_INFECTED, true); + me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_INTERRUPT, true); + me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_FEAR, true); + me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_HORROR, true); + me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_TURN, true); + me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_SLEEP, true); + me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_SILENCE, true); + me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_SNARE, true); + me->ApplySpellImmune(0, IMMUNITY_EFFECT, SPELL_EFFECT_KNOCK_BACK, true); + me->ApplySpellImmune(0, IMMUNITY_EFFECT, SPELL_EFFECT_KNOCK_BACK_DEST, true); + } + + bool doCast(Unit* victim, uint32 spellId, bool triggered = false) + { + if (CheckBotCast(victim, spellId) != SPELL_CAST_OK) + return false; + return bot_ai::doCast(victim, spellId, triggered); + } + + void StartAttack(Unit* u, bool force = false) + { + if (!bot_ai::StartAttack(u, force)) + return; + GetInPosition(force, u); + } + + void JustEnteredCombat(Unit* u) override { dmgReceived = 0; DraincheckTimer = 2000; bot_ai::JustEnteredCombat(u); } + void KilledUnit(Unit* u) override { bot_ai::KilledUnit(u); } + void EnterEvadeMode(EvadeReason why = EVADE_REASON_OTHER) override { bot_ai::EnterEvadeMode(why); } + void MoveInLineOfSight(Unit* u) override { bot_ai::MoveInLineOfSight(u); } + void JustDied(Unit* u) override { dmgReceived = 0; bot_ai::JustDied(u); } + void DoNonCombatActions(uint32 /*diff*/) { } + + void UpdateAI(uint32 diff) override + { + if (!GlobalUpdate(diff)) + return; + + DoVehicleActions(diff); + if (!CanBotAttackOnVehicle()) + return; + + CheckDevourMagic(diff); + + if (IsSpellReady(DEVOUR_MAGIC_1, diff)) + CureGroup(DEVOUR_MAGIC_1, diff); + + //if (!me->IsInCombat()) + // DoNonCombatActions(diff); + + if (IsCasting()) + return; + + CheckReplenishHealth(diff); + CheckReplenishMana(diff); + + if (ProcessImmediateNonAttackTarget()) + return; + + if (!CheckAttackTarget()) + return; + + CheckUsableItems(diff); + + Attack(diff); + } + + void Attack(uint32 diff) + { + Unit* mytar = opponent ? opponent : disttarget ? disttarget : nullptr; + if (!mytar) + return; + + StartAttack(mytar, IsMelee()); + + CheckAttackState(); + if (!me->IsAlive() || !mytar->IsAlive()) + return; + + CheckDrainMana(diff); + + MoveBehind(mytar); + + if (!HasRole(BOT_ROLE_DPS)) + return; + + if (GC_Timer > diff) + return; + + if (me->GetDistance(mytar) > 30) + return; + + if (me->isMoving() && !me->HasInArc(float(M_PI)/2, mytar)) + return; + + if (!CanAffectVictimAny(mytar, SPELL_SCHOOL_SHADOW, SPELL_SCHOOL_ARCANE)) + return; + + if (me->GetPower(POWER_MANA) >= SPLASH_ATTACK_COST && IsSpellReady(SPLASH_ATTACK_1, diff)) + { + if (doCast(mytar, GetSpell(SPLASH_ATTACK_1))) + return; + } + else if (IsSpellReady(MAIN_ATTACK_1, diff)) + { + if (doCast(mytar, GetSpell(MAIN_ATTACK_1))) + return; + } + } + + void CheckDevourMagic(uint32 diff) + { + if (DevourcheckTimer > diff || !IsSpellReady(DEVOUR_MAGIC_1, diff, false) || IsCasting() || + (GetHealthPCT(me) > 75 && Rand() > 15 && + (!HasRole(BOT_ROLE_DPS) || me->GetPower(POWER_MANA) >= SPLASH_ATTACK_COST * 6))) + return; + + DevourcheckTimer = urand(350, 700); + + Unit* target = FindHostileDispelTarget(40); + if (target && doCast(target, GetSpell(DEVOUR_MAGIC_1))) + return; + } + + void CheckDrainMana(uint32 diff) + { + if (DraincheckTimer > diff || Rand() > 40 || IAmFree() || !HasRole(BOT_ROLE_DPS) || IsCasting() || + !IsSpellReady(DRAIN_MANA_1, diff, false) || me->GetPower(POWER_MANA) >= SPLASH_ATTACK_COST) + return; + + DraincheckTimer = urand(750, 1500); + + std::list targets; + GetNearbyFriendlyTargetsList(targets, 40); + targets.remove_if(BOTAI_PRED::DrainTargetExclude()); + + if (targets.empty()) + return; + + Unit* target = Trinity::Containers::SelectRandomContainerElement(targets); + if (doCast(target, GetSpell(DRAIN_MANA_1))) + return; + } + + void CheckReplenishHealth(uint32 diff) + { + if (ReplHealthcheckTimer > diff || !IsSpellReady(REPLENISH_HEALTH_1, diff) || IAmFree() || + !HasRole(BOT_ROLE_HEAL) || IsCasting() || + (HasRole(BOT_ROLE_DPS) && me->GetPower(POWER_MANA) > 0)) + return; + + ReplHealthcheckTimer = 1000; + + Group const* gr = master->GetGroup(); + if (!gr) + return; + + bool haveHp = false; + uint8 partycombat = 0, partynocombat = 0; + for (Unit const* member : BotMgr::GetAllGroupMembers(gr)) + { + if (me->GetMap() == member->FindMap()) + { + if (member->IsInCombat()) + partycombat++; + else if (member->IsAlive()) + partynocombat++; + + if (!haveHp && member->IsAlive() && me->GetDistance(member) < 15 && GetHealthPCT(member) < 95) + haveHp = true; + } + } + + if (haveHp && (!me->IsInCombat() || partycombat > partynocombat) && + doCast(me, GetSpell(REPLENISH_HEALTH_1))) + return; + } + + void CheckReplenishMana(uint32 diff) + { + if (ReplManacheckTimer > diff || !IsSpellReady(REPLENISH_MANA_1, diff) || IAmFree() || IsCasting() || + (HasRole(BOT_ROLE_DPS) && me->GetPower(POWER_MANA) > 0)) + return; + + ReplManacheckTimer = 1000; + + Group const* gr = master->GetGroup(); + if (!gr) + return; + + bool haveMana = false; + uint8 partycombat = 0, partynocombat = 0; + for (Unit const* member : BotMgr::GetAllGroupMembers(gr)) + { + if (me->GetMap() == member->FindMap()) + { + if (member->IsInCombat()) + partycombat++; + else if (member->IsAlive()) + partynocombat++; + + if (!haveMana && member->IsAlive() && me->GetDistance(member) < 15 && GetManaPCT(member) < 95 && + !(member->IsNPCBot() && member->ToCreature()->GetBotClass() == BOT_CLASS_SPHYNX)) + haveMana = true; + } + } + + if (haveMana && (!me->IsInCombat() || partycombat > partynocombat) && + doCast(me, GetSpell(REPLENISH_MANA_1))) + return; + } + + void ApplyClassDamageMultiplierSpell(int32& damage, SpellNonMeleeDamage& /*damageinfo*/, SpellInfo const* /*spellInfo*/, WeaponAttackType /*attackType*/, bool iscrit) const override + { + //uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //uint8 lvl = me->getLevel(); + float fdamage = float(damage); + + //apply bonus damage mods + float pctbonus = 1.0f; + if (iscrit) + pctbonus *= 1.333f; + + damage = int32(fdamage * pctbonus); + } + + void OnClassSpellGo(SpellInfo const* spellInfo) override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + + if (baseId == MAIN_ATTACK_1 || baseId == SPLASH_ATTACK_1 || baseId == DEVOUR_MAGIC_1 || + baseId == DRAIN_MANA_1 || baseId == REPLENISH_MANA_1 || baseId == REPLENISH_HEALTH_1) + GC_Timer = uint32(me->GetAttackTime(BASE_ATTACK) * me->m_modAttackSpeedPct[BASE_ATTACK]); + + if (baseId == SPLASH_ATTACK_1) + me->CastSpell(me, MH_ATTACK_ANIM, true); + else if (baseId == MAIN_ATTACK_1) + me->CastSpell(me, MH_OH_ATTACK_ANIM, true); + + if (baseId == DEVOUR_MAGIC_1) + { + me->CastSpell(me, MH_OH_ATTACK_ANIM, true); + + if (dispelsDealt > 0) + { + //gain 20% of max mana and 5% of max health for every dispel + int32 manaGain = me->GetMaxPower(POWER_MANA) / 5 * dispelsDealt; + uint32 healthGain = me->GetMaxHealth() / 20 * dispelsDealt; + + HealInfo hinfo(me, me, healthGain, spellInfo, spellInfo->GetSchoolMask()); + + me->EnergizeBySpell(me, DEVOUR_MAGIC_1, manaGain, POWER_MANA); + me->HealBySpell(hinfo); + + me->CastSpell(me, SPELL_DEVOUR_MAGIC_CASTER_IMPACT, true); + } + + dispelsDealt = 0; + } + + if (baseId == DRAIN_MANA_1) + me->CastSpell(me, MH_OH_ATTACK_ANIM, true); + + if (baseId == REPLENISH_MANA_1) + me->SendPlaySpellVisual(425); //arcane cast omni + if (baseId == REPLENISH_HEALTH_1) + me->SendPlaySpellVisual(21); //empty cast finish anim + + if (baseId == REPLENISH_MANA_1 || baseId == REPLENISH_HEALTH_1) + me->SetPower(POWER_MANA, 0); + } + + void SpellHitTarget(WorldObject* wtarget, SpellInfo const* spell) override + { + Unit* target = wtarget->ToUnit(); + if (!target) + return; + + uint32 spellId = spell->Id; + if (blastVisualTimer < GetLastDiff() && spellId == SPLASH_ATTACK_1) + { + blastVisualTimer = 500; + me->CastSpell(*target, SHADOWFURY_VISUAL, true); + } + //Devour Magic: damage to summons + if (spellId == DEVOUR_MAGIC_1 && target->IsSummon() && target->GetUInt32Value(UNIT_CREATED_BY_SPELL) && + !target->IsTotem() && me->GetReactionTo(target) <= REP_NEUTRAL) + { + SpellInfo const* devInfo = AssertBotSpellInfoOverride(spellId); + uint32 damage = std::min(target->GetMaxHealth() / 2, me->GetMaxHealth() / 5 + me->SpellBaseDamageBonusDone(SPELL_SCHOOL_MASK_MAGIC)); + Unit::DealDamage(me, target, damage, nullptr, SPELL_DIRECT_DAMAGE, devInfo->GetSchoolMask(), devInfo); + OnBotDispelDealt(target, 1); + } + + if (spellId == DRAIN_MANA_1) + { + me->CastSpell(target, SPELL_DEVOUR_MAGIC_BEAM, true); + target->SendPlaySpellVisual(419); //drain impact visual + } + if (spellId == REPLENISH_MANA_1) + if (target != me) + target->SendPlaySpellVisual(524/*436*/); //mana gain visual//heal bigger crimson ish + + OnSpellHitTarget(target, spell); + } + + void SpellHit(WorldObject* wcaster, SpellInfo const* spell) override + { + Unit* caster = wcaster->ToUnit(); + if (!caster) + return; + + OnSpellHit(caster, spell); + } + + void OnBotDispelDealt(Unit* dispelled, uint8 num) override + { + //cast drain visual on dispelled + if (me != dispelled) + { + me->CastSpell(dispelled, SPELL_DEVOUR_MAGIC_BEAM, true); + dispelled->SendPlaySpellVisual(357/*317*/); //purge visual + } + + dispelsDealt += num; + } + + void DamageDealt(Unit* victim, uint32& damage, DamageEffectType damageType) override + { + bot_ai::DamageDealt(victim, damage, damageType); + } + + void DamageTaken(Unit* u, uint32& damage, DamageEffectType /*damageType*/, SpellInfo const* /*spellInfo*/) override + { + if (damage) + { + dmgReceived += damage / 10; + uint32 energizeThreshold = me->GetMaxPower(POWER_MANA) / 10; + + int32 manaGain = 0; + while (dmgReceived >= energizeThreshold) + { + manaGain += energizeThreshold; + dmgReceived -= energizeThreshold; + } + if (manaGain) + me->EnergizeBySpell(me, SPELL_ENERGIZE, manaGain, POWER_MANA); + } + + if (!u) + return; + if (!u->IsInCombat() && !me->IsInCombat()) + return; + OnOwnerDamagedBy(u); + } + + void OwnerAttackedBy(Unit* u) override + { + if (!u) + return; + OnOwnerDamagedBy(u); + } + + void CheckAttackState() override + { + } + + void Reset() override + { + blastVisualTimer = 0; + DevourcheckTimer = 0; + DraincheckTimer = 0; + ReplManacheckTimer = 0; + ReplHealthcheckTimer = 0; + + dmgReceived = 0; + dispelsDealt = 0; + + DefaultInit(); + + me->SetPower(POWER_MANA, 0); + } + + void ReduceCD(uint32 diff) override + { + if (blastVisualTimer > diff) blastVisualTimer -= diff; + if (DevourcheckTimer > diff) DevourcheckTimer -= diff; + if (DraincheckTimer > diff) DraincheckTimer -= diff; + if (ReplManacheckTimer > diff) ReplManacheckTimer -= diff; + if (ReplHealthcheckTimer > diff) ReplHealthcheckTimer -= diff; + } + + void InitPowers() override + { + me->SetPowerType(POWER_MANA); + } + + void InitSpells() override + { + InitSpellMap(MAIN_ATTACK_1, true, false); + InitSpellMap(SPLASH_ATTACK_1, true, false); + InitSpellMap(DEVOUR_MAGIC_1, true, false); + InitSpellMap(DRAIN_MANA_1, true, false); + + InitSpellMap(REPLENISH_MANA_1, true, false); + InitSpellMap(REPLENISH_HEALTH_1, true, false); + } + + void ApplyClassPassives() const override + { + } + + bool CanUseManually(uint32 basespell) const override + { + switch (basespell) + { + case DEVOUR_MAGIC_1: + case DRAIN_MANA_1: + case REPLENISH_MANA_1: + case REPLENISH_HEALTH_1: + return true; + default: + return false; + } + } + + std::vector const* GetDamagingSpellsList() const override + { + return &Sphynx_spells_damage; + } + //std::vector const* GetCCSpellsList() const override + //{ + // return &Sphynx_spells_cc; + //} + std::vector const* GetHealingSpellsList() const override + { + return &Sphynx_spells_heal; + } + std::vector const* GetSupportSpellsList() const override + { + return &Sphynx_spells_support; + } + + private: + uint32 blastVisualTimer; + uint32 DevourcheckTimer, DraincheckTimer, ReplManacheckTimer, ReplHealthcheckTimer; + + uint32 dmgReceived; + uint8 dispelsDealt; + }; +}; + +void AddSC_sphynx_bot() +{ + new sphynx_bot(); +} diff --git a/src/server/game/AI/NpcBots/bot_warlock_ai.cpp b/src/server/game/AI/NpcBots/bot_warlock_ai.cpp new file mode 100644 index 000000000..81654c526 --- /dev/null +++ b/src/server/game/AI/NpcBots/bot_warlock_ai.cpp @@ -0,0 +1,2100 @@ +#include "bot_ai.h" +#include "botmgr.h" +#include "botspell.h" +#include "bottraits.h" +#include "Containers.h" +#include "Group.h" +#include "Log.h" +#include "Map.h" +#include "MotionMaster.h" +#include "Player.h" +#include "ScriptMgr.h" +#include "Spell.h" +#include "SpellAuraEffects.h" +#include "SpellMgr.h" +#include "TemporarySummon.h" +#include "World.h" +/* +Warlock NpcBot (reworked by Trickerer onlysuffering@gmail.com) +Complete - 85% +TODO: rituals (not directly feasable), demonic circle, demonic empowerment, demonic pact, health funnel, healthstones for bots, etc... +*/ + +enum WarlockBaseSpells +{ + CURSE_OF_WEAKNESS_1 = 702, + CURSE_OF_AGONY_1 = 980, + CURSE_OF_TONGUES_1 = 1714, + CURSE_OF_EXHAUSTION_1 = 18223, + CURSE_OF_THE_ELEMENTS_1 = 1490, + CURSE_OF_DOOM_1 = 603,//NI + SHADOW_BOLT_1 = 686, + IMMOLATE_1 = 348, + CORRUPTION_1 = 172, + SEED_OF_CORRUPTION_1 = 27243, + INCINERATE_1 = 29722, + SEARING_PAIN_1 = 5676, + SHADOWBURN_1 = 17877, + CONFLAGRATE_1 = 17962, + SOUL_FIRE_1 = 6353, + CHAOS_BOLT_1 = 50796, + RAIN_OF_FIRE_1 = 5740, + HELLFIRE_1 = 1949, + SHADOWFLAME_1 = 47897, + SHADOWFURY_1 = 30283, + HAUNT_1 = 48181, + UNSTABLE_AFFLICTION_1 = 30108, + FEAR_1 = 5782, + HOWL_OF_TERROR_1 = 5484, + DEATH_COIL_1 = 6789, + SOULSHATTER_1 = 29858, + + DRAIN_SOUL_1 = 1120, + DRAIN_MANA_1 = 5138, + BANISH_1 = 710, + + DEMON_SKIN_1 = 687, + DEMON_ARMOR_1 = 706, + FEL_ARMOR_1 = 28176, + DETECT_INVISIBILITY_1 = 132, + UNENDING_BREATH_1 = 5697, + SHADOW_WARD_1 = 6229, + LIFE_TAP_1 = 1454, + DARK_PACT_1 = 18220, + + CREATE_HEALTHSTONE_1 = 6201, + CREATE_SOULSTONE_1 = 693, + + CHAOTIC_MIND = 61188, //8 sec duration, no cd + + RITUAL_OF_SUMMONING_1 = 698, + RITUAL_OF_SOULS_1 = 29893 +}; +enum WarlockPassives +{ + IMPROVED_SHADOW_BOLT = 17803,//rank 5 + IMPROVED_DRAIN_SOUL = 18372,//rank 2 + SOUL_SIPHON = 17805,//rank 2 + AFTERMATH = 18120,//rank 2 + IMPROVED_FEAR = 53759,//rank 2 + NIGHTFALL = 18095,//rank 2 + SHADOW_EMBRACE = 32394,//rank 5 + SIPHON_LIFE = 63108, + BACKLASH = 34939,//rank 3 + MOLTEN_CORE = 47247,//rank 3 + NETHER_PROTECTION = 30302,//rank 3 + ERADICATION = 47197,//rank 3 + DEMONIC_RESILIENCE = 30321,//rank 3 + SOUL_LEECH = 30296,//rank 3 + PYROCLASM = 63245,//rank 3 + DECIMATION = 63158,//rank 2 + IMPROVED_SOUL_LEECH = 54118,//rank 2 + PANDEMIC = 58435, + BACKDRAFT = 47260,//rank 3 + EVERLASTING_AFFLICTION = 47205,//rank 5 + + //Special + GLYPH_CORRUPTION = 56218, + GLYPH_LIFE_TAP = 63320, + GLYPH_FEAR = 56244, + GLYPH_QUICK_DECAY = 70947, + GLYPH_CONFLAGRATE = 56235, + GLYPH_SHADOWFLAME = 63310 +}; + +enum WarlockSpecial +{ + CHAOS_BOLT_PASSIVE = 58284, + DEMONIC_IMMOLATE_PASSIVE = 75445, + + BACKLASH_BUFF = 34936, + BACKDRAFT_BUFF = 54277,//rank 3 + SHADOW_TRANCE_BUFF = 17941, + MOLTEN_CORE_BUFF = 71165,//rank 3 + DECIMATION_BUFF = 63167,//rank 2 + CHAOTIC_MIND_BUFF = 61189,// "Soul Fire!" 6 sec duration Soul Fire instant cast + GLYPH_LIFE_TAP_BUFF = 63321,//"Life Tap" + + SHADOW_MASTERY_DEBUFF = 17800,// Improved Shadow Bolt talent debuff + + SIPHON_LIFE_HEAL = 63106, + LIFE_TAP_ENERGIZE = 31818, + + SEED_OF_CORRUPTION_FINAL_DAMAGE_1 = 27285, + + SOULSTONE_RESURRECTION_1 = 20707, + SOULSTONE_RESURRECTION_2 = 20762, + SOULSTONE_RESURRECTION_3 = 20763, + SOULSTONE_RESURRECTION_4 = 20764, + SOULSTONE_RESURRECTION_5 = 20765, + SOULSTONE_RESURRECTION_6 = 27239, + SOULSTONE_RESURRECTION_7 = 47883, + + BLOOD_PACT_1 = 6307, + FEL_INTELLIGENCE_1 = 54424, + + SOUL_LINK_PET = 25228,//split effect lvl 20 req + FEL_SYNERGY_HEAL = 54181, + LIFE_TAP_ENERGIZE_PET = 32553 +}; + +enum CurseType : uint32 +{ + CURSE_NONE = 0, + CURSE_WEAKNESS = 1, + CURSE_AGONY = 2, + CURSE_DOOM = 3, + CURSE_ELEMENTS = 4, + CURSE_TONGUES = 5, + CURSE_EXHAUSTION = 6, + + CURSE_FLAG_MY_WEAKNESS, + CURSE_FLAG_MY_AGONY, + CURSE_FLAG_MY_DOOM, + CURSE_FLAG_MY_ELEMENTS, + CURSE_FLAG_MY_TONGUES, + CURSE_FLAG_MY_EXHAUSTION, + + CURSE_MASK_WEAKNESS = (1 << CURSE_WEAKNESS), + CURSE_MASK_AGONY = (1 << CURSE_AGONY), + CURSE_MASK_DOOM = (1 << CURSE_DOOM), + CURSE_MASK_ELEMENTS = (1 << CURSE_ELEMENTS), + CURSE_MASK_TONGUES = (1 << CURSE_TONGUES), + CURSE_MASK_EXHAUSTION = (1 << CURSE_EXHAUSTION), + + CURSE_MASK_MY_WEAKNESS = (1 << CURSE_FLAG_MY_WEAKNESS), + CURSE_MASK_MY_AGONY = (1 << CURSE_FLAG_MY_AGONY), + CURSE_MASK_MY_DOOM = (1 << CURSE_FLAG_MY_DOOM), + CURSE_MASK_MY_ELEMENTS = (1 << CURSE_FLAG_MY_ELEMENTS), + CURSE_MASK_MY_TONGUES = (1 << CURSE_FLAG_MY_TONGUES), + CURSE_MASK_MY_EXHAUSTION = (1 << CURSE_FLAG_MY_EXHAUSTION), + + CURSE_MASK_MY_CURSE_ANY = (CURSE_MASK_MY_WEAKNESS | CURSE_MASK_MY_AGONY | CURSE_MASK_MY_DOOM | \ + CURSE_MASK_MY_ELEMENTS | CURSE_MASK_MY_TONGUES | CURSE_MASK_MY_EXHAUSTION) +}; + +static const uint32 Warlock_spells_damage_arr[] = +{ CHAOS_BOLT_1, CONFLAGRATE_1, CORRUPTION_1, CURSE_OF_AGONY_1, CURSE_OF_DOOM_1, DEATH_COIL_1, DRAIN_SOUL_1, HAUNT_1, +HELLFIRE_1, IMMOLATE_1, INCINERATE_1, RAIN_OF_FIRE_1, SEARING_PAIN_1, SEED_OF_CORRUPTION_1, SHADOW_BOLT_1, +SHADOWBURN_1, SHADOWFLAME_1, SHADOWFURY_1, SOUL_FIRE_1, UNSTABLE_AFFLICTION_1 }; + +static const uint32 Warlock_spells_cc_arr[] = +{ BANISH_1, CURSE_OF_TONGUES_1, CURSE_OF_EXHAUSTION_1, DEATH_COIL_1, FEAR_1, HOWL_OF_TERROR_1, SHADOWFURY_1 }; + +static const uint32 Warlock_spells_support_arr[] = +{ CURSE_OF_TONGUES_1, CURSE_OF_EXHAUSTION_1, CURSE_OF_THE_ELEMENTS_1, CURSE_OF_WEAKNESS_1, DARK_PACT_1, DRAIN_MANA_1, +DEMON_SKIN_1, DEMON_ARMOR_1, DETECT_INVISIBILITY_1, FEL_ARMOR_1, LIFE_TAP_1, SHADOW_WARD_1, SOULSHATTER_1, +UNENDING_BREATH_1/*, CREATE_HEALTHSTONE_1, CREATE_SOULSTONE_1, RITUAL_OF_SUMMONING_1, RITUAL_OF_SOULS_1*/ }; + +static const std::vector Warlock_spells_damage(FROM_ARRAY(Warlock_spells_damage_arr)); +static const std::vector Warlock_spells_cc(FROM_ARRAY(Warlock_spells_cc_arr)); +static const std::vector Warlock_spells_support(FROM_ARRAY(Warlock_spells_support_arr)); + +class warlock_bot : public CreatureScript +{ +public: + warlock_bot() : CreatureScript("warlock_bot") { } + + CreatureAI* GetAI(Creature* creature) const override + { + return new warlock_botAI(creature); + } +/* + bool OnGossipHello(Player* player, Creature* creature) + { + return creature->GetBotAI()->OnGossipHello(player, 0); + } + + bool OnGossipSelect(Player* player, Creature* creature, uint32 sender, uint32 action) + { + if (bot_ai* ai = creature->GetBotAI()) + return ai->OnGossipSelect(player, creature, sender, action); + return true; + } + + bool OnGossipSelectCode(Player* player, Creature* creature, uint32 sender, uint32 action, char const* code) + { + if (bot_ai* ai = creature->GetBotAI()) + return ai->OnGossipSelectCode(player, creature, sender, action, code); + return true; + } +*/ + struct warlock_botAI : public bot_ai + { + static uint32 const _healthStoneSpells[8/*createHealthstoneRank*/]; + + warlock_botAI(Creature* creature) : bot_ai(creature) + { + _botclass = BOT_CLASS_WARLOCK; + + InitUnitFlags(); + } + + bool doCast(Unit* victim, uint32 spellId) + { + if (CheckBotCast(victim, spellId) != SPELL_CAST_OK) + return false; + return bot_ai::doCast(victim, spellId); + } + + void JustEnteredCombat(Unit* u) override { canShadowWard = false; bot_ai::JustEnteredCombat(u); } + void KilledUnit(Unit* u) override { bot_ai::KilledUnit(u); } + void EnterEvadeMode(EvadeReason why = EVADE_REASON_OTHER) override { bot_ai::EnterEvadeMode(why); } + void MoveInLineOfSight(Unit* u) override { bot_ai::MoveInLineOfSight(u); } + void JustDied(Unit* u) override { UnsummonAll(false); bot_ai::JustDied(u); } + void DoNonCombatActions(uint32 diff) + { + if (GC_Timer > diff || me->IsMounted() || IsCasting() || Feasting() || Rand() > 20) + return; + + if (GetSpell(FEL_ARMOR_1) && !IsTank()) + { + if (!me->GetAuraEffect(SPELL_AURA_MOD_SPELL_DAMAGE_OF_STAT_PERCENT, SPELLFAMILY_WARLOCK, 0x0, 0x20000000, 0x0) + /*!HasAuraName(me, FEL_ARMOR_1)*/ && + doCast(me, GetSpell(FEL_ARMOR_1))) + return; + } + else if (GetSpell(DEMON_ARMOR_1)) + { + if (!me->GetAuraEffect(SPELL_AURA_MOD_RESISTANCE, SPELLFAMILY_WARLOCK, 0x0, 0x20, 0x0) + /*!HasAuraName(me, DEMON_ARMOR_1)*/ && + doCast(me, GetSpell(DEMON_ARMOR_1))) + return; + } + else if (!GetSpell(FEL_ARMOR_1) && !GetSpell(DEMON_ARMOR_1) && GetSpell(DEMON_SKIN_1)) + { + if (!me->GetAuraEffect(SPELL_AURA_MOD_RESISTANCE, SPELLFAMILY_WARLOCK, 0x0, 0x0, 0x10) + /*!HasAuraName(me, DEMON_SKIN_1)*/ && + doCast(me, GetSpell(DEMON_SKIN_1))) + return; + } + + if (me->GetVictim()) + return; + + if (!hasHealthstone && GetSpell(CREATE_HEALTHSTONE_1)) + { + if (doCast(me, GetSpell(CREATE_HEALTHSTONE_1))) + return; + } + + if (!hasSoulstone && GetSpell(CREATE_SOULSTONE_1)) + { + if (doCast(me, GetSpell(CREATE_SOULSTONE_1))) + return; + } + + if (GetSpell(DETECT_INVISIBILITY_1)) + { + if (master->IsAlive() && !master->HasAuraType(SPELL_AURA_MOD_INVISIBILITY_DETECT) && + doCast(master, GetSpell(DETECT_INVISIBILITY_1))) + return; + } + + //TODO: soulstone on self/bots + //BUG: players cannot accept this buff if they are below lvl 20 (should be 8) + if (hasSoulstone && soulstoneTimer <= diff && GetSpell(CREATE_SOULSTONE_1)) + { + std::vector targets; + + if (!IAmFree()) + { + std::vector all_members = BotMgr::GetAllGroupMembers(master->GetGroup()); + for (uint8 i = 0; i < 3; ++i) + { + if (i > 0 && !targets.empty()) + break; + for (Unit* member : all_members) + { + if ((i >= 2 || (i == 0 ? member->IsPlayer() : member->IsNPCBot())) && me->GetMap() == member->FindMap() && + member->IsAlive() && !member->isPossessed() && !member->IsCharmed() && + !(member->IsNPCBot() && member->ToCreature()->IsTempBot()) && + me->GetDistance(member) < 30 && !member->GetDummyAuraEffect(SPELLFAMILY_GENERIC, 92, 0)) + { + if (i >= 2 || member->GetClass() == CLASS_PRIEST || member->GetClass() == CLASS_PALADIN || + member->GetClass() == CLASS_DRUID || member->GetClass() == CLASS_SHAMAN) + { + targets.push_back(member); + } + } + } + } + } + + if (targets.empty() && master->IsAlive() && !master->isPossessed() && !master->IsCharmed() && + me->GetDistance(master) < 30 && !master->GetDummyAuraEffect(SPELLFAMILY_GENERIC, 92, 0)) + targets.push_back(master); + + if (!targets.empty()) + { + Unit* target = targets.size() == 1 ? targets.front() : Trinity::Containers::SelectRandomContainerElement(targets); + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(CREATE_SOULSTONE_1); + uint32 rank = spellInfo->GetRank(); + + while (rank + 1 < std::size(_healthStoneSpells) && target->GetLevel() > spellInfo->SpellLevel && spellInfo->GetNextRankSpell()) + { + spellInfo = spellInfo->GetNextRankSpell(); + rank = spellInfo->GetRank(); + } + + uint32 spellId; + switch (spellInfo->Id) + { + case 693: spellId = SOULSTONE_RESURRECTION_1; break; //rank 1 + case 20752: spellId = SOULSTONE_RESURRECTION_2; break; //rank 2 + case 20755: spellId = SOULSTONE_RESURRECTION_3; break; //rank 3 + case 20756: spellId = SOULSTONE_RESURRECTION_4; break; //rank 4 + case 20757: spellId = SOULSTONE_RESURRECTION_5; break; //rank 5 + case 27238: spellId = SOULSTONE_RESURRECTION_6; break; //rank 6 + case 47884: spellId = SOULSTONE_RESURRECTION_7; break; //rank 7 + default: + TC_LOG_ERROR("entities.player", "bot_warlockAI: unknown soulstone Id {}", spellInfo->Id); + spellId = SOULSTONE_RESURRECTION_1; + break; + } + me->CastSpell(target, spellId, false); + } + } + } + + void StartAttack(Unit* u, bool force = false) + { + if (!bot_ai::StartAttack(u, force)) + return; + GetInPosition(force, u); + fearTimer = std::max(fearTimer, 1000); + } + + void CheckFear(uint32 diff) + { + if (fearTimer > diff || GC_Timer > diff || !me->IsInCombat() || IsTank() || IsCasting()) + return; + + fearTimer = 1600; + + uint32 FEAR = GetSpell(FEAR_1); + if (!FEAR) + return; + + if (FindAffectedTarget(FEAR, me->GetGUID(), 70, 255)) + return; + + Unit* feartarget = FindFearTarget(CalcSpellMaxRange(FEAR_1)); + if (feartarget && doCast(feartarget, FEAR)) + return; + } + + void CheckBanish(uint32 diff) + { + if (banishTimer > diff || GC_Timer > diff || !me->IsInCombat() || IsTank() || IsCasting() || Rand() > 50) + return; + + banishTimer = 1600; + + uint32 BANISH = GetSpell(BANISH_1); + if (!BANISH) + return; + + if (FindAffectedTarget(BANISH, me->GetGUID(), 70, 255)) + return; + + Unit* banishTarget = FindUndeadCCTarget(CalcSpellMaxRange(BANISH_1), BANISH_1); + if (banishTarget && doCast(banishTarget, BANISH)) + return; + } + + void CheckUnBanish(uint32 diff) + { + if (unbanishTimer > diff || GC_Timer > diff || me->GetVictim() || IsCasting() || Rand() > 30) + return; + + unbanishTimer = 2000; + + //we check only our spell rank which is enough in 99% cases + uint32 BANISH = GetSpell(BANISH_1); + if (!BANISH) + return; + + //looks like you cannot dispel other people's banish + Unit* unbanishTarget = FindAffectedTarget(BANISH, me->GetGUID(), CalcSpellMaxRange(BANISH_1)); + if (unbanishTarget && doCast(unbanishTarget, BANISH)) + return; + } + + void CheckDrainMana(uint32 diff) + { + if (drainManaTimer > diff || IsPotionReady() || !IsSpellReady(DRAIN_MANA_1, diff) || + !me->getAttackers().empty() || IsTank() || IsCasting() || GetManaPCT(me) > 25 || Rand() > 60) + return; + + drainManaTimer = 1000; + + //1 case: feared mana pot running around + Unit* drainTarget = FindDrainTarget(CalcSpellMaxRange(DRAIN_MANA_1)); + + if (!drainTarget) + { + Unit* u = me->GetVictim(); + if (u && u->GetPowerType() == POWER_MANA && u->GetMaxPower(POWER_MANA) >= me->GetMaxPower(POWER_MANA) && + GetManaPCT(u) >= 15 && me->GetDistance(u) < CalcSpellMaxRange(DRAIN_MANA_1)) + drainTarget = u; + } + + if (drainTarget && doCast(drainTarget, GetSpell(DRAIN_MANA_1))) + return; + } + + void CheckWard(uint32 diff) + { + if (!canShadowWard || (!me->IsInCombat() && !me->HasAuraType(SPELL_AURA_PERIODIC_DAMAGE)) || + !IsSpellReady(SHADOW_WARD_1, diff) || IsCasting()) + return; + + if (doCast(me, GetSpell(SHADOW_WARD_1))) + return; + } + + void CheckSoulShatter(uint32 diff) + { + if (!IsSpellReady(SOULSHATTER_1, diff) || me->getAttackers().empty() || Rand() < 80) + return; + + Unit* u = *(me->getAttackers().begin()); + if (u->GetThreatManager().GetThreatListSize() < 3 || u->GetThreatManager().GetThreat(me) < 100.f) + return; + + if (doCast(me, GetSpell(SOULSHATTER_1))) + return; + } + + void Counter(uint32 diff) + { + if (GC_Timer > diff || IsCasting() || Rand() > 25) + return; + + bool busyCasting = me->IsNonMeleeSpellCast(true,true,true); + + //Fear + if (!busyCasting && IsSpellReady(FEAR_1, diff)) + { + Unit* u = FindCastingTarget(CalcSpellMaxRange(FEAR_1), 0, FEAR_1); + if (u && doCast(u, GetSpell(FEAR_1))) + return; + } + //Howl of Terror (only instant cast) + if ((GetSpec() == BOT_SPEC_WARLOCK_AFFLICTION) && + !busyCasting && me->GetLevel() >= 45 && IsSpellReady(HOWL_OF_TERROR_1, diff)) + { + Unit* u = FindCastingTarget(8, 0, FEAR_1); //same immunity + if (u && doCast(u, GetSpell(HOWL_OF_TERROR_1))) + return; + } + //Shadowfury + if (HasRole(BOT_ROLE_DPS) && IsSpellReady(SHADOWFURY_1, diff)) + { + if (Unit* u = FindCastingTarget(CalcSpellMaxRange(SHADOWFURY_1), 0, SHADOWFURY_1)) + { + if (busyCasting) + me->InterruptNonMeleeSpells(true); + if (doCast(u, GetSpell(SHADOWFURY_1))) + return; + } + } + //Death Coil + if (HasRole(BOT_ROLE_DPS) && IsSpellReady(DEATH_COIL_1, diff)) + { + if (Unit* u = FindCastingTarget(CalcSpellMaxRange(DEATH_COIL_1), 0, DEATH_COIL_1)) + { + if (busyCasting) + me->InterruptNonMeleeSpells(true); + if (doCast(u, GetSpell(DEATH_COIL_1))) + return; + } + } + } + + void DoDefend(uint32 diff) + { + if (GC_Timer > diff || !me->IsInCombat() || Rand() > 120) + return; + + Unit::AttackerSet const& m_attackers = master->getAttackers(); + Unit::AttackerSet const& b_attackers = me->getAttackers(); + bool needFearM = !IAmFree() && !m_attackers.empty() && (!IsTank(master) || GetHealthPCT(master) < 75); + + //HOWL + //fear master's attackers + if (IsSpellReady(HOWL_OF_TERROR_1, diff)) + { + if (needFearM) + { + uint8 tCount = 0; + for (Unit::AttackerSet::const_iterator iter = m_attackers.begin(); iter != m_attackers.end(); ++iter) + { + if (!(*iter)) continue; + if (CCed(*iter, true)) continue; + if ((*iter)->ToCreature() && (*iter)->GetCreatureType() == CREATURE_TYPE_UNDEAD) continue; + if (me->GetDistance(*iter) > 9) continue; + if (me->IsValidAttackTarget(*iter)) + ++tCount; + } + if (tCount > 1 && doCast(me, GetSpell(HOWL_OF_TERROR_1))) + return; + } + // Defend myself + if (!b_attackers.empty()) + { + uint8 tCount = 0; + for (Unit::AttackerSet::const_iterator iter = b_attackers.begin(); iter != b_attackers.end(); ++iter) + { + if (!(*iter)) continue; + if (CCed(*iter, true)) continue; + if ((*iter)->ToCreature() && (*iter)->GetCreatureType() == CREATURE_TYPE_UNDEAD) continue; + if (me->GetDistance(*iter) > 9) continue; + if (me->IsValidAttackTarget(*iter)) + ++tCount; + } + if (tCount > 1 && doCast(me, GetSpell(HOWL_OF_TERROR_1))) + return; + } + } + //COIL + if (HasRole(BOT_ROLE_DPS) && IsSpellReady(DEATH_COIL_1, diff)) + { + Unit* u = needFearM ? *(m_attackers.begin()) : nullptr; + if (u && u->GetMaxHealth() > master->GetMaxHealth() * 2 && + u->GetDistance(me) < 30) + { + if (doCast(u, GetSpell(DEATH_COIL_1))) + return; + } + u = !b_attackers.empty() ? *(b_attackers.begin()) : nullptr; + if (u && u->GetMaxHealth() > me->GetMaxHealth() * 2 && u->GetDistance(me) < 8) + { + if (doCast(u, GetSpell(DEATH_COIL_1))) + return; + } + } + } + + bool BuffTarget(Unit* target, uint32 /*diff*/) override + { + if (target->GetTypeId() != TYPEID_PLAYER) return false; + if (me->IsInCombat() && !master->GetMap()->IsRaid()) return false; + + if (GetSpell(UNENDING_BREATH_1) && target->HasUnitMovementFlag(MOVEMENTFLAG_SWIMMING) && + !target->HasAuraType(SPELL_AURA_WATER_BREATHING)) + { + if (doCast(target, GetSpell(UNENDING_BREATH_1))) + return true; + } + + return false; + } + + void UpdateAI(uint32 diff) override + { + if (!GlobalUpdate(diff)) + return; + + DoVehicleActions(diff); + if (!CanBotAttackOnVehicle()) + return; + + //pet is killed or unreachable + if (GC_Timer <= diff && !me->IsInCombat() && !me->IsMounted() && !me->GetVictim() && !IsCasting() && Rand() < 25 && + (!botPet || me->GetDistance2d(botPet) > sWorld->GetMaxVisibleDistanceOnContinents())) + SummonBotPet(); + + //Hellfire interrupt + Spell const* spell = me->GetCurrentSpell(CURRENT_CHANNELED_SPELL); + if (spell && spell->GetSpellInfo()->GetFirstRankSpell()->Id == HELLFIRE_1 && + ((!IAmFree() && !master->GetBotMgr()->IsPartyInCombat()) || GetHealthPCT(me) < 25)) + me->InterruptSpell(CURRENT_CHANNELED_SPELL); + else + { + spell = me->GetCurrentSpell(CURRENT_GENERIC_SPELL); + SpellInfo const* baseSpellInfo = spell ? spell->GetSpellInfo()->GetFirstRankSpell() : nullptr; + uint32 base_id = baseSpellInfo ? baseSpellInfo->Id : 0; + if (baseSpellInfo && (base_id == FEAR_1 || base_id == BANISH_1 || baseSpellInfo->SpellVisual[0] == 99)) + { + if (Unit const* target = ObjectAccessor::GetUnit(*me, spell->m_targets.GetObjectTargetGUID())) + { + //Fear interrupt + if (base_id == FEAR_1 && target->HasAuraType(SPELL_AURA_MOD_FEAR)) + me->InterruptSpell(CURRENT_GENERIC_SPELL); + //Banish interrupt + else if (base_id == BANISH_1) + { + if (AuraEffect const* bani = target->GetAuraEffect(SPELL_AURA_SCHOOL_IMMUNITY, SPELLFAMILY_WARLOCK, 0x0, 0x8000000, 0x0)) + { + //Already banished + //check spell cast time + if (bani->GetBase()->GetDuration() > bani->GetBase()->GetMaxDuration() - 1500) + me->InterruptSpell(CURRENT_GENERIC_SPELL); + } + else if (!target->getAttackers().empty()) + me->InterruptSpell(CURRENT_GENERIC_SPELL); + } + //Soulstone resurrection interrupt + else if (spell->GetSpellInfo()->SpellVisual[0] == 99 && target->GetDummyAuraEffect(SPELLFAMILY_GENERIC, 92, 0)) + me->InterruptSpell(CURRENT_GENERIC_SPELL); + } + } + } + + if (hasHealthstone && healthstoneTimer <= diff && + /*GetSpell(CREATE_HEALTHSTONE_1) && */!IsCasting() && GetHealthPCT(me) < 65) + { + uint32 healthStone = InitSpell(me, CREATE_HEALTHSTONE_1); + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(healthStone); + //ASSERT(spellInfo); + uint32 rank = spellInfo ? spellInfo->GetRank() : 1; + //ASSERT(rank >= 1 && rank <= 8); + spellInfo = sSpellMgr->GetSpellInfo(_healthStoneSpells[rank - 1]); + ASSERT(spellInfo); + int32 healing = spellInfo->_effects[0].BasePoints; + //Glyph of Healthstone + if (me->GetLevel() >= 15) + healing = int32(float(healing) * 1.3f); + CastSpellExtraArgs args(true); + args.AddSpellBP0(healing); + me->CastSpell(me, _healthStoneSpells[rank - 1], args); + healthstoneTimer = 120000; //2 min + hasHealthstone = false; + return; + } + else if (IsPotionReady() && GetHealthPCT(me) < 50) + { + DrinkPotion(false); + } + + if (IsSpellReady(DARK_PACT_1, diff) && !IsCasting() && botPet && botPet->GetPower(POWER_MANA) >= 300 && + GetManaPCT(me) < 20) + { + if (doCast(me, GetSpell(DARK_PACT_1))) + return; + } + else if (IsSpellReady(LIFE_TAP_1, diff) && !IsCasting() && GetHealthPCT(me) > (me->IsInCombat() ? 30 : 15) && + GetManaPCT(me) < 15 && Rand() < 50) + { + //it is possible that CheckCast will return SPELL_FAILED_NO_POWER if not enough hp + if (doCast(me, GetSpell(LIFE_TAP_1))) + return; + } + else if (IsPotionReady() && GetManaPCT(me) < 10) + { + DrinkPotion(true); + } + + CheckRacials(diff); + + CheckSoulShatter(diff); + DoDefend(diff); + CheckFear(diff); + CheckWard(diff); + + BuffAndHealGroup(diff); + + if (!me->IsInCombat()) + DoNonCombatActions(diff); + + Counter(diff); + + CheckBanish(diff); + CheckUnBanish(diff); + + CheckDrainMana(diff); + + if (ProcessImmediateNonAttackTarget()) + return; + + if (!CheckAttackTarget()) + return; + + if (IsCasting()) + return; + + CheckUsableItems(diff); + + DoNormalAttack(diff); + } + + void DoNormalAttack(uint32 diff) + { + Unit* mytar = opponent ? opponent : disttarget ? disttarget : nullptr; + if (!mytar) + return; + + StartAttack(mytar, IsMelee()); + + CheckAttackState(); + if (!me->IsAlive() || !mytar->IsAlive()) + return; + + MoveBehind(mytar); + + if (GC_Timer > diff) + return; + + auto [can_do_shadow, can_do_fire] = CanAffectVictimBools(mytar, SPELL_SCHOOL_SHADOW, SPELL_SCHOOL_FIRE); + + float dist = me->GetDistance(mytar); + + //spell reflections + if (IsSpellReady(CURSE_OF_THE_ELEMENTS_1, diff) && can_do_shadow && dist < CalcSpellMaxRange(CURSE_OF_THE_ELEMENTS_1) && + CanRemoveReflectSpells(mytar, CURSE_OF_THE_ELEMENTS_1) && + doCast(mytar, CURSE_OF_THE_ELEMENTS_1)) + return; + else if (IsSpellReady(CURSE_OF_WEAKNESS_1, diff) && can_do_shadow && dist < CalcSpellMaxRange(CURSE_OF_WEAKNESS_1) && + CanRemoveReflectSpells(mytar, CURSE_OF_WEAKNESS_1) && + doCast(mytar, CURSE_OF_WEAKNESS_1)) + return; + + //Offensive heal (Death Coil) + if (IsSpellReady(DEATH_COIL_1, diff) && can_do_shadow && HasRole(BOT_ROLE_DPS) && dist < CalcSpellMaxRange(DEATH_COIL_1) && + GetHealthPCT(me) < 35) + { + //if (me->IsNonMeleeSpellCast(true)) + // me->InterruptNonMeleeSpells(true); + if (doCast(mytar, GetSpell(DEATH_COIL_1))) + return; + } + + //Life Tap / Dark Pact for Glyph of Life Tap + if (lifeTapCheckTimer <= diff && HasRole(BOT_ROLE_DPS) && Rand() < 75) + { + lifeTapCheckTimer = 10000; + if (me->GetLevel() >= 15 && !me->GetAuraEffect(SPELL_AURA_MOD_SPELL_DAMAGE_OF_STAT_PERCENT, SPELLFAMILY_WARLOCK, 208, 0)) + { + //doesn't work: wrong spell proc entry 10.12.2020 + //if (IsSpellReady(DARK_PACT_1, diff) && botPet && GetManaPCT(me) > 70) + //{ + // if (doCast(me, GetSpell(DARK_PACT_1))) + // return; + //} + //else + if (IsSpellReady(LIFE_TAP_1, diff) && GetHealthPCT(me) > 30) + { + if (doCast(me, GetSpell(LIFE_TAP_1))) + return; + } + } + } + //Shadowfury + if (IsSpellReady(SHADOWFURY_1, diff) && can_do_shadow && HasRole(BOT_ROLE_DPS) && !CCed(mytar, true) && Rand() < 55) + { + if (FindSplashTarget(CalcSpellMaxRange(SHADOWFURY_1)) && + doCast(mytar, GetSpell(SHADOWFURY_1))) + return; + } + //Hellfire + if (IsSpellReady(HELLFIRE_1, diff) && HasRole(BOT_ROLE_DPS) && !IAmFree() && !JumpingOrFalling() && + GetHealthPCT(me) > 90 && Rand() < 25) + { + std::list targets; + GetNearbyTargetsList(targets, 12.f, 0); + for (std::list::iterator itr = targets.begin(); itr != targets.end();) + { + Unit* u = *itr; + bool erase = false; + if (u->isMoving()) + { + if (me->GetDistance(u) > 10.f && !u->HasInArc(float(M_PI)/2, me)) + erase = true; + } + else if (me->GetDistance(u) > 9.5f) + erase = true; + + if (erase) + { + targets.erase(itr++); + continue; + } + ++itr; + } + if (targets.size() >= 4 && doCast(me, GetSpell(HELLFIRE_1))) + return; + } + //Rain of Fire + if (IsSpellReady(RAIN_OF_FIRE_1, diff) && HasRole(BOT_ROLE_DPS) && !JumpingOrFalling() && Rand() < 45 && + (GetSpec() != BOT_SPEC_WARLOCK_AFFLICTION || !GetSpell(SEED_OF_CORRUPTION_1))) + { + if (Unit* raintarget = FindAOETarget(CalcSpellMaxRange(RAIN_OF_FIRE_1))) + { + if (doCast(raintarget, GetSpell(RAIN_OF_FIRE_1))) + return; + } + } + //Searing Pain (PvP) + if (longCasted && IsSpellReady(SEARING_PAIN_1, diff) && can_do_fire && HasRole(BOT_ROLE_DPS) && + GetSpec() != BOT_SPEC_WARLOCK_AFFLICTION && + mytar->GetTypeId() == TYPEID_PLAYER && Rand() < 35 && dist < CalcSpellMaxRange(SEARING_PAIN_1)) + { + if (doCast(mytar, GetSpell(SEARING_PAIN_1))) + return; + } + //Shadowflame + if (longCasted && IsSpellReady(SHADOWFLAME_1, diff) && can_do_shadow && HasRole(BOT_ROLE_DPS) && Rand() < 65) + { + std::list targets; + GetNearbyTargetsInConeList(targets, 8); //radius 10 yd + if (!targets.empty() && doCast(me, GetSpell(SHADOWFLAME_1))) + return; + } + //Curse, checking affliction range + if (curseCheckTimer <= diff && can_do_shadow && GetSpellCooldown(CURSE_OF_WEAKNESS_1) <= diff && Rand() < 85 && + dist < CalcSpellMaxRange(CURSE_OF_WEAKNESS_1) && mytar->GetHealth() > me->GetMaxHealth() / 4) + { + curseCheckTimer = 2500; + uint32 curses = _getCursesMask(mytar); + if (!(curses & CURSE_MASK_MY_CURSE_ANY)) + { + if (!(curses & CURSE_MASK_ELEMENTS) && GetSpell(CURSE_OF_THE_ELEMENTS_1) && !IAmFree() && + (GetSpec() != BOT_SPEC_WARLOCK_AFFLICTION || Rand() < 33) && + master->GetGroup() && master->GetGroup()->GetMembersCount() > 2) + { + if (doCast(mytar, GetSpell(CURSE_OF_THE_ELEMENTS_1))) + return; + } + if (!(curses & CURSE_MASK_MY_AGONY) && GetSpell(CURSE_OF_AGONY_1) && HasRole(BOT_ROLE_DPS) && + mytar->GetHealth() > me->GetMaxHealth() / 4 * (1 + mytar->getAttackers().size())) + { + if (doCast(mytar, GetSpell(CURSE_OF_AGONY_1))) + return; + } + if (!(curses & CURSE_MASK_TONGUES) && GetSpell(CURSE_OF_TONGUES_1) && mytar->GetHealth() > me->GetMaxHealth() / 2 && + mytar->IsNonMeleeSpellCast(false, false, true)) + { + if (doCast(mytar, GetSpell(CURSE_OF_TONGUES_1))) + return; + } + if (!(curses & CURSE_MASK_EXHAUSTION) && GetSpell(CURSE_OF_EXHAUSTION_1) && !CCed(mytar, true) && + mytar->IsControlledByPlayer() && !mytar->HasAuraWithMechanic(1<GetMap()->IsDungeon() && + mytar->GetMaxHealth() > me->GetMaxHealth() * 2) + { + if (doCast(mytar, GetSpell(CURSE_OF_WEAKNESS_1))) + return; + } + } + } + + if (!HasRole(BOT_ROLE_DPS)) + return; + + //Chaos Bolt + if (IsSpellReady(CHAOS_BOLT_1, diff) && can_do_fire && dist < CalcSpellMaxRange(CHAOS_BOLT_1)) + { + if (doCast(mytar, GetSpell(CHAOS_BOLT_1))) + return; + } + //Soul Fire 1 + if (IsSpellReady(SOUL_FIRE_1, diff) && can_do_fire && Rand() < 150 && dist < CalcSpellMaxRange(SOUL_FIRE_1) && + (mytar->IsPolymorphed() || me->HasAuraTypeWithAffectMask(SPELL_AURA_NO_REAGENT_USE, sSpellMgr->GetSpellInfo(SOUL_FIRE_1)))) + { + if (doCast(mytar, GetSpell(SOUL_FIRE_1))) + return; + } + //Conflagrate (always glyphed, does not consume dot) + if (longCasted && IsSpellReady(CONFLAGRATE_1, diff) && can_do_fire && dist < CalcSpellMaxRange(CONFLAGRATE_1) && + mytar->HasAuraState(AURA_STATE_CONFLAGRATE) && + mytar->GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_WARLOCK, 0x4, 0x0, 0x0, me->GetGUID())) + { + if (doCast(mytar, GetSpell(CONFLAGRATE_1))) + return; + } + //Shadowburn + if (longCasted && IsSpellReady(SHADOWBURN_1, diff) && can_do_shadow && dist < CalcSpellMaxRange(SHADOWBURN_1) && + mytar->HasAuraState(AURA_STATE_HEALTHLESS_35_PERCENT)) + { + if (doCast(mytar, GetSpell(SHADOWBURN_1))) + return; + } + //Immolate + if (IsSpellReady(IMMOLATE_1, diff) && can_do_fire && Rand() < 85 && dist < CalcSpellMaxRange(IMMOLATE_1) && + (GetSpec() != BOT_SPEC_WARLOCK_AFFLICTION || !GetSpell(UNSTABLE_AFFLICTION_1)) && + (GetSpell(CONFLAGRATE_1) || mytar->GetHealth() > me->GetMaxHealth()/4 * (1 + mytar->getAttackers().size())) && + !mytar->GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_WARLOCK, 0x4, 0x0, 0x0, me->GetGUID())) + { + if (doCast(mytar, GetSpell(IMMOLATE_1))) + return; + } + //Haunt + if (IsSpellReady(HAUNT_1, diff) && can_do_shadow && Rand() < 125 && dist < CalcSpellMaxRange(HAUNT_1) && + mytar->GetHealth() > me->GetMaxHealth()/4 * (1 + mytar->getAttackers().size()) && + !mytar->GetAuraEffect(SPELL_AURA_MOD_DAMAGE_FROM_CASTER, SPELLFAMILY_WARLOCK, 0x0, 0x40000, 0x0, me->GetGUID())) + { + if (doCast(mytar, GetSpell(HAUNT_1))) + return; + } + //Unstable Affliction + if (IsSpellReady(UNSTABLE_AFFLICTION_1, diff) && can_do_shadow && Rand() < 115 && dist < CalcSpellMaxRange(UNSTABLE_AFFLICTION_1) && + mytar->GetHealth() > me->GetMaxHealth()/4 * (1 + mytar->getAttackers().size()) && + !mytar->GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_WARLOCK, 0x0, 0x100, 0x0, me->GetGUID())) + { + if (doCast(mytar, GetSpell(UNSTABLE_AFFLICTION_1))) + return; + } + //Seed of Corruption + if (IsSpellReady(SEED_OF_CORRUPTION_1, diff) && Rand() < 85) + { + Unit* target = FindAOETarget(CalcSpellMaxRange(SEED_OF_CORRUPTION_1)); + if (target && !target->GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_WARLOCK, 0x0, 0x10, 0x0, me->GetGUID())) + { + if (doCast(target, GetSpell(SEED_OF_CORRUPTION_1))) + return; + } + + SetSpellCooldown(SEED_OF_CORRUPTION_1, 1000); //fail + } + //Corruption + if (IsSpellReady(CORRUPTION_1, diff) && can_do_shadow && Rand() < 90 && dist < CalcSpellMaxRange(CORRUPTION_1) && + mytar->GetHealth() > me->GetMaxHealth()/4 * (1 + mytar->getAttackers().size()) && + !mytar->GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_WARLOCK, 0x2, 0x0, 0x0, me->GetGUID()) &&//corruption + !mytar->GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_WARLOCK, 0x0, 0x10, 0x0, me->GetGUID()))//seed of corruption + { + if (doCast(mytar, GetSpell(CORRUPTION_1))) + return; + } + //Drain Soul: only if can quad damage + if (IsSpellReady(DRAIN_SOUL_1, diff) && can_do_shadow && mytar->GetTypeId() == TYPEID_UNIT && + Rand() < (50 + 85 * me->GetMap()->IsDungeon()) && GetHealthPCT(mytar) < 25 && + mytar->GetHealth() > me->GetMaxHealth() / 2 && dist < CalcSpellMaxRange(DRAIN_SOUL_1)) + { + if (doCast(mytar, GetSpell(DRAIN_SOUL_1))) + return; + } + //Soul Fire (conditional) + if (IsSpellReady(SOUL_FIRE_1, diff) && can_do_fire && Rand() < 90 && dist < CalcSpellMaxRange(SOUL_FIRE_1) && + mytar->GetHealth() > me->GetMaxHealth()/8 * (1 + mytar->getAttackers().size()) && me->HasAura(BACKDRAFT_BUFF)) + { + if (doCast(mytar, GetSpell(SOUL_FIRE_1))) + return; + } + //Main: Shadow Bolt, Incinerate, Searing Pain (tank), checking destruction range + if (dist < CalcSpellMaxRange(SHADOW_BOLT_1)) + { + uint32 boltinerate = + IsTank() && GetSpell(SEARING_PAIN_1) ? SEARING_PAIN_1 : + GetSpell(SHADOW_BOLT_1) && GetSpec() == BOT_SPEC_WARLOCK_AFFLICTION ? SHADOW_BOLT_1 : + GetSpell(INCINERATE_1) && mytar->HasAuraState(AURA_STATE_CONFLAGRATE) ? + //mytar->GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_WARLOCK, 0x4, 0x0, 0x0) && + //mytar->GetAuraEffect(SPELL_AURA_MOD_ATTACKER_SPELL_CRIT_CHANCE, SPELLFAMILY_WARLOCK, 213, 0) && + //(me->GetMap()->IsRaid() || !me->HasAura(SHADOW_TRANCE_BUFF)) ? + INCINERATE_1 : SHADOW_BOLT_1; + + bool can_cast_boltinerate; + switch (boltinerate) + { + case SEARING_PAIN_1: case INCINERATE_1: + can_cast_boltinerate = can_do_fire; + break; + case SHADOW_BOLT_1: + can_cast_boltinerate = can_do_shadow; + break; + default: + can_cast_boltinerate = true; + break; + } + + if (boltinerate && can_cast_boltinerate && doCast(mytar, GetSpell(boltinerate))) + return; + } + + if (Spell const* shot = me->GetCurrentSpell(CURRENT_AUTOREPEAT_SPELL)) + { + if (shot->GetSpellInfo()->Id == SHOOT_WAND && shot->m_targets.GetUnitTarget() != mytar) + me->InterruptSpell(CURRENT_AUTOREPEAT_SPELL); + } + else if (IsSpellReady(SHOOT_WAND, diff) && !me->isMoving() && me->GetDistance(mytar) < 30 && GetEquips(BOT_SLOT_RANGED) && + doCast(mytar, SHOOT_WAND)) + return; + } + + void ApplyClassSpellCritMultiplierAll(Unit const* victim, float& crit_chance, SpellInfo const* spellInfo, SpellSchoolMask /*schoolMask*/, WeaponAttackType /*attackType*/) const override + { + //victim can be NULL + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + uint8 lvl = me->GetLevel(); + + //Molten Core part 2.2: 15% additional critical chance for Soul Fire + if (lvl >= 35 && baseId == SOUL_FIRE_1) + { + moltencore = me->HasAura(MOLTEN_CORE_BUFF); + if (moltencore) + crit_chance += 15.f; + } + + //Devastation: 5% additional critical chance for Destruction spells + if ((GetSpec() == BOT_SPEC_WARLOCK_DESTRUCTION) && + lvl >= 30 && spellInfo->SpellFamilyName == SPELLFAMILY_WARLOCK && + ((spellInfo->SpellFamilyFlags[0] & 0x3E5) || (spellInfo->SpellFamilyFlags[1] & 0x8310C0))) + crit_chance += 5.f; + //Fire and Brimstone part 2: 25% additional critical chance for Conflagrate + if ((GetSpec() == BOT_SPEC_WARLOCK_DESTRUCTION) && lvl >= 55 && baseId == CONFLAGRATE_1) + crit_chance += 25.f; + //Malediction part 2: 9% additional critical chance for Corruption and Unstable Affliction + if ((GetSpec() == BOT_SPEC_WARLOCK_AFFLICTION) && + lvl >= 45 && (baseId == CORRUPTION_1 || baseId == UNSTABLE_AFFLICTION_1)) + crit_chance += 9.f; + //Glyph of Shadowburn: 20% additional critical chance for Shadowburn on targets 35% hp and below + if (lvl >= 20 && baseId == SHADOWBURN_1 && victim && victim->HasAuraState(AURA_STATE_HEALTHLESS_35_PERCENT)) + crit_chance += 20.f; + //Improved Corruption part 2: 5% additional critical chance for Seed of Corruption + if (lvl >= 10 && (baseId == SEED_OF_CORRUPTION_1 || baseId == SEED_OF_CORRUPTION_FINAL_DAMAGE_1)) + crit_chance += 5.f; + //Improved Searing Pain: 10% additional critical chance for Searing Pain + if ((GetSpec() == BOT_SPEC_WARLOCK_DESTRUCTION) && lvl >= 25 && baseId == SEARING_PAIN_1) + crit_chance += 10.f; + + //Master Demonologist part 1.2 (me): 5% additional critical chance for Fire spells + if ((GetSpec() == BOT_SPEC_WARLOCK_DEMONOLOGY) && + lvl >= 35 && botPet && myPetType == BOT_PET_IMP && (spellInfo->GetSchoolMask() & SPELL_SCHOOL_MASK_FIRE)) + crit_chance += 5.f; + //Master Demonologist part 3.2 (me): 5% additional critical chance for Shadow spells + if ((GetSpec() == BOT_SPEC_WARLOCK_DEMONOLOGY) && + lvl >= 35 && botPet && myPetType == BOT_PET_SUCCUBUS && (spellInfo->GetSchoolMask() & SPELL_SCHOOL_MASK_SHADOW)) + crit_chance += 5.f; + + //Warlock T84P Bonus (64932): 5% additional critical chance for Shadow Bolt and Incinerate + if (lvl >= 80 && (baseId == SHADOW_BOLT_1 || baseId == INCINERATE_1)) + crit_chance += 5.f; + } + + void ApplyClassDamageMultiplierSpell(int32& damage, SpellNonMeleeDamage& damageinfo, SpellInfo const* spellInfo, WeaponAttackType /*attackType*/, bool iscrit) const override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + uint8 lvl = me->GetLevel(); + float fdamage = float(damage); + + //2) apply bonus damage mods + float pctbonus = 1.0f; //Special condition + if (iscrit) + { + //!!!spell damage is not yet critical and will be multiplied by 1.5 + //so we should put here bonus damage mult /1.5 + //Ruin: 50% additional crit damage bonus for Destruction spells + if (lvl >= 20 && spellInfo->SpellFamilyName == SPELLFAMILY_WARLOCK && + ((spellInfo->SpellFamilyFlags[0] & 0x13E5) || (spellInfo->SpellFamilyFlags[1] & 0xC310C0))) + pctbonus += 0.333f; + //Pandemic part 2,3: crit damage for periodics and haunt + if ((GetSpec() == BOT_SPEC_WARLOCK_AFFLICTION) && + lvl >= 50 && spellInfo->SpellFamilyName == SPELLFAMILY_WARLOCK && + ((spellInfo->SpellFamilyFlags[0] & 0x2) || (spellInfo->SpellFamilyFlags[1] & 0x40100))) + pctbonus += 0.333f; + //Glyph of Searing Pain: 20% additional crit damage bonus for Searing Pain + if (lvl >= 18 && baseId == SEARING_PAIN_1) + pctbonus += 0.133f; + } + //Improved Shadow Bolt and Incinerate (38393): 6% bonus damage for Shadow bolt and Incinerate + if (baseId == SHADOW_BOLT_1 || baseId == INCINERATE_1) + pctbonus += 0.06f; + //Glyph of Incinerate: 5% bonus damage for Incinerate + if (baseId == INCINERATE_1) + pctbonus += 0.05f; + //Improved Immolate: 30% bonus damage for Immolate + if ((GetSpec() == BOT_SPEC_WARLOCK_DESTRUCTION) && lvl >= 30 && baseId == IMMOLATE_1) + pctbonus += 0.3f; + //EmberStorm part 1: 15% bonus damage for Fire spells + if ((GetSpec() == BOT_SPEC_WARLOCK_DESTRUCTION) && + lvl >= 35 && spellInfo->SpellFamilyName == SPELLFAMILY_WARLOCK && + ((spellInfo->SpellFamilyFlags[0] & 0x364) || (spellInfo->SpellFamilyFlags[1] & 0x8200C0))) + pctbonus += 0.15f; + //Fire and Brimstone part 1: 10% bonus damage for Incinerate and Chaos Bolt + if ((GetSpec() == BOT_SPEC_WARLOCK_DESTRUCTION) && + lvl >= 55 && (baseId == INCINERATE_1 || baseId == CHAOS_BOLT_1) && + damageinfo.target->GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_WARLOCK, 0x4, 0x0, 0x0, me->GetGUID())) + pctbonus += 0.1f; + //Molten Core part 1: 18% bonus damage for Incinerate and Soul Fire + if ((GetSpec() == BOT_SPEC_WARLOCK_DESTRUCTION) && + lvl >= 35 && (baseId == INCINERATE_1 || baseId == SOUL_FIRE_1)) + { + if (me->HasAura(MOLTEN_CORE_BUFF)) + pctbonus += 0.18f; + } + //Improved Corruption part 1: 10% bonus damage for Corruption + if (lvl >= 10 && baseId == CORRUPTION_1) + pctbonus += 0.1f; + //Corruption (28829): 12% bonus damage for Corruption + if (lvl >= 40 && baseId == CORRUPTION_1) + pctbonus += 0.12f; + //Malediction part 1: 3% bonus damage for All spells + if ((GetSpec() == BOT_SPEC_WARLOCK_AFFLICTION) && lvl >= 45) + pctbonus += 0.03f; + //Death's Embrace part 2: 12% bonus damage for Shadow spells on targets below 35 pct health + if ((GetSpec() == BOT_SPEC_WARLOCK_AFFLICTION) && + lvl >= 50 && damageinfo.target->HasAuraState(AURA_STATE_HEALTHLESS_35_PERCENT) && + ((spellInfo->SpellFamilyFlags[0] & 0x8248B) || (spellInfo->SpellFamilyFlags[1] & 0x59913))) + pctbonus += 0.12f; + + //Empowered Corruption: 36% spellpower bonus for Corruption + if (lvl >= 25 && baseId == CORRUPTION_1) + fdamage += me->SpellBaseDamageBonusDone(SPELL_SCHOOL_MASK_MAGIC) * 0.36f * me->CalculateDefaultCoefficient(spellInfo, DOT) * me->CalculateSpellpowerCoefficientLevelPenalty(spellInfo); + //Shadow and Flame: 20% spellpower bonus for Shadow Bolt, Shadowburn, Chaos Bolt and Incineration + if ((GetSpec() == BOT_SPEC_WARLOCK_DESTRUCTION) && lvl >= 45 && + (baseId == SHADOW_BOLT_1 || baseId == CHAOS_BOLT_1 || baseId == SHADOWBURN_1 || baseId == INCINERATE_1)) + fdamage += me->SpellBaseDamageBonusDone(SPELL_SCHOOL_MASK_MAGIC) * 0.2f * me->CalculateDefaultCoefficient(spellInfo, SPELL_DIRECT_DAMAGE) * me->CalculateSpellpowerCoefficientLevelPenalty(spellInfo); + //Everlasting Affliction part 1: 5% spellpower bonus for Corruption and Unstable Affliction + if ((GetSpec() == BOT_SPEC_WARLOCK_AFFLICTION) && + lvl >= 55 && (baseId == CORRUPTION_1 || baseId == UNSTABLE_AFFLICTION_1)) + fdamage += me->SpellBaseDamageBonusDone(SPELL_SCHOOL_MASK_MAGIC) * 0.05f * me->CalculateDefaultCoefficient(spellInfo, DOT) * me->CalculateSpellpowerCoefficientLevelPenalty(spellInfo); + + //Firestone/Spellstone: 1% bonus damage for all spells + if (lvl >= 28) + pctbonus += 0.01f; + + //Improved Shadow Bolt part 1: 10% bonus damage for Shadow Bolt + if (lvl >= 10 && baseId == SHADOW_BOLT_1) + pctbonus += 0.1f; + //Improved Corruption and Immolate (Updated) (61992): 5% bonus damage for Corruption and Immolate + if (lvl >= 10 && (baseId == CORRUPTION_1 || baseId == IMMOLATE_1)) + pctbonus += 0.05f; + //Improved Curse of Agony: 10% bonus damage for Curse of Agony + if (lvl >= 10 && baseId == CURSE_OF_AGONY_1) + pctbonus += 0.1f; + //Shadow Mastery: 15% bonus damage for Shadow Spells + if ((GetSpec() == BOT_SPEC_WARLOCK_AFFLICTION) && + lvl >= 35 && ((spellInfo->SpellFamilyFlags[0] & 0x80091) || spellInfo->SpellFamilyFlags[1] & 0x451910)) + pctbonus += 0.15f; + //Contagion: 5% bonus damage for Curse of Agony, Corruption and Seed of Corruption + if ((GetSpec() == BOT_SPEC_WARLOCK_AFFLICTION) && + lvl >= 40 && (baseId == CORRUPTION_1 || baseId == SEED_OF_CORRUPTION_1 || + baseId == SEED_OF_CORRUPTION_FINAL_DAMAGE_1 || baseId == CURSE_OF_AGONY_1)) + pctbonus += 0.05f; + + //Warlock T82P Bonus (64931): 20/10% bonus damage for Unstable Affliction and Immolate + if (lvl >= 80 && baseId == UNSTABLE_AFFLICTION_1) + pctbonus += 0.2f; + if (lvl >= 80 && baseId == IMMOLATE_1) + pctbonus += 0.1f; + //Warlock T94P Bonus (67231): 10% bonus damage for Immolate, Corruption and Unstable Affliction + if (lvl >= 80 && (baseId == IMMOLATE_1 || baseId == CORRUPTION_1 || baseId == UNSTABLE_AFFLICTION_1)) + pctbonus += 0.2f; + + //Glyph of Immolate: 10% bonus damage for Immolate + if (lvl >= 15 && baseId == IMMOLATE_1) + pctbonus += 0.1f; + + //Demonic Pact part 1: 10% bonus damage for all spells + if ((GetSpec() == BOT_SPEC_WARLOCK_DEMONOLOGY) && lvl >= 55) + pctbonus *= 1.1f; + //Master Demonologist part 1.1 (me): 5% bonus damage for Fire spells + if ((GetSpec() == BOT_SPEC_WARLOCK_DEMONOLOGY) && + lvl >= 35 && botPet && myPetType == BOT_PET_IMP && (spellInfo->GetSchoolMask() & SPELL_SCHOOL_MASK_FIRE)) + pctbonus *= 1.05f; + //Master Demonologist part 3.1 (me): 5% bonus damage for Shadow spells + if ((GetSpec() == BOT_SPEC_WARLOCK_DEMONOLOGY) && + lvl >= 35 && botPet && myPetType == BOT_PET_SUCCUBUS && (spellInfo->GetSchoolMask() & SPELL_SCHOOL_MASK_SHADOW)) + pctbonus *= 1.05f; + + damage = int32(fdamage * pctbonus); + } + + void ApplyClassDamageMultiplierHeal(Unit const* /*victim*/, float& heal, SpellInfo const* spellInfo, DamageEffectType /*damagetype*/, uint32 /*stack*/) const override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + uint8 lvl = me->GetLevel(); + float pctbonus = 0.0f; + + //Glyph of Siphon Life: 25% bonus healing for Siphon Life effect (50% for bots) + if (baseId == SIPHON_LIFE_HEAL) + pctbonus += 0.5f; + //Improved Death Coil (30052): 30% bonus healing for Death Coil + if (lvl >= 60 && baseId == DEATH_COIL_1) + pctbonus += 0.3f; + + heal = heal * (1.0f + pctbonus); + } + + void ApplyClassSpellCostMods(SpellInfo const* spellInfo, int32& cost) const override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + uint8 lvl = me->GetLevel(); + float fcost = float(cost); + float pctbonus = 0.0f; + + //pct mods + //Doomcaller Reduced Shadow Bolt Cost (26117): -15% mana cost for Shadow Bolt + if (baseId == SHADOW_BOLT_1) + pctbonus += 0.15f; + //Glyph of Shadow Bolt: -10% mana cost for Shadow Bolt + if (lvl >= 15 && baseId == SHADOW_BOLT_1) + pctbonus += 0.1f; + //Cataclysm: -10% mana cost for Destruction spells + if (lvl >= 15 && spellInfo->SpellFamilyName == SPELLFAMILY_WARLOCK && + ((spellInfo->SpellFamilyFlags[0] & 0x3E5) || (spellInfo->SpellFamilyFlags[1] & 0x8310C0))) + pctbonus += 0.1f; + //Suppression: -6% mana cost for Affliction spells + if (lvl >= 10 && spellInfo->SpellFamilyName == SPELLFAMILY_WARLOCK && + ((spellInfo->SpellFamilyFlags[0] & 0x814CC41A) || (spellInfo->SpellFamilyFlags[1] & 0x248F1B))) + pctbonus += 0.06f; + + //cost can be < 0 + cost = int32(fcost * (1.0f - pctbonus)); + } + + void ApplyClassSpellCastTimeMods(SpellInfo const* spellInfo, int32& casttime) const override + { + //casttime is in milliseconds + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + uint8 lvl = me->GetLevel(); + int32 timebonus = 0; + float pctbonus = 0.0f; + + //100% mods + //Backlash: -100% cast time for Shadow Bolt or Incinerate + if (lvl >= 15 && (baseId == SHADOW_BOLT_1 || baseId == INCINERATE_1)) + { + backlash = me->HasAura(BACKLASH_BUFF); + shadowtrance = (baseId == SHADOW_BOLT_1 && me->HasAura(SHADOW_TRANCE_BUFF)); + if (backlash || shadowtrance) + timebonus += casttime; + } + //Improved Howl of Terror: -1.5sec (-100%) cast time for Howl of Terror + if ((GetSpec() == BOT_SPEC_WARLOCK_AFFLICTION) && lvl >= 45 && baseId == HOWL_OF_TERROR_1) + timebonus += casttime; + //Chaotic Mind (custom) + if (baseId == SOUL_FIRE_1) + { + chaoticmind = me->HasAura(CHAOTIC_MIND_BUFF); + if (chaoticmind) + timebonus += casttime; + } + + //pct mods + //BackDraft part 1: -30% cast time for Destruction spells + if (lvl >= 50 && spellInfo->SpellFamilyName == SPELLFAMILY_WARLOCK && + ((spellInfo->SpellFamilyFlags[0] & 0x165) || (spellInfo->SpellFamilyFlags[1] & 0x310C0))) + { + //skip soul fire insta cast + backdraft = me->HasAura(BACKDRAFT_BUFF) && !(chaoticmind && baseId == SOUL_FIRE_1); + if (backdraft) + pctbonus += 0.3f; + } + //Molten Core part 2.1: -30% cast time for Incinerate + if (lvl >= 35 && baseId == INCINERATE_1) + { + moltencore = me->HasAura(MOLTEN_CORE_BUFF); + if (moltencore) + pctbonus += 0.3f; + } + //Decimation: -40% cast time for Soul Fire + if (baseId == SOUL_FIRE_1 && me->HasAura(DECIMATION_BUFF)) + pctbonus += 0.4f; + + //flat mods + //Bane: -0.5 sec cast time for Shadow Bolt, Immolate and Chaos Bolt, -2 sec cast for Soul Fire + if (lvl >= 10) + { + if (baseId == SHADOW_BOLT_1 || baseId == IMMOLATE_1 || baseId == CHAOS_BOLT_1) + timebonus += 500; + else if (baseId == SOUL_FIRE_1) + timebonus += 2000; + } + //EmberStorm part 2: -0.25 sec cast time for Incinerate + if ((GetSpec() == BOT_SPEC_WARLOCK_DESTRUCTION) && lvl >= 35 && baseId == INCINERATE_1) + timebonus += 250; + //Glyph of Unstable Affliction: -0.2 sec cast time for Unstable Affliction + if (lvl >= 50 && baseId == UNSTABLE_AFFLICTION_1) + timebonus += 200; + //Fear Cast Time Reduction (23047): -0.2 sec cast time for Fear + if (baseId == FEAR_1) + timebonus += 200; + + casttime = std::max((float(casttime) * (1.0f - pctbonus)) - timebonus, 0); + + instaCast = (casttime <= 500); //triggered GCD is too long + } + + void ApplyClassSpellCooldownMods(SpellInfo const* /*spellInfo*/, uint32& cooldown) const override + { + //cooldown is in milliseconds + //uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + //uint8 lvl = me->GetLevel(); + int32 timebonus = 0; + float pctbonus = 0.0f; + + //pct mods + //Glyph of Rapid Charge: -7% cooldown for Charge + //if (lvl >= 40 && spellId == GetSpell(CHARGE_1)) + // pctbonus += 0.07f; + + //flat mods + + cooldown = std::max((float(cooldown) * (1.0f - pctbonus)) - timebonus, 0); + } + + void ApplyClassSpellCategoryCooldownMods(SpellInfo const* spellInfo, uint32& cooldown) const override + { + //cooldown is in milliseconds + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + uint8 lvl = me->GetLevel(); + int32 timebonus = 0; + float pctbonus = 0.0f; + + //Glyph of Chaos Bolt: -2 sec cooldown for Chaos Bolt + if (lvl >= 60 && baseId == CHAOS_BOLT_1) + timebonus += 2000; + //Improved Death Coil (24487): -15% cooldown for Death Coil (30 sec for bots) + if (baseId == DEATH_COIL_1) + timebonus += 30000; + //Glyph of Howl of Terror: -8 sec cooldown for Howl of Terror + if (lvl >= 45 && baseId == HOWL_OF_TERROR_1) + timebonus += 8000; + + cooldown = std::max((float(cooldown) * (1.0f - pctbonus)) - timebonus, 0); + } + + void ApplyClassSpellGlobalCooldownMods(SpellInfo const* spellInfo, float& cooldown) const override + { + //cooldown is in milliseconds + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + uint8 lvl = me->GetLevel(); + float timebonus = 0.0f; + float pctbonus = 0.0f; + + //pct mods + //BackDraft: -30% global cooldown for Destruction spells + if ((GetSpec() == BOT_SPEC_WARLOCK_DESTRUCTION) && + lvl >= 50 && spellInfo->SpellFamilyName == SPELLFAMILY_WARLOCK && + ((spellInfo->SpellFamilyFlags[0] & 0x165) || (spellInfo->SpellFamilyFlags[1] & 0x310C0)) && + me->HasAura(BACKDRAFT_BUFF)) + pctbonus += 0.3f; + + //flat mods + //Amplify Curse: -0.5 sec global cooldown for Curses + if (lvl >= 20 && spellInfo->SpellFamilyName == SPELLFAMILY_WARLOCK && + ((spellInfo->SpellFamilyFlags[0] & 0x408400) || (spellInfo->SpellFamilyFlags[1] & 0x200202) || + (spellInfo->SpellFamilyFlags[2] & 0x800))) + timebonus += 500.f; + + //Fear Cast Time Reduction (23047): -0.2 sec global cooldown for Fear + if (baseId == FEAR_1) + timebonus += 200; + + cooldown = (cooldown * (1.0f - pctbonus)) - timebonus; + } + + void ApplyClassSpellRadiusMods(SpellInfo const* spellInfo, float& radius) const override + { + //uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + uint8 lvl = me->GetLevel(); + float flatbonus = 0.0f; + float pctbonus = 0.0f; + + //pct mods + + //flat mods + //Improved Rain of Fire / Hellfire + if (lvl >= 40 && (spellInfo->SpellFamilyFlags[0] & 0x60)) + flatbonus += 2.f; + + radius = radius * (1.0f + pctbonus) + flatbonus; + } + + void ApplyClassSpellRangeMods(SpellInfo const* spellInfo, float& maxrange) const override + { + //uint32 spellId = spellInfo->Id; + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + uint8 lvl = me->GetLevel(); + float flatbonus = 0.0f; + float pctbonus = 0.0f; + + //pct mods + //Grim Reach: +20% range for Affliction Spells + if (lvl >= 25 && ((spellInfo->SpellFamilyFlags[0] & 0x8048C41A) || (spellInfo->SpellFamilyFlags[1] & 0x40713))) + pctbonus += 0.2f; + //Destructive Reach: +20% range for Destruction Spells + if (lvl >= 25 && ((spellInfo->SpellFamilyFlags[0] & 0x13A5) || (spellInfo->SpellFamilyFlags[1] & 0x8210C0))) + pctbonus += 0.2f; + + //flat mods + //Glyph of Curse of Exhaustion: +5 yd range for Curse of Exhaustion + if (lvl >= 70 && baseId == CURSE_OF_EXHAUSTION_1) + flatbonus += 5.f; + + maxrange = maxrange * (1.0f + pctbonus) + flatbonus; + } + + void OnClassSpellGo(SpellInfo const* spellInfo) override + { + //uint32 spellId = spellInfo->Id; + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + + //Instacast buffs handling + if (baseId == SHADOW_BOLT_1) + { + if (backlash) + me->RemoveAurasDueToSpell(BACKLASH_BUFF); + else if (shadowtrance) + me->RemoveAurasDueToSpell(SHADOW_TRANCE_BUFF); + } + if (baseId == INCINERATE_1) + { + if (backlash) + me->RemoveAurasDueToSpell(BACKLASH_BUFF); + } + if (chaoticmind && baseId == SOUL_FIRE_1) + me->RemoveAurasDueToSpell(CHAOTIC_MIND_BUFF); + + //Backdraft + if (backdraft && spellInfo->SpellFamilyName == SPELLFAMILY_WARLOCK && + ((spellInfo->SpellFamilyFlags[0] & 0x165) || (spellInfo->SpellFamilyFlags[1] & 0x310C0))) + { + if (Aura* bd = me->GetAura(BACKDRAFT_BUFF)) + bd->DropCharge(); + } + //Molten Core + if (moltencore && (baseId == INCINERATE_1 || baseId == SOUL_FIRE_1)) + { + if (Aura* mc = me->GetAura(MOLTEN_CORE_BUFF)) + mc->DropCharge(); + } + //Decimation: NOT DROPPED ON CAST + //if (baseId == SOUL_FIRE_1) + //{ + // if (Aura* mc = me->GetAura(DECIMATION_BUFF)) + // mc->DropCharge(); + //} + longCasted = !instaCast && + (baseId == SHADOW_BOLT_1 || baseId == INCINERATE_1 || baseId == CHAOS_BOLT_1 || + baseId == SOUL_FIRE_1 || baseId == HAUNT_1 || baseId == SEARING_PAIN_1); //damaging spells + } + + void SpellHit(WorldObject* wcaster, SpellInfo const* spell) override + { + Unit* caster = wcaster->ToUnit(); + if (!caster) + return; + + uint32 spellId = spell->Id; + uint32 baseId = spell->GetFirstRankSpell()->Id; + uint8 lvl = me->GetLevel(); + + //Shadow Ward helper + if (!canShadowWard && (spell->GetSchoolMask() & SPELL_SCHOOL_MASK_SHADOW) && + (spell->HasEffect(SPELL_EFFECT_SCHOOL_DAMAGE) || spell->HasAura(SPELL_AURA_PERIODIC_DAMAGE))) + canShadowWard = true; + + //Create Healthstone trigger + if (baseId == CREATE_HEALTHSTONE_1) + { + hasHealthstone = true; + } + //Create Soulstone trigger + if (baseId == CREATE_SOULSTONE_1) + { + hasSoulstone = true; + } + + //Glyph of Soul Link: +5% increased effect + if (baseId == SOUL_LINK_PET) + { + if (AuraEffect* link = me->GetAuraEffect(spellId, 0)) + link->ChangeAmount(link->GetAmount() + 5); + } + + //Life Tap energize + if (baseId == LIFE_TAP_1) + { + //level * 3 based on in-game tooltip and spellwork (BasePoints = 2000 + Level * 4,00) + int32 damage = spell->_effects[0].CalcValue(me); + int32 manaGain = damage; + //damage += int32(me->GetLevel() * 3); + manaGain += 0.5f * me->SpellBaseDamageBonusDone(SPELL_SCHOOL_MASK_MAGIC); + + //Life Tap (id: 28830) + //damage = int32(float(damage) * 0.88f); + //Improved Life Tap + if (me->GetLevel() >= 15) + manaGain = int32(float(manaGain) * 1.2f); + + me->ModifyHealth(-damage); + CastSpellExtraArgs args; + args.AddSpellBP0(manaGain); + me->CastSpell(me, LIFE_TAP_ENERGIZE, args); + + //Mana Feed + if ((GetSpec() == BOT_SPEC_WARLOCK_DEMONOLOGY) && me->GetLevel() >= 35 && botPet) + me->EnergizeBySpell(botPet, LIFE_TAP_ENERGIZE_PET, manaGain, POWER_MANA); + } + + //Glyph of Life Tap trigger + if (baseId == GLYPH_LIFE_TAP_BUFF) + SetShouldUpdateStats(); + + if (baseId == DEMON_ARMOR_1 || baseId == FEL_ARMOR_1) + { + if (Aura* armo = me->GetAura(spellId, me->GetGUID())) + { + //Armors duration + uint32 dur = 1 * HOUR * IN_MILLISECONDS; + armo->SetDuration(dur); + armo->SetMaxDuration(dur); + + //Demonic Aegis + if (lvl >= 20) + { + for (uint8 i = 0; i != MAX_SPELL_EFFECTS; ++i) + if (AuraEffect* eff = armo->GetEffect(i)) + eff->ChangeAmount(eff->GetAmount() * 13 / 10); + } + } + } + //Chaotic Mind (custom) + if (baseId == CHAOTIC_MIND) + { + if (Aura* mind = me->GetAura(spellId)) + { + uint32 dur = 30000; //30 sec + mind->SetDuration(dur); + mind->SetMaxDuration(dur); + } + } + OnSpellHit(caster, spell); + } + + void SpellHitTarget(WorldObject* wtarget, SpellInfo const* spell) override + { + Unit* target = wtarget->ToUnit(); + if (!target) + return; + + uint32 spellId = spell->Id; + uint32 baseId = spell->GetFirstRankSpell()->Id; + uint8 lvl = me->GetLevel(); + + //Soulstone resurrection trigger (not ranked) + if (spellId == SOULSTONE_RESURRECTION_1 || spellId == SOULSTONE_RESURRECTION_2 || + spellId == SOULSTONE_RESURRECTION_3 || spellId == SOULSTONE_RESURRECTION_4 || + spellId == SOULSTONE_RESURRECTION_5 || spellId == SOULSTONE_RESURRECTION_6 || + spellId == SOULSTONE_RESURRECTION_7) + { + hasSoulstone = false; + //reduced for bot + //soulstoneTimer = 15 * MINUTE * IN_MILLISECONDS; + soulstoneTimer = 10 * MINUTE * IN_MILLISECONDS; + } + + //Improved Imp part 3 + if (lvl >= 10 && baseId == BLOOD_PACT_1 && botPet) + { + AuraEffect* pact = target->GetAuraEffect(spellId, 0, botPet->GetGUID()); + if (pact) + pact->ChangeAmount(pact->GetAmount() * 1.3f); + } + + //Improved Felhunter part 3 + if ((GetSpec() == BOT_SPEC_WARLOCK_AFFLICTION) && lvl >= 35 && baseId == FEL_INTELLIGENCE_1 && botPet) + { + Aura const* feli = target->GetAura(spellId, botPet->GetGUID()); + if (feli) + { + for (uint8 i = EFFECT_0; i != EFFECT_2; ++i) + { + if (AuraEffect* effi = feli->GetEffect(i)) + effi->ChangeAmount(effi->GetAmount() + effi->GetAmount() / 10); + } + } + } + + //Glyph of Unending Breath: swim speed + if (/*lvl >= 15 && */baseId == UNENDING_BREATH_1) + { + AuraEffect* brea = target->GetAuraEffect(spellId, 1, me->GetGUID()); + if (brea) + brea->ChangeAmount(brea->GetAmount() + 20); + } + + //Chaotic Mind (custom) + if (lvl >= 60 && target != me && GetSpec() != BOT_SPEC_WARLOCK_AFFLICTION && + spell->SpellFamilyName == SPELLFAMILY_WARLOCK && !spell->IsPositive()) + { + if (urand(1,100) <= 5) + me->CastSpell(me, CHAOTIC_MIND, true); + } + if (baseId == IMMOLATE_1 || baseId == CORRUPTION_1) + { + if (Aura* per = target->GetAura(spellId, me->GetGUID())) + { + //Improved Corruption and Immolate (37380): +3 sec duration for Immolate and Corruption + uint32 dur = per->GetDuration() + 3000; + //Molten Core: + 9 sec duration for Immolate + if ((GetSpec() == BOT_SPEC_WARLOCK_DESTRUCTION) && lvl >= 35 && baseId == IMMOLATE_1) + dur += 9000; + per->SetDuration(dur); + per->SetMaxDuration(dur); + } + } + //Glyph of Death Coil: + 0.5 sec duration for Death Coil (2 sec on creatures) + if (baseId == DEATH_COIL_1) + { + if (Aura* dc = target->GetAura(spellId, me->GetGUID())) + { + uint32 dur = dc->GetDuration() + (target->GetTypeId() == TYPEID_PLAYER ? 500 : 2000); + dc->SetDuration(dur); + dc->SetMaxDuration(dur); + } + } + //Improved Curse of Weakness: +20% increased effect + if (baseId == CURSE_OF_WEAKNESS_1) + { + if (AuraEffect* weak = target->GetAuraEffect(spellId, 0, me->GetGUID())) + { + weak->ChangeAmount(weak->GetAmount() * 12 / 10); + } + } + //Glyph of Haunt: +3% increased effect + if (lvl >= 60 && baseId == HAUNT_1) + { + if (AuraEffect* haun = target->GetAuraEffect(spellId, 2, me->GetGUID())) + { + haun->ChangeAmount(haun->GetAmount() + 3); + } + } + + OnSpellHitTarget(target, spell); + } + + void DamageDealt(Unit* victim, uint32& damage, DamageEffectType damageType) override + { + //Fel Synergy (Life Tap) + if (damage && botPet && me->GetLevel() >= 10 && (damageType == SPELL_DIRECT_DAMAGE || damageType == DOT)) + { + uint32 healVal = float(damage) * 0.15f; + if (healVal) + { + SpellInfo const* synhealInfo = sSpellMgr->GetSpellInfo(FEL_SYNERGY_HEAL); + HealInfo hinfo(me, botPet, healVal, synhealInfo, synhealInfo->GetSchoolMask()); + botPet->HealBySpell(hinfo); + } + } + + bot_ai::DamageDealt(victim, damage, damageType); + } + + void DamageTaken(Unit* u, uint32& /*damage*/, DamageEffectType /*damageType*/, SpellInfo const* /*spellInfo*/) override + { + if (!u) + return; + if (!u->IsInCombat() && !me->IsInCombat()) + return; + OnOwnerDamagedBy(u); + } + + void OwnerAttackedBy(Unit* u) override + { + if (!u) + return; + OnOwnerDamagedBy(u); + } + + void SummonBotPet() + { + if (botPet) + UnsummonAll(false); + + if (myPetType == BOT_PET_INVALID) //disabled + return; + + if (petSummonTimer > GetLastDiff()) + return; + + uint32 entry; + + if (myPetType) + entry = myPetType; + else if (me->GetLevel() >= 50 && GetSpec() == BOT_SPEC_WARLOCK_DEMONOLOGY) + entry = BOT_PET_FELGUARD; + else if (!IAmFree()) + { + if (me->GetLevel() >= 30 && master->GetMaxPower(POWER_MANA) > 1 && + !master->GetBotMgr()->HasBotClass(BOT_CLASS_MAGE) && + !master->GetBotMgr()->HasBotClass(BOT_CLASS_PRIEST) && + !master->GetBotMgr()->HasBotPetType(BOT_PET_FELHUNTER)) + entry = BOT_PET_FELHUNTER; + else if ((me->GetLevel() < 68 || !master->GetBotMgr()->HasBotClass(BOT_CLASS_WARRIOR)) && + !master->GetBotMgr()->HasBotPetType(BOT_PET_IMP)) + entry = BOT_PET_IMP; + else if (me->GetLevel() >= 10 && IsTank()) + entry = BOT_PET_VOIDWALKER; + else if (me->GetLevel() >= 20 && !IsMeleeClass(master->GetClass())) + entry = BOT_PET_SUCCUBUS; + else if (me->GetLevel() >= 10) + entry = BOT_PET_VOIDWALKER; + else + entry = BOT_PET_IMP; + } + else + entry = urand(BOT_PET_WARLOCK_START, BOT_PET_WARLOCK_END); + + //ensurance + if ((entry == BOT_PET_VOIDWALKER && me->GetLevel() < 10) || + (entry == BOT_PET_SUCCUBUS && me->GetLevel() < 20) || + (entry == BOT_PET_FELHUNTER && me->GetLevel() < 30) || + (entry == BOT_PET_FELGUARD && (me->GetLevel() < 50 || _spec != BOT_SPEC_WARLOCK_DEMONOLOGY)) || + (entry != BOT_PET_IMP && entry != BOT_PET_VOIDWALKER && entry != BOT_PET_SUCCUBUS && + entry != BOT_PET_FELHUNTER && entry != BOT_PET_FELGUARD)) + entry = 0; + + myPetType = entry; + + //try next time + if (!myPetType) + return; + + ResetSpellCooldown(BLOOD_PACT_1); + ResetSpellCooldown(FEL_INTELLIGENCE_1); + + Position pos; + + me->CastSpell(me, SUMMON_DEMON_VISUAL, true); + Creature* myPet = me->SummonCreature(myPetType, *me, TEMPSUMMON_CORPSE_DESPAWN); + me->GetNearPoint(myPet, pos.m_positionX, pos.m_positionY, pos.m_positionZ, 0, me->GetOrientation() + M_PI / 2); + myPet->GetMotionMaster()->MovePoint(me->GetMapId(), pos); + myPet->SetCreator(master); + myPet->SetOwnerGUID(me->GetGUID()); + myPet->SetFaction(master->GetFaction()); + myPet->SetControlledByPlayer(!IAmFree()); + myPet->SetPvP(me->IsPvP()); + myPet->SetByteValue(UNIT_FIELD_BYTES_2, 1, master->GetByteValue(UNIT_FIELD_BYTES_2, 1)); + + //fix scale and equips + switch (myPetType) + { + case BOT_PET_FELHUNTER: + myPet->SetObjectScale(1.1f); + break; + case BOT_PET_FELGUARD: + myPet->SetObjectScale(0.75f); + myPet->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 0, 22199); + break; + } + + botPet = myPet; + } + + void UnsummonAll(bool savePets = true) override + { + UnsummonPet(savePets); + } + + void SummonedCreatureDies(Creature* /*summon*/, Unit* /*killer*/) override + { + } + + void SummonedCreatureDespawn(Creature* summon) override + { + //all warlock bot pets despawn at death or manually (gossip, teleport, etc.) + //TC_LOG_ERROR("entities.unit", "SummonedCreatureDespawn: {}'s {}", me->GetName(), summon->GetName()); + if (summon == botPet) + { + petSummonTimer = 10000; + botPet = nullptr; + + //party aura hack removal helper + switch (summon->GetEntry()) + { + case BOT_PET_IMP: + me->RemoveAurasDueToSpell(InitSpell(me, BLOOD_PACT_1)); + break; + case BOT_PET_FELHUNTER: + me->RemoveAurasDueToSpell(InitSpell(me, FEL_INTELLIGENCE_1)); + break; + } + } + } + + float GetSpellAttackRange(bool longRange) const override + { + return longRange ? CalcSpellMaxRange(SHADOW_BOLT_1) : 20.f; + } + + uint32 GetAIMiscValue(uint32 data) const override + { + switch (data) + { + case CREATE_HEALTHSTONE_1: + return uint32(hasHealthstone); + case BOTAI_MISC_PET_TYPE: + return myPetType; + case BOTAI_MISC_PET_AVAILABLE_1: + return BOT_PET_IMP; + case BOTAI_MISC_PET_AVAILABLE_2: + return me->GetLevel() >= 10 ? BOT_PET_VOIDWALKER : 0; + case BOTAI_MISC_PET_AVAILABLE_3: + return me->GetLevel() >= 20 ? BOT_PET_SUCCUBUS : 0; + case BOTAI_MISC_PET_AVAILABLE_4: + return me->GetLevel() >= 30 ? BOT_PET_FELHUNTER : 0; + case BOTAI_MISC_PET_AVAILABLE_5: + return me->GetLevel() >= 50 && GetSpec() == BOT_SPEC_WARLOCK_DEMONOLOGY ? BOT_PET_FELGUARD : 0; + default: + return 0; + } + } + + void SetAIMiscValue(uint32 data, uint32 value) override + { + switch (data) + { + case CREATE_HEALTHSTONE_1: + hasHealthstone = bool(value); + break; + case BOTAI_MISC_PET_TYPE: + myPetType = value; + UnsummonAll(false); + break; + default: + break; + } + } + + void Reset() override + { + UnsummonAll(false); + + myPetType = 0; + + fearTimer = 0; + banishTimer = 0; + unbanishTimer = 0; + drainManaTimer = 0; + healthstoneTimer = 0; + soulstoneTimer = 0; + lifeTapCheckTimer = 0; + curseCheckTimer = 0; + + petSummonTimer = 5000; + + hasHealthstone = false; + hasSoulstone = false; + + DefaultInit(); + } + + void ReduceCD(uint32 diff) override + { + if (fearTimer > diff) fearTimer -= diff; + if (banishTimer > diff) banishTimer -= diff; + if (unbanishTimer > diff) unbanishTimer -= diff; + if (drainManaTimer > diff) drainManaTimer -= diff; + if (healthstoneTimer > diff) healthstoneTimer -= diff; + if (soulstoneTimer > diff) soulstoneTimer -= diff; + if (lifeTapCheckTimer > diff) lifeTapCheckTimer -= diff; + if (curseCheckTimer > diff) curseCheckTimer -= diff; + + if (petSummonTimer > diff) petSummonTimer -= diff; + } + + void InitPowers() override + { + me->SetPowerType(POWER_MANA); + + if (botPet && botPet->GetPowerType() != POWER_MANA) + botPet->SetByteValue(UNIT_FIELD_BYTES_0, 3, POWER_MANA); + } + + void InitSpells() override + { + uint8 lvl = me->GetLevel(); + bool isAffl = GetSpec() == BOT_SPEC_WARLOCK_AFFLICTION; + //bool isDemo = GetSpec() == BOT_SPEC_WARLOCK_DEMONOLOGY; + bool isDest = GetSpec() == BOT_SPEC_WARLOCK_DESTRUCTION; + + InitSpellMap(CURSE_OF_WEAKNESS_1); + InitSpellMap(CURSE_OF_AGONY_1); + InitSpellMap(CURSE_OF_TONGUES_1); + InitSpellMap(CURSE_OF_THE_ELEMENTS_1); + InitSpellMap(SHADOW_BOLT_1); + InitSpellMap(IMMOLATE_1); + InitSpellMap(CORRUPTION_1); + InitSpellMap(SEED_OF_CORRUPTION_1); + InitSpellMap(INCINERATE_1); + InitSpellMap(SEARING_PAIN_1); + InitSpellMap(SOUL_FIRE_1); + InitSpellMap(RAIN_OF_FIRE_1); + InitSpellMap(HELLFIRE_1); + InitSpellMap(SHADOWFLAME_1); + InitSpellMap(FEAR_1); + InitSpellMap(HOWL_OF_TERROR_1); + InitSpellMap(DEATH_COIL_1); + InitSpellMap(SOULSHATTER_1); + + InitSpellMap(DRAIN_SOUL_1); + InitSpellMap(DRAIN_MANA_1); + InitSpellMap(BANISH_1); + + InitSpellMap(DEMON_SKIN_1); + InitSpellMap(DEMON_ARMOR_1); + InitSpellMap(FEL_ARMOR_1); + InitSpellMap(DETECT_INVISIBILITY_1); + InitSpellMap(UNENDING_BREATH_1); + InitSpellMap(SHADOW_WARD_1); + InitSpellMap(LIFE_TAP_1); + InitSpellMap(DARK_PACT_1); + InitSpellMap(CREATE_HEALTHSTONE_1); + InitSpellMap(CREATE_SOULSTONE_1); + + InitSpellMap(RITUAL_OF_SUMMONING_1); //manual only + InitSpellMap(RITUAL_OF_SOULS_1); //not casted + + /*Talent*/lvl >= 30 && isAffl ? InitSpellMap(CURSE_OF_EXHAUSTION_1) : RemoveSpell(CURSE_OF_EXHAUSTION_1); + /*Talent*/lvl >= 50 && isAffl ? InitSpellMap(UNSTABLE_AFFLICTION_1) : RemoveSpell(UNSTABLE_AFFLICTION_1); + /*Talent*/lvl >= 60 && isAffl ? InitSpellMap(HAUNT_1) : RemoveSpell(HAUNT_1); + + /*Talent*/lvl >= 20 && isDest ? InitSpellMap(SHADOWBURN_1) : RemoveSpell(SHADOWBURN_1); + /*Talent*/lvl >= 40 && isDest ? InitSpellMap(CONFLAGRATE_1) : RemoveSpell(CONFLAGRATE_1); + /*Talent*/lvl >= 50 && isDest ? InitSpellMap(SHADOWFURY_1) : RemoveSpell(SHADOWFURY_1); + /*Talent*/lvl >= 60 && isDest ? InitSpellMap(CHAOS_BOLT_1) : RemoveSpell(CHAOS_BOLT_1); + } + + void ApplyClassPassives() const override + { + uint8 level = master->GetLevel(); + bool isAffl = GetSpec() == BOT_SPEC_WARLOCK_AFFLICTION; + bool isDemo = GetSpec() == BOT_SPEC_WARLOCK_DEMONOLOGY; + bool isDest = GetSpec() == BOT_SPEC_WARLOCK_DESTRUCTION; + + RefreshAura(CHAOS_BOLT_PASSIVE); + RefreshAura(DEMONIC_IMMOLATE_PASSIVE); + + RefreshAura(IMPROVED_DRAIN_SOUL, level >= 15 ? 1 : 0); + RefreshAura(SOUL_SIPHON, level >= 15 ? 1 : 0); + RefreshAura(IMPROVED_FEAR, level >= 20 ? 1 : 0); + RefreshAura(NIGHTFALL, level >= 25 ? 1 : 0); + RefreshAura(SHADOW_EMBRACE, isAffl && level >= 30 ? 1 : 0); + RefreshAura(SIPHON_LIFE, isAffl && level >= 30 ? 1 : 0); + RefreshAura(ERADICATION, isAffl && level >= 40 ? 1 : 0); + RefreshAura(PANDEMIC, isAffl && level >= 50 ? 1 : 0); + RefreshAura(EVERLASTING_AFFLICTION, isAffl && level >= 55 ? 1 : 0); + + RefreshAura(DEMONIC_RESILIENCE, isDemo && level >= 40 ? 1 : 0); + RefreshAura(DECIMATION, isDemo && level >= 45 ? 1 : 0); + + RefreshAura(IMPROVED_SHADOW_BOLT, level >= 10 ? 1 : 0); + RefreshAura(AFTERMATH, level >= 15 ? 1 : 0); + RefreshAura(BACKLASH, level >= 30 ? 1 : 0); + RefreshAura(MOLTEN_CORE, isDest && level >= 35 ? 1 : 0); + RefreshAura(NETHER_PROTECTION, isDest && level >= 35 ? 1 : 0); + RefreshAura(SOUL_LEECH, isDest && level >= 40 ? 1 : 0); + RefreshAura(PYROCLASM, isDest && level >= 40 ? 1 : 0); + RefreshAura(IMPROVED_SOUL_LEECH, isDest && level >= 45 ? 1 : 0); + RefreshAura(BACKDRAFT, isDest && level >= 50 ? 1 : 0); + + RefreshAura(GLYPH_CORRUPTION, level >= 15 ? 1 : 0); + RefreshAura(GLYPH_LIFE_TAP, level >= 15 ? 1 : 0); + RefreshAura(GLYPH_FEAR, level >= 15 ? 1 : 0); + RefreshAura(GLYPH_QUICK_DECAY, level >= 15 ? 1 : 0); + RefreshAura(GLYPH_CONFLAGRATE, level >= 40 ? 1 : 0); + RefreshAura(GLYPH_SHADOWFLAME, level >= 75 ? 1 : 0); + } + + bool CanUseManually(uint32 basespell) const override + { + switch (basespell) + { + case RAIN_OF_FIRE_1: + case SHADOWFLAME_1: + case HOWL_OF_TERROR_1: + case DETECT_INVISIBILITY_1: + case UNENDING_BREATH_1: + //case RITUAL_OF_SUMMONING_1: + case SHADOW_WARD_1: + case LIFE_TAP_1: + case DARK_PACT_1: + return true; + //case FEL_ARMOR_1: + // return true; + //case DEMON_ARMOR_1: + // return !GetSpell(FEL_ARMOR_1); + //case DEMON_SKIN_1: + // return !GetSpell(FEL_ARMOR_1) && !GetSpell(DEMON_ARMOR_1); + default: + return false; + } + } + + std::vector const* GetDamagingSpellsList() const override + { + return &Warlock_spells_damage; + } + std::vector const* GetCCSpellsList() const override + { + return &Warlock_spells_cc; + } + //std::vector const* GetHealingSpellsList() const override + //{ + // return &Warlock_spells_heal; + //} + std::vector const* GetSupportSpellsList() const override + { + return &Warlock_spells_support; + } + + private: + //Timers + uint32 fearTimer, banishTimer, unbanishTimer, drainManaTimer, healthstoneTimer, + soulstoneTimer, lifeTapCheckTimer, curseCheckTimer; + //Pet + uint32 myPetType; + uint32 petSummonTimer; + //Special + mutable bool backlash, shadowtrance, backdraft, moltencore, chaoticmind; + bool canShadowWard; + bool longCasted; //some sort of rotation thing + mutable bool instaCast; + bool hasHealthstone, hasSoulstone; + + uint32 _getCursesMask(Unit const* unit) const + { + uint32 mask = 0; + Unit::AuraApplicationMap const& aurapps = unit->GetAppliedAuras(); + for (Unit::AuraApplicationMap::const_iterator itr = aurapps.begin(); itr != aurapps.end(); ++itr) + { + bool my_cast = itr->second->GetBase()->GetCasterGUID() == me->GetGUID(); + switch (itr->second->GetBase()->GetSpellInfo()->GetFirstRankSpell()->Id) + { + case CURSE_OF_WEAKNESS_1: mask |= CURSE_MASK_WEAKNESS | (my_cast ? CURSE_MASK_MY_WEAKNESS : CurseType(0)); break; + case CURSE_OF_AGONY_1: mask |= CURSE_MASK_AGONY | (my_cast ? CURSE_MASK_MY_AGONY : CurseType(0)); break; + case CURSE_OF_DOOM_1: mask |= CURSE_MASK_DOOM | (my_cast ? CURSE_MASK_MY_DOOM : CurseType(0)); break; + case CURSE_OF_THE_ELEMENTS_1: mask |= CURSE_MASK_ELEMENTS | (my_cast ? CURSE_MASK_MY_ELEMENTS : CurseType(0)); break; + case CURSE_OF_TONGUES_1: mask |= CURSE_MASK_TONGUES | (my_cast ? CURSE_MASK_MY_TONGUES : CurseType(0)); break; + case CURSE_OF_EXHAUSTION_1: mask |= CURSE_MASK_EXHAUSTION | (my_cast ? CURSE_MASK_MY_EXHAUSTION : CurseType(0)); break; + default: break; + } + } + + return mask; + } + }; +}; + +//HealthstoneSpellIds (Improved Healthstone rank 2) +uint32 const warlock_bot::warlock_botAI::_healthStoneSpells[8/*createHealthstoneRank*/] = +{ + 23469,// Minor + 23471,// Lesser + 23473,// + 23475,// Greater + 23477,// Major + 27237,// Master + 47872,// Demonic + 47877 // Fel +}; + +void AddSC_warlock_bot() +{ + new warlock_bot(); +} diff --git a/src/server/game/AI/NpcBots/bot_warrior_ai.cpp b/src/server/game/AI/NpcBots/bot_warrior_ai.cpp new file mode 100644 index 000000000..ae821bb3f --- /dev/null +++ b/src/server/game/AI/NpcBots/bot_warrior_ai.cpp @@ -0,0 +1,2196 @@ +#include "bot_ai.h" +#include "botmgr.h" +#include "botspell.h" +#include "bottext.h" +#include "bottraits.h" +#include "Containers.h" +#include "Group.h" +#include "Item.h" +#include "Map.h" +#include "MotionMaster.h" +#include "MovementDefines.h" +#include "ObjectAccessor.h" +#include "Player.h" +#include "ScriptMgr.h" +#include "Spell.h" +#include "SpellAuraEffects.h" +#include "SpellMgr.h" + +#include "Formulas.h" +/* +Warrior NpcBot (reworked by Trickerer onlysuffering@gmail.com) +Complete - 98% +TODO: +*/ + +enum WarriorBaseSpells +{ + BATTLE_STANCE_1 = 2457, + DEFENSIVE_STANCE_1 = 71, + BERSERKER_STANCE_1 = 2458, + + INTIMIDATING_SHOUT_1 = 5246, + ENRAGED_REGENERATION_1 = 55694, + CHARGE_1 = 100, + OVERPOWER_1 = 7384, + TAUNT_1 = 355, + BLOODRAGE_1 = 2687, + BERSERKER_RAGE_1 = 18499, + INTERCEPT_1 = 20252, + CLEAVE_1 = 845, + HAMSTRING_1 = 1715, + INTERVENE_1 = 3411, + WHIRLWIND_1 = 1680, + BLADESTORM_1 = 46924, + BATTLE_SHOUT_1 = 6673, + REND_1 = 772, + EXECUTE_1 = 5308, + PUMMEL_1 = 6552, + BLOODTHIRST_1 = 23881, + MORTAL_STRIKE_1 = 12294, + SLAM_1 = 1464, + SUNDER_ARMOR_1 = 7386, + SWEEPING_STRIKES_1 = 12328, + RECKLESSNESS_1 = 1719, + RETALIATION_1 = 20230, + DEATH_WISH_1 = 12292, + VICTORY_RUSH_1 = 34428, + THUNDER_CLAP_1 = 6343, + LAST_STAND_1 = 12975, + REVENGE_1 = 6572, + SHIELD_BLOCK_1 = 2565, + SHIELD_SLAM_1 = 23922, + SPELL_REFLECTION_1 = 23920, + DISARM_1 = 676, + SHIELD_WALL_1 = 871, + SHIELD_BASH_1 = 72, + HEROIC_THROW_1 = 57755, + CONCUSSION_BLOW_1 = 12809, + VIGILANCE_1 = 50720, + DEVASTATE_1 = 20243, + MOCKING_BLOW_1 = 694, + SHOCKWAVE_1 = 46968, + PIERCING_HOWL_1 = 12323, + HEROIC_STRIKE_1 = 78, + CHALLENGING_SHOUT_1 = 1161, + COMMANDING_SHOUT_1 = 469, + SHATTERING_THROW_1 = 64382, + DEMORALIZING_SHOUT_1 = 1160, + HEROIC_FURY_1 = 60970 +}; +enum WarriorPassives +{ +//Talents + ARMORED_TO_THE_TEETH = 61222,//rank 3 + SHIELD_SPECIALIZATION = 12727,//rank 5 + DEEP_WOUNDS_1 = 12834, + DEEP_WOUNDS_2 = 12849, + DEEP_WOUNDS_3 = 12867, + BLOOD_CRAZE1 = 16487, + BLOOD_CRAZE2 = 16489, + BLOOD_CRAZE3 = 16492, + TOUGHNESS = 12764,//rank 5 + TWO_HANDED_WEAPON_SPECIALIZATION = 12712,//rank 3 + TASTE_FOR_BLOOD1 = 56636, + TASTE_FOR_BLOOD2 = 56637, + TASTE_FOR_BLOOD3 = 56638, + DUAL_WIELD_SPECIALIZATION = 23588,//rank 5 + IMPROVED_SPELL_REFLECTION = 59089,//rank 2 + SWORD_SPEC1 = 12281, + SWORD_SPEC2 = 12812, + SWORD_SPEC3 = 12813, + SWORD_SPEC4 = 12814, + SWORD_SPEC5 = 12815, + IMPROVED_HAMSTRING = 23695,//rank 3 + TRAUMA1 = 46854, + TRAUMA2 = 46855, + FLURRY1 = 12319, + FLURRY2 = 12971, + FLURRY3 = 12972, + FLURRY4 = 12973, + FLURRY5 = 12974, + ONE_HANDED_WEAPON_SPECIALIZATION = 16542,//rank 5 + SECOND_WIND = 29838,//rank 2 + IMPROVED_DEFENSIVE_STANCE = 29594,//rank 2 + JUGGERNAUGHT = 64976, + FURIOUS_ATTACKS = 46911,//rank 2 + SAFEGUARD = 46949,//rank 2 + SUDDEN_DEATH = 29724,//rank 3 + ENDLESS_RAGE = 29623, + BLOOD_FRENZY = 29859, + RAMPAGE = 29801, + BLOODSURGE = 46915,//rank 3 + WARBRINGER = 57499, + CRITICAL_BLOCK = 47296,//rank 3 + WRECKING_CREW = 56614,//rank 5 + DAMAGE_SHIELD = 58874,//rank 2 +//other + GLYPH_HEROIC_STRIKE = 58357, + GLYPH_REVENGE = 58364, + GLYPH_EXECUTION = 58367, + GLYPH_BLOCKING = 58375, + GLYPH_VIGILANCE = 63326, + GLYPH_DEVASTATE = 58388, + + WARRIOR_T10_PROT_4P = 70844 //bloodrage absorb +}; +enum WarriorSpecial +{ + STANCE_NONE = 0, + STANCE_BATTLE = 1, + STANCE_DEFENSIVE = 2, + STANCE_BERSERKER = 3, + + TASTE_FOR_BLOOD_BUFF = 60503, + SWORD_AND_BOARD_BUFF = 50227, + BLOODSURGE_BUFF = 46916,//"Slam!" + JUGGERNAUGHT_BUFF = 65156, + GLYPH_REVENGE_BUFF = 58363, + UNRELENTING_ASSAULT_SPELL = 64850, + VICTORIOUS_SPELL = 32216, + REVENGE_STUN_SPELL = 12798, + //SWORD_SPECIALIZATION_TRIGGERED = 16459, + VIGILANCE_PROC = 50725, + IMPROVED_BERSERKER_RAGE_EFFECT = 23691,//rank 2 + UNBRIDLED_WRATH_EFFECT = 12964, + SUNDER_ARMOR_DEBUFF = 58567, + GAG_ORDER_DEBUFF = 18498,//silence + //SUDDEN_DEATH_BUFF = 52437, + BLOODRAGE_PERIODIC_EFFECT = 29131, + + //VICTORIOUS_STATE_PASSIVE = 32215, + BERSERKER_STANCE_PASSIVE = 7381 +}; + +static uint32 Warrior_spells_damage_arr[] = +{ BLADESTORM_1, BLOODTHIRST_1, CLEAVE_1, CONCUSSION_BLOW_1, DEVASTATE_1, EXECUTE_1, HEROIC_STRIKE_1, HEROIC_THROW_1, +INTERCEPT_1, MOCKING_BLOW_1, MORTAL_STRIKE_1, OVERPOWER_1, REND_1, RETALIATION_1, REVENGE_1, SHATTERING_THROW_1, +SHIELD_SLAM_1, SHOCKWAVE_1, SLAM_1, THUNDER_CLAP_1, VICTORY_RUSH_1, WHIRLWIND_1 }; + +static uint32 Warrior_spells_cc_arr[] = +{ CHARGE_1, INTERCEPT_1, INTIMIDATING_SHOUT_1, CONCUSSION_BLOW_1, DISARM_1, HAMSTRING_1, PIERCING_HOWL_1, +SHIELD_BASH_1, SHOCKWAVE_1 }; + +static uint32 Warrior_spells_support_arr[] = +{ BATTLE_SHOUT_1, COMMANDING_SHOUT_1, CHALLENGING_SHOUT_1, DEMORALIZING_SHOUT_1, BERSERKER_RAGE_1, BLOODRAGE_1, +DEATH_WISH_1, ENRAGED_REGENERATION_1, HEROIC_FURY_1, INTERVENE_1, LAST_STAND_1, PUMMEL_1, RECKLESSNESS_1, +RETALIATION_1, SHIELD_BASH_1, SHIELD_BLOCK_1, SHIELD_WALL_1, SPELL_REFLECTION_1, SUNDER_ARMOR_1, SWEEPING_STRIKES_1, +TAUNT_1, VIGILANCE_1 }; + +static const std::vector Warrior_spells_damage(FROM_ARRAY(Warrior_spells_damage_arr)); +static const std::vector Warrior_spells_cc(FROM_ARRAY(Warrior_spells_cc_arr)); +static const std::vector Warrior_spells_support(FROM_ARRAY(Warrior_spells_support_arr)); + +static float rageIncomeMult; +static float rageLossMult; + +class warrior_bot : public CreatureScript +{ +public: + warrior_bot() : CreatureScript("warrior_bot") { } + + CreatureAI* GetAI(Creature* creature) const override + { + return new warrior_botAI(creature); + } +/* + bool OnGossipHello(Player* player, Creature* creature) + { + return creature->GetBotAI()->OnGossipHello(player, 0); + } + + bool OnGossipSelect(Player* player, Creature* creature, uint32 sender, uint32 action) + { + if (bot_ai* ai = creature->GetBotAI()) + return ai->OnGossipSelect(player, creature, sender, action); + return true; + } + + bool OnGossipSelectCode(Player* player, Creature* creature, uint32 sender, uint32 action, char const* code) + { + if (bot_ai* ai = creature->GetBotAI()) + return ai->OnGossipSelectCode(player, creature, sender, action, code); + return true; + } +*/ + struct warrior_botAI : public bot_ai + { + warrior_botAI(Creature* creature) : bot_ai(creature) + { + _botclass = BOT_CLASS_WARRIOR; + + InitUnitFlags(); + } + + bool doCast(Unit* victim, uint32 spellId) + { + if (CheckBotCast(victim, spellId) != SPELL_CAST_OK) + return false; + return bot_ai::doCast(victim, spellId); + } + + uint8 GetBotStance() const override + { + if (_inStance(1)) + return WARRIOR_BATTLE_STANCE; + else if (_inStance(2)) + return WARRIOR_DEFENSIVE_STANCE; + else if (_inStance(3)) + return WARRIOR_BERSERKER_STANCE; + + return BOT_STANCE_NONE; + } + + void StartAttack(Unit* u, bool force = false) + { + if (!bot_ai::StartAttack(u, force)) + return; + GetInPosition(force, u); + } + + void JustEnteredCombat(Unit* u) override { bot_ai::JustEnteredCombat(u); } + void KilledUnit(Unit* u) override + { + //Victorious State spell + //only on targets which give xp or honor + if (u->GetLevel() > Trinity::XP::GetGrayLevel(me->GetLevel())) + me->CastSpell(me, VICTORIOUS_SPELL, true); + + bot_ai::KilledUnit(u); + } + void EnterEvadeMode(EvadeReason why = EVADE_REASON_OTHER) override { bot_ai::EnterEvadeMode(why); } + void MoveInLineOfSight(Unit* u) override { bot_ai::MoveInLineOfSight(u); } + void JustDied(Unit* u) override { bot_ai::JustDied(u); } + void DoNonCombatActions(uint32 /*diff*/) { } + + //void modrage(int32 mod, bool set = false) + //{ + // if (set && mod < 0) + // return; + // if (mod < 0 && rage < abs(mod)) + // { + // //debug set rage to 0 + // mod = 0; + // set = true; + // return; + // } + + // if (set) + // rage = mod ? mod*10 : 0; + // else + // rage += mod*10; + + // me->SetPower(POWER_RAGE, rage); + //} + + void getrage() + { + rage = me->GetPower(POWER_RAGE); + if (me->FindCurrentSpellBySpellId(GetSpell(CLEAVE_1))) + rage = std::max(rage - 200, 0); + else if (me->FindCurrentSpellBySpellId(GetSpell(HEROIC_STRIKE_1))) + rage = std::max(rage - rcost(HEROIC_STRIKE_1), 0); + } + + int32 rcost(uint32 spellId) const + { + if (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId)) + return spellInfo->CalcPowerCost(me, spellInfo->GetSchoolMask()); + return 0; + } + + void BreakCC(uint32 diff) override + { + if (IsSpellReady(HEROIC_FURY_1, diff) && Rand() < 55 && + (CCed(me, true) || me->HasAuraWithMechanic(1<GetAuraEffect(SPELL_AURA_MECHANIC_IMMUNITY, SPELLFAMILY_WARRIOR, 0x0, 0x20000, 0x0) && + /*!me->HasAura(ENRAGED_REGENERATION_1)*/ + me->HasAuraWithMechanic((1<IsAlive()) + { + if (ragetimer2 <= diff) + { + ragetimer2 = 3000; + //Anger Management + if (me->IsInCombat() && me->GetLevel() >= 20) + { + if (me->GetPower(POWER_RAGE) < 990) + me->SetPower(POWER_RAGE, me->GetPower(POWER_RAGE) + uint32(10.f * rageIncomeMult)); //1 rage per 3 sec + else + me->SetPower(POWER_RAGE, 1000); //max + } + } + if (ragetimer <= diff) + { + ragetimer = 1500; + if (!me->IsInCombat() && + !me->HasAuraTypeWithFamilyFlags(SPELL_AURA_PERIODIC_ENERGIZE, SPELLFAMILY_WARRIOR, 0x100)) + { + if (me->GetPower(POWER_RAGE) > uint32(10.f * rageLossMult)) + me->SetPower(POWER_RAGE, me->GetPower(POWER_RAGE) - uint32(10.f * rageLossMult)); //-1 rage per 1.5 sec + else + me->SetPower(POWER_RAGE, 0); //min + } + } + getrage(); + } + + if (!GlobalUpdate(diff)) + return; + + DoVehicleActions(diff); + if (!CanBotAttackOnVehicle()) + return; + + if (IsPotionReady()) + { + if (GetHealthPCT(me) < 40) + DrinkPotion(false); + } + + CheckRacials(diff); + + CheckShouts(diff); + CheckVigilance(diff); + CheckIntervene(diff); + CheckSpellReflect(diff); + + if (me->IsInCombat()) + CheckShatteringThrow(diff); + else + DoNonCombatActions(diff); + + if (IsCasting()) + return; + + if (ProcessImmediateNonAttackTarget()) + return; + + if (!CheckAttackTarget()) + { + if (!me->IsInCombat() && stancetimer <= diff && Rand() < 5 && me->getAttackers().empty() && rage <= 250) + { + uint8 mystance = 0; + if (IsTank()) + { + if (!_inStance(2)) + mystance = 2; + } + else + mystance = 1; + + if (mystance) + stanceChange(diff, mystance); + } + return; + } + + CheckUsableItems(diff); + + Attack(diff); + } + + void Attack(uint32 diff) + { + Unit* mytar = opponent ? opponent : disttarget ? disttarget : nullptr; + if (!mytar) + return; + + StartAttack(mytar, IsMelee()); + + CheckAttackState(); + if (!me->IsAlive() || !mytar->IsAlive()) + return; + + bool const isFury = GetSpec() == BOT_SPEC_WARRIOR_FURY; + bool const isArms = GetSpec() == BOT_SPEC_WARRIOR_ARMS; + + //Keep stance in combat + if (stancetimer <= diff && Rand() < 10 + 15 * (me->GetPower(POWER_RAGE) <= 250)) + { + uint8 mystance; + if (IsTank()) + mystance = 2; + else if (isFury && me->GetLevel() >= 30) + mystance = 3; + else + mystance = 1; + + stanceChange(diff, mystance); + } + + if (IsSpellReady(BLOODRAGE_1, diff, false) && me->IsInCombat() && rage < 600 && Rand() < 20 && + !me->GetAuraEffect(SPELL_AURA_MECHANIC_IMMUNITY, SPELLFAMILY_WARRIOR, 0x0, 0x20000, 0x0) + /*!me->HasAura(ENRAGED_REGENERATION_1)*/) + { + if (doCast(me, GetSpell(BLOODRAGE_1))) + getrage(); + } + + getrage(); + + //SelfHeal + if (IsSpellReady(ENRAGED_REGENERATION_1, diff) && Rand() < 80 && GetHealthPCT(me) < 40 && + rage >= rcost(ENRAGED_REGENERATION_1) && me->HasAuraWithMechanic(1u<getAttackers(); + float dist = me->GetDistance(mytar); + + //FEAR + if (IsSpellReady(INTIMIDATING_SHOUT_1, diff) && Rand() < 70 && rage >= rcost(INTIMIDATING_SHOUT_1)) + { + if (mytar->IsNonMeleeSpellCast(false, false, true) && dist < 5 && + mytar->GetCreatureType() != CREATURE_TYPE_UNDEAD) + { + if (doCast(mytar, GetSpell(INTIMIDATING_SHOUT_1))) + return; + } + Unit::AttackerSet const& m_attackers = master->getAttackers(); + Unit* fearTarget = nullptr; + uint8 tCount = 0; + //fear master's attackers + if (!m_attackers.empty() && + ((master->GetClass() != BOT_CLASS_DEATH_KNIGHT && + master->GetClass() != BOT_CLASS_WARRIOR && + master->GetClass() != BOT_CLASS_PALADIN) || + GetHealthPCT(master) < 70)) + { + for (Unit::AttackerSet::const_iterator iter = m_attackers.begin(); iter != m_attackers.end(); ++iter) + { + if (!(*iter)) continue; + if ((*iter)->GetCreatureType() == CREATURE_TYPE_UNDEAD) continue; + if (me->GetDistance((*iter)) < 7.5f) + ++tCount; + if (!fearTarget && me->GetDistance((*iter)) < 5) + fearTarget = (*iter); + if (fearTarget && tCount > 1) + break; + } + if (fearTarget && tCount > 1 && doCast(fearTarget, GetSpell(INTIMIDATING_SHOUT_1))) + return; + } + //Defend myself + if (b_attackers.size() > 1 && (!IsTank() || GetHealthPCT(me) < 50)) + { + tCount = 0; + fearTarget = nullptr; + for (Unit::AttackerSet::const_iterator iter = b_attackers.begin(); iter != b_attackers.end(); ++iter) + { + if (!(*iter)) continue; + if ((*iter)->GetCreatureType() == CREATURE_TYPE_UNDEAD) continue; + if (me->GetDistance((*iter)) < 7.5f) + ++tCount; + if (!fearTarget && me->GetDistance((*iter)) < 5) + fearTarget = (*iter); + if (fearTarget && tCount > 1) + break; + } + if (fearTarget && tCount > 1 && doCast(fearTarget, GetSpell(INTIMIDATING_SHOUT_1))) + return; + } + }//end FEAR + + //LAST STAND + if (IsSpellReady(LAST_STAND_1, diff, false) && + GetHealthPCT(me) < (30 + 20 * (b_attackers.size() > 1) + 10 * me->HasAuraType(SPELL_AURA_PERIODIC_DAMAGE))) + { + if (doCast(me, GetSpell(LAST_STAND_1))) + return; + } + + Unit const* u = mytar->GetVictim(); + + //TAUNT //No GCD + if (IsSpellReady(TAUNT_1, diff, false) && u && u != me && Rand() < 50 && dist < 30 && + mytar->CanHaveThreatList() && !CCed(mytar) && !mytar->HasAuraType(SPELL_AURA_MOD_TAUNT) && + (!IsTank(u) || (IsTank() && GetHealthPCT(me) > 67 && + (GetHealthPCT(u) < 30 || (IsOffTank() && !IsOffTank(u) && IsPointedOffTankingTarget(mytar)) || + (!IsOffTank() && IsOffTank(u) && IsPointedTankingTarget(mytar))))) && + ((!IsTankingClass(u->GetClass()) && (GetHealthPCT(u) < 80 || _inStance(2))) || IsTank()) && + IsInBotParty(u) && + (_inStance(2) || (stancetimer <= diff && stanceChange(diff, 2)))) + { + if (doCast(mytar, GetSpell(TAUNT_1))) + return; + } + //TAUNT 2 (distant) + if (IsSpellReady(TAUNT_1, diff, false) && !IAmFree() && u == me && Rand() < 35 && IsTank() && + (IsOffTank() || master->GetBotMgr()->GetNpcBotsCountByRole(BOT_ROLE_TANK_OFF) == 0) && + !(me->GetLevel() >= 40 && mytar->GetTypeId() == TYPEID_UNIT && + (mytar->ToCreature()->IsDungeonBoss() || mytar->ToCreature()->isWorldBoss())) && + (_inStance(2) || stancetimer <= diff)) + { + Unit* tUnit = FindDistantTauntTarget(); + if (tUnit && (_inStance(2) || (stancetimer <= diff && stanceChange(diff, 2)))) + { + if (doCast(tUnit, GetSpell(TAUNT_1))) + return; + } + } + //CHARGE (warbringer) + if (IsSpellReady(CHARGE_1, diff, false) && !HasRole(BOT_ROLE_RANGED) && Rand() < 70 && + !HasBotCommandState(BOT_COMMAND_STAY) && + !(IsTank() && mytar->GetTypeId() == TYPEID_UNIT && mytar->ToCreature()->isWorldBoss()) && + dist > 8 && dist < CalcSpellMaxRange(CHARGE_1) && + ((IsTank() && me->GetLevel() >= 50) || + (!me->IsInCombat() && (_inStance(1) || (stancetimer <= diff && stanceChange(diff, 1)))))) + { + if (doCast(mytar, GetSpell(CHARGE_1))) + return; + } + //INTERCEPT (warbringer) + if (IsSpellReady(INTERCEPT_1, diff, false) && !HasRole(BOT_ROLE_RANGED) && HasRole(BOT_ROLE_DPS) && + !HasBotCommandState(BOT_COMMAND_STAY) && + !(IsTank() && mytar->GetTypeId() == TYPEID_UNIT && mytar->ToCreature()->isWorldBoss()) && + //!me->HasUnitState(UNIT_STATE_CHARGING) && + !(me->GetMotionMaster()->GetCurrentMovementGenerator() && me->GetMotionMaster()->GetCurrentMovementGenerator()->BaseUnitState == UNIT_STATE_CHARGING) && //not charging + (me->IsInCombat() || !IsSpellReady(CHARGE_1, diff, false)) && + Rand() < 60 && dist > 10 && dist < 25 && !CCed(mytar) && rage >= rcost(INTERCEPT_1) && + ((IsTank() && me->GetLevel() >= 50) || + (!IsTank() && (_inStance(3) || (stancetimer <= diff && stanceChange(diff, 3)))))) + { + if (doCast(mytar, GetSpell(INTERCEPT_1))) + return; + } + //CHALLENGING SHOUT + if (IsSpellReady(CHALLENGING_SHOUT_1, diff) && Rand() < 40 && + !(u == me && me->GetLevel() >= 40 && mytar->GetTypeId() == TYPEID_UNIT && + (mytar->ToCreature()->IsDungeonBoss() || mytar->ToCreature()->isWorldBoss())) && + rage >= rcost(CHALLENGING_SHOUT_1)) + { + if (IsTank()) + { + std::list targets; + GetNearbyTargetsList(targets, 9.f, 1); + uint8 count = 0; + for (std::list::const_iterator itr = targets.begin(); itr != targets.end(); ++itr) + { + if (!((*itr)->GetVictim() && IsTank((*itr)->GetVictim()))) + if (++count > 1) + break; + } + if (count > 1 && doCast(me, GetSpell(CHALLENGING_SHOUT_1))) + return; + } + if (u && u != me && !IsSpellReady(TAUNT_1, diff, false) && !IsTank(u) && !CCed(mytar) && dist < 9 && + (!IsTankingClass(u->GetClass()) || IsTank()) && IsInBotParty(u)) + { + if (doCast(me, GetSpell(CHALLENGING_SHOUT_1))) + return; + } + } + + bool can_do_normal = CanAffectVictimAny(mytar, SPELL_SCHOOL_NORMAL); + + //BERSERKER RAGE (for rage) + if (IsSpellReady(BERSERKER_RAGE_1, diff) && Rand() < 15 && rage < 80/* && me->GetLevel() >= 35*/) + { + if (doCast(me, GetSpell(BERSERKER_RAGE_1))) + return; + } + //MOCKING BLOW + if (IsSpellReady(MOCKING_BLOW_1, diff) && can_do_normal && HasRole(BOT_ROLE_DPS) && Rand() < 70 && u && u != me && + !IsTank(u) && dist < 5 && rage >= rcost(MOCKING_BLOW_1) && + !CCed(mytar) && (!IsTankingClass(u->GetClass()) || IsTank()) && IsInBotParty(u) && + (_inStance(4) || (stancetimer <= diff && stanceChange(diff, 4)))) + { + if (doCast(mytar, GetSpell(MOCKING_BLOW_1))) + return; + } + //SHIELD SLAM + if (IsSpellReady(SHIELD_SLAM_1, diff) && can_do_normal && HasRole(BOT_ROLE_DPS) && CanBlock() && + (_inStance(4) || stancetimer <= diff) && dist <= 5 && rage >= rcost(SHIELD_SLAM_1) && + Rand() < (75 + 200*(me->GetAuraEffect(SPELL_AURA_ADD_PCT_MODIFIER, SPELLFAMILY_WARRIOR, 2780, 0) != nullptr) + /*me->HasAura(SWORD_AND_BOARD_BUFF)*/)) + { + //check Shield Block + if (IsSpellReady(SHIELD_BLOCK_1, diff, false) && (_inStance(2) || (IsTank() && stanceChange(diff, 2)))) + { + if (doCast(me, GetSpell(SHIELD_BLOCK_1))) + {} + } + if (_inStance(4) || stanceChange(diff, 4)) + { + if (doCast(mytar, GetSpell(SHIELD_SLAM_1))) + return; + } + } + //SHIELD BLOCK + if (IsSpellReady(SHIELD_BLOCK_1, diff, false) && CanBlock() && Rand() < 70 && + (_inStance(2) || stancetimer <= diff) && + ((u == me && dist < 8) || (!b_attackers.empty() && me->GetDistance2d(*(b_attackers.begin())) < 8)) && + GetHealthPCT(me) < (65 + 8 * uint8(b_attackers.size()))) + { + if ((_inStance(2) || stanceChange(diff, 2)) && + doCast(me, GetSpell(SHIELD_BLOCK_1))) + return; + } + //SHOCKWAVE - frontal cone + if (IsSpellReady(SHOCKWAVE_1, diff) && can_do_normal && HasRole(BOT_ROLE_DPS) && dist < 8.f && !CCed(mytar) && + rage >= rcost(SHOCKWAVE_1) && Rand() < (70 + 70 * mytar->IsNonMeleeSpellCast(false)) && + me->HasInArc(float(M_PI)/2, mytar) && mytar->IsWithinLOSInMap(me, LINEOFSIGHT_ALL_CHECKS, VMAP::ModelIgnoreFlags::M2)) + { + if (doCast(me, GetSpell(SHOCKWAVE_1))) + return; + } + //HEROIC THROW + if (IsSpellReady(HEROIC_THROW_1, diff) && can_do_normal && HasRole(BOT_ROLE_DPS) && dist < 30 && + (mytar->GetTypeId() == TYPEID_UNIT || dist > 6) && + Rand() < (20 - 15 * CanBlock() + 90 * mytar->IsNonMeleeSpellCast(false,false,true))) + { + if (doCast(mytar, GetSpell(HEROIC_THROW_1))) + return; + } + //THUNDER CLAP + if (IsSpellReady(THUNDER_CLAP_1, diff) && HasRole(BOT_ROLE_DPS) && !isFury && Rand() < 40 && + (_inStance(4) || stancetimer <= diff) && dist < 7.5f && rage >= rcost(THUNDER_CLAP_1) && + ((IsTank() && b_attackers.size() > 1) || + (mytar->GetHealth() > me->GetMaxHealth() / 2 && + !mytar->HasAuraTypeWithFamilyFlags(SPELL_AURA_MOD_MELEE_HASTE, SPELLFAMILY_WARRIOR, 0x80)) || + FindSplashTarget(7.5f, mytar, 15.f))) + { + if (_inStance(4) || (me->GetLevel() >= 20 && stanceChange(diff, 4))) + { + if (doCast(me, GetSpell(THUNDER_CLAP_1))) + return; + } + } + //REVENGE + if (IsSpellReady(REVENGE_1, diff) && can_do_normal && HasRole(BOT_ROLE_DPS) && IsTank() && me->HasReactive(REACTIVE_DEFENSE) && + Rand() < 150 && (_inStance(2) || stancetimer <= diff) && dist < 5 && rage >= rcost(REVENGE_1)) + { + if (_inStance(2) || stanceChange(diff, 2)) + { + if (doCast(mytar, GetSpell(REVENGE_1))) + return; + } + } + //CONCUSSION BLOW + if (IsSpellReady(CONCUSSION_BLOW_1, diff) && can_do_normal && HasRole(BOT_ROLE_DPS) && !CCed(mytar) && + dist < 5 && rage >= rcost(CONCUSSION_BLOW_1) && + mytar->GetDiminishing(DIMINISHING_STUN) <= DIMINISHING_LEVEL_2 && + Rand() < (30 + 60 * mytar->IsNonMeleeSpellCast(false,false,true))) + { + if (doCast(mytar, GetSpell(CONCUSSION_BLOW_1))) + return; + } + + MoveBehind(mytar); + + //SHIELD BASH - shared cd with pummel + if (IsSpellReady(SHIELD_BASH_1, diff, false) && can_do_normal && CanBlock() && Rand() < 80 && + (_inStance(4) || stancetimer <= diff) && + dist < 5 && rage >= rcost(SHIELD_BASH_1) && mytar->IsNonMeleeSpellCast(false,false,true)) + { + if ((_inStance(4) || stanceChange(diff, 4)) && + doCast(mytar, GetSpell(SHIELD_BASH_1))) + return; + } + //PUMMEL - shared cd with shield bash + if (IsSpellReady(PUMMEL_1, diff, false) && can_do_normal && !IsTank() && !CanBlock() && Rand() < 80 && + dist < 5 && (_inStance(3) || stancetimer <= diff) && + rage >= rcost(PUMMEL_1) && mytar->IsNonMeleeSpellCast(false,false,true)) + { + if ((_inStance(3) || stanceChange(diff, 3)) && + doCast(mytar, GetSpell(PUMMEL_1))) + return; + } + //HAMSTRING + if (IsSpellReady(HAMSTRING_1, diff) && can_do_normal && Rand() < 70 && (_inStance(5) || stancetimer <= diff) && + (!GetSpell(PIERCING_HOWL_1) || mytar->GetTypeId() == TYPEID_PLAYER) && + (mytar->isMoving() || mytar->GetTypeId() == TYPEID_PLAYER) && dist < 5 && rage >= rcost(HAMSTRING_1) && + !mytar->HasAuraWithMechanic(1<GetLevel() >= 15 && stanceChange(diff, 5))) + if (doCast(mytar, GetSpell(HAMSTRING_1))) + return; + } + //PIERCING HOWL + if (IsSpellReady(PIERCING_HOWL_1, diff) && can_do_normal && mytar->isMoving() && Rand() < 80 && + dist < 9 && rage >= rcost(PIERCING_HOWL_1) && !mytar->HasAuraWithMechanic(1<HasAuraType(SPELL_AURA_ALLOW_ONLY_ABILITY)) && + !mytar->HasAuraType(SPELL_AURA_MOD_DISARM) && + mytar->GetHealth() > me->GetMaxHealth() / 8 * (1 + mytar->getAttackers().size()) && + rage >= rcost(DISARM_1)) + { + //check weapons + bool hasWeapon = true; + if (mytar->GetTypeId() == TYPEID_UNIT && !mytar->GetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID)) + hasWeapon = false; + else if (Player const* pla = mytar->ToPlayer()) + if (!pla->GetWeaponForAttack(BASE_ATTACK) || !pla->IsUseEquipedWeapon(true)) + hasWeapon = false; + + if (hasWeapon && (_inStance(2) || stanceChange(diff, 2)) && + doCast(mytar, GetSpell(DISARM_1))) + return; + } + //DEMORALIZING SHOUT + if (IsSpellReady(DEMORALIZING_SHOUT_1, diff) && can_do_normal && Rand() < 15 + 25 * IsTank() && dist < 10 && + (mytar->GetClass() == CLASS_WARRIOR || mytar->GetClass() == CLASS_ROGUE || + (mytar->GetTypeId() == TYPEID_UNIT && mytar->ToCreature()->GetCreatureTemplate()->rank != CREATURE_ELITE_NORMAL)) && + mytar->GetHealth() > me->GetMaxHealth() / 8 * (1 + mytar->getAttackers().size()) && + rage >= rcost(DEMORALIZING_SHOUT_1) && + !mytar->HasAuraTypeWithFamilyFlags(SPELL_AURA_MOD_ATTACK_POWER, SPELLFAMILY_WARRIOR, 0x20000)) + { + if (doCast(me, GetSpell(DEMORALIZING_SHOUT_1))) + return; + } + + //UBERS + //Shield Wall + if (IsSpellReady(SHIELD_WALL_1, diff, false) && CanBlock() && + GetHealthPCT(me) < (30 + 4 * b_attackers.size() + 20 * (mytar->GetTypeId() == TYPEID_UNIT && mytar->ToCreature()->isWorldBoss())) && + (_inStance(2) || stanceChange(diff, 2))) + { + if (doCast(me, GetSpell(SHIELD_WALL_1))) + return; + } + //Retaliation + if (IsSpellReady(RETALIATION_1, diff) && HasRole(BOT_ROLE_DPS) && !CanBlock() && Rand() < 40 && + !me->HasAuraType(SPELL_AURA_MOD_DISARM) && b_attackers.size() > 4 && + (_inStance(1) || stanceChange(diff, 1))) + { + if (doCast(me, GetSpell(RETALIATION_1))) + return; + } + //Recklessness + if (IsSpellReady(RECKLESSNESS_1, diff) && can_do_normal && HasRole(BOT_ROLE_DPS) && !CanBlock() && !IsTank() && Rand() < 60 && + GetHealthPCT(me) > 50 && (_inStance(3) || stancetimer <= diff) && b_attackers.size() < 2 && + (mytar->GetHealth() > me->GetHealth()/2 * (1 + mytar->getAttackers().size()) || mytar->IsControlledByPlayer()) && + !me->GetAuraEffect(SPELL_AURA_MECHANIC_IMMUNITY, SPELLFAMILY_WARRIOR, 0x0, 0x20000, 0x0) + /*!me->HasAura(ENRAGED_REGENERATION_1)*/ && + (_inStance(3) || stanceChange(diff, 3))) + { + if (doCast(me, GetSpell(RECKLESSNESS_1))) + return; + } + //DEATHWISH + if (IsSpellReady(DEATH_WISH_1, diff) && can_do_normal && HasRole(BOT_ROLE_DPS) && !IsTank() && Rand() < 70 && + dist < 15 && rage >= rcost(DEATH_WISH_1) && + mytar->GetHealth() > me->GetHealth()/4 * (1 + mytar->getAttackers().size()) && + !me->GetAuraEffect(SPELL_AURA_MECHANIC_IMMUNITY, SPELLFAMILY_WARRIOR, 0x0, 0x20000, 0x0) + /*!me->HasAura(ENRAGED_REGENERATION_1)*/) + { + if (doCast(me, GetSpell(DEATH_WISH_1))) + return; + } + + //VICTORY RUSH + if (IsSpellReady(VICTORY_RUSH_1, diff) && can_do_normal && HasRole(BOT_ROLE_DPS) && Rand() < 70 && dist < 5 && _inStance(5) && + me->GetAuraEffect(SPELL_AURA_DUMMY, SPELLFAMILY_WARRIOR, 0x0, 0x40000, 0x0)) + { + if (doCast(mytar, GetSpell(VICTORY_RUSH_1))) + return; + } + //DEVASTATE - only with shield + if (IsSpellReady(DEVASTATE_1, diff) && can_do_normal && HasRole(BOT_ROLE_DPS) && CanBlock() && Rand() < 100 && + dist < 5 && rage >= rcost(DEVASTATE_1)) + { + if (doCast(mytar, GetSpell(DEVASTATE_1))) + return; + } + //SUNDER ARMOR + if (IsSpellReady(SUNDER_ARMOR_1, diff) && !IAmFree() && can_do_normal && dist < 5 && Rand() < 45 && + (IsTank() ? (mytar->GetHealth() > me->GetMaxHealth()) : (Rand() < 25 && mytar->GetHealth() > me->GetMaxHealth() * 2)) && + (!HasRole(BOT_ROLE_DPS) || !CanBlock() || !GetSpell(DEVASTATE_1)) && + (IsTank() || master->GetBotMgr()->HasBotWithSpec(BOT_SPEC_WARRIOR_PROTECTION, false)) && rage >= rcost(SUNDER_ARMOR_1)) + { + AuraEffect const* sunder = mytar->GetAuraEffect(SUNDER_ARMOR_DEBUFF, 0); + if ((!sunder || sunder->GetBase()->GetStackAmount() < 5 || sunder->GetBase()->GetDuration() < 20000) && + doCast(mytar, GetSpell(SUNDER_ARMOR_1))) + return; + } + //SWEEPING STRIKES //no GCD + if (IsSpellReady(SWEEPING_STRIKES_1, diff, false) && HasRole(BOT_ROLE_DPS) && !IsTank() && Rand() < 65 && + (_inStance(5) || stancetimer <= diff) && rage >= rcost(SWEEPING_STRIKES_1) && + (b_attackers.size() > 1 || FindSplashTarget(7, mytar))) + { + if ((_inStance(5) || stanceChange(diff, 5)) && + doCast(me, GetSpell(SWEEPING_STRIKES_1))) + getrage(); + } + //REND + if (IsSpellReady(REND_1, diff) && can_do_normal && HasRole(BOT_ROLE_DPS) && Rand() < 80 && + mytar->GetHealth() > me->GetMaxHealth() / 4 * (1 + mytar->getAttackers().size()) && + (isArms || mytar->GetClass() == CLASS_ROGUE || mytar->GetShapeshiftForm() == FORM_CAT) && + dist < 5 && rage >= rcost(REND_1) && mytar->GetCreatureType() != CREATURE_TYPE_MECHANICAL && + !(mytar->GetTypeId() == TYPEID_UNIT && + (mytar->ToCreature()->GetCreatureTemplate()->MechanicImmuneMask & (1<<(MECHANIC_BLEED-1)))) && + !mytar->GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_WARRIOR, 0x20, 0x0, 0x0, me->GetGUID()) && + (_inStance(4) || (me->GetLevel() >= 15 && stanceChange(diff, 4)))) + { + if (doCast(mytar, GetSpell(REND_1))) + return; + } + //BLOODTHIRST + if (IsSpellReady(BLOODTHIRST_1, diff) && can_do_normal && HasRole(BOT_ROLE_DPS) && + dist < 5 && rage >= rcost(BLOODTHIRST_1)) + { + if (doCast(mytar, GetSpell(BLOODTHIRST_1))) + return; + } + //MORTAL STRIKE + if (IsSpellReady(MORTAL_STRIKE_1, diff) && can_do_normal && HasRole(BOT_ROLE_DPS) && !CanBlock() && + dist < 5 && rage >= rcost(MORTAL_STRIKE_1)) + { + if (doCast(mytar, GetSpell(MORTAL_STRIKE_1))) + return; + } + //OVERPOWER + if (IsSpellReady(OVERPOWER_1, diff) && can_do_normal && HasRole(BOT_ROLE_DPS) && !IsTank() && (!isFury || rage < 250) && + (_inStance(1) || stancetimer <= diff) && dist < 5 && rage >= rcost(OVERPOWER_1) && + (me->HasReactive(REACTIVE_OVERPOWER) || + me->GetAuraEffect(SPELL_AURA_ABILITY_IGNORE_AURASTATE, SPELLFAMILY_WARRIOR, 2961, 0) + /*me->HasAura(TASTE_FOR_BLOOD_BUFF)*/)) + { + if (_inStance(1) || (me->GetLevel() >= 15 && stanceChange(diff, 1))) + { + if (doCast(mytar, GetSpell(OVERPOWER_1))) + return; + } + } + //BLADESTORM + if (IsSpellReady(BLADESTORM_1, diff) && can_do_normal && HasRole(BOT_ROLE_DPS) && !IsTank() && !CanBlock() && + dist < 10 && rage >= rcost(BLADESTORM_1) && + (b_attackers.size() > 1 || mytar->GetHealth() > me->GetHealth() / 3 * (1 + mytar->getAttackers().size()) || + mytar->IsControlledByPlayer()) && + (Rand() < 50 || me->HasAuraTypeWithFamilyFlags(SPELL_AURA_ADD_FLAT_MODIFIER, SPELLFAMILY_WARRIOR, 0x10) + /*me->HasAura(RECKLESSNESS_1)*/) && + (me->GetMap()->IsDungeon() || mytar->GetMaxHealth() > me->GetMaxHealth() * 8 || CCed(mytar, true) || mytar->HasAuraWithMechanic(1<IsControlledByPlayer() || me->GetLevel() < 60 || !me->GetMap()->IsDungeon()) && + (_inStance(3) || stancetimer <= diff) && dist < 6.f && + rage >= rcost(WHIRLWIND_1) && (isFury || rage >= 500 || FindSplashTarget(7.f, mytar, 15.f))) + { + if ((_inStance(3) || stanceChange(diff, 3)) && + doCast(me, GetSpell(WHIRLWIND_1))) + return; + } + //EXECUTE + if (IsSpellReady(EXECUTE_1, diff) && can_do_normal && HasRole(BOT_ROLE_DPS) && !IsTank() && Rand() < 110 && + (isFury || !me->GetMap()->IsRaid()) && + (mytar->HasAuraState(AURA_STATE_HEALTHLESS_20_PERCENT) || + me->GetAuraEffect(SPELL_AURA_ABILITY_IGNORE_AURASTATE, SPELLFAMILY_WARRIOR, 0x0, 0x2000000, 0x0)) && + dist < 5 && rage >= rcost(EXECUTE_1) && + (_inStance(5) || (stancetimer <= diff && stanceChange(diff, 5)))) + { + if (doCast(mytar, GetSpell(EXECUTE_1))) + return; + } + //SLAM only with improved, has SPELL_ATTR2_NOT_RESET_AUTO_ACTIONS + if (IsSpellReady(SLAM_1, diff) && can_do_normal && HasRole(BOT_ROLE_DPS) && !IsTank() && !CanBlock() && + me->GetLevel() >= 40 && dist < 5 && rage >= rcost(SLAM_1) && + ((isArms && !mytar->isMoving() && me->getAttackTimer(BASE_ATTACK) > 500) || + me->GetAuraEffect(SPELL_AURA_ADD_PCT_MODIFIER, SPELLFAMILY_WARRIOR, 0x0, 0x1000000, 0x0)) + /*me->HasAura(BLOODSURGE_BUFF)*/) + { + if (doCast(mytar, GetSpell(SLAM_1))) + return; + } + + //skip if already have cleave of heroic strike casted + if (me->GetCurrentSpell(CURRENT_MELEE_SPELL)) + return; + + //CLEAVE //no GCD + if (IsSpellReady(CLEAVE_1, diff, false) && can_do_normal && HasRole(BOT_ROLE_DPS) && Rand() < 70 && + dist < 5 && (!IsTank() || rage >= 500) && rage >= rcost(CLEAVE_1) && FindSplashTarget()) + { + if (doCast(mytar, GetSpell(CLEAVE_1))) + return; + } + //HEROIC STRIKE + if (IsSpellReady(HEROIC_STRIKE_1, diff, false) && can_do_normal && HasRole(BOT_ROLE_DPS) && Rand() < 55 && rage >= 350 && + dist < 5 && (isFury || IsTank() || rage >= 650) && rage >= rcost(HEROIC_STRIKE_1)) + { + if (doCast(mytar, GetSpell(HEROIC_STRIKE_1))) + return; + } + } + + void CheckShouts(uint32 diff) + { + if (shoutCheckTimer > diff || GC_Timer > diff || Rand() > 35 || (IAmFree() && !IsWanderer()) || me->IsMounted() || IsCasting() || + (rage < rcost(BATTLE_SHOUT_1) && !IsSpellReady(BLOODRAGE_1, diff, false))) + return; + + shoutCheckTimer = urand(3000, 5000); + + if (IAmFree()) + { + if (GetSpell(BATTLE_SHOUT_1) && + !me->GetAuraEffect(SPELL_AURA_MOD_RANGED_ATTACK_POWER, SPELLFAMILY_WARRIOR, 0x10000, 0x0, 0x0) && + !me->GetAuraEffect(SPELL_AURA_MOD_RANGED_ATTACK_POWER, SPELLFAMILY_PALADIN, 0x2, 0x0, 0x0)) + { + if (rage < rcost(BATTLE_SHOUT_1)) + { + if (IsSpellReady(BLOODRAGE_1, diff, false)) + { + if (doCast(me, GetSpell(BLOODRAGE_1))) + {} + else + return; + } + else + return; + } + if (doCast(me, GetSpell(BATTLE_SHOUT_1))) + return; + } + + return; + } + + if (me->GetDistance(master) > 30) + return; + + //ignore Blood Pact + AuraEffect const* bs = me->GetAuraEffect(SPELL_AURA_MOD_RANGED_ATTACK_POWER, SPELLFAMILY_WARRIOR, 0x10000, 0x0, 0x0); + AuraEffect const* cs = me->GetAuraEffect(SPELL_AURA_230, SPELLFAMILY_WARRIOR, 0x0, 0x80, 0x0); + AuraEffect const* bm = me->GetAuraEffect(SPELL_AURA_MOD_RANGED_ATTACK_POWER, SPELLFAMILY_PALADIN, 0x2, 0x0, 0x0); + + bool hasBS = bs && (bs->GetBase()->GetDuration() >= 30000 || bs->GetBase()->GetCasterGUID() != me->GetGUID()) && bs->GetBase()->GetId() >= GetSpell(BATTLE_SHOUT_1); + bool hasCS = cs && (cs->GetBase()->GetDuration() >= 30000 || cs->GetBase()->GetCasterGUID() != me->GetGUID()) && cs->GetBase()->GetId() >= GetSpell(COMMANDING_SHOUT_1); + bool hasBM = bm != nullptr; + + if (hasCS && (hasBS || hasBM)) + return; + + bool battleshout = !hasBM && !hasBS && (!cs || cs->GetBase()->GetCasterGUID() != me->GetGUID()) && + (!IsTank(me) || !GetSpell(COMMANDING_SHOUT_1)) && GetSpell(BATTLE_SHOUT_1); + bool commandingshout = !hasCS && (!bs || bs->GetBase()->GetCasterGUID() != me->GetGUID()) && + GetSpell(COMMANDING_SHOUT_1); + + if (battleshout && !hasCS && !HasRole(BOT_ROLE_DPS) && GetSpell(COMMANDING_SHOUT_1)) + { + battleshout = false; + commandingshout = true; + } + + if (battleshout || commandingshout) + { + if (rage < rcost(BATTLE_SHOUT_1) && IsSpellReady(BLOODRAGE_1, diff, false) && + doCast(me, GetSpell(BLOODRAGE_1))) + getrage(); + + if ((battleshout && doCast(me, GetSpell(BATTLE_SHOUT_1))) || + (commandingshout && doCast(me, GetSpell(COMMANDING_SHOUT_1)))) + return; + } + } + + void CheckVigilance(uint32 diff) + { + if (vigiCheckTimer > diff || Rand() > 30 || !IsSpellReady(VIGILANCE_1, diff) || me->IsInCombat() || me->IsMounted() || IsCasting()) + return; + + vigiCheckTimer = urand(1500, 3000); + uint32 VIGILANCE = GetSpell(VIGILANCE_1); + + if (Unit* u = vigilanceTargetGuid ? ObjectAccessor::GetUnit(*me, vigilanceTargetGuid) : nullptr) + { + bool myVig = u->HasAura(VIGILANCE, me->GetGUID()); + if (!IsTank() || !myVig) + { + if (myVig) + u->RemoveAura(VIGILANCE, me->GetGUID(), 0, AURA_REMOVE_BY_EXPIRE); + vigilanceTargetGuid = ObjectGuid::Empty; + } + return; + } + else if (vigilanceTargetGuid) + vigilanceTargetGuid = ObjectGuid::Empty; + + if (!IAmFree() && !IsTank()) + return; + + Unit* target = nullptr; + if (Group const* gr = GetGroup()) + { + std::set targets; + for (uint8 i = 0; i < 4 && !targets.empty(); ++i) + { + for (Unit* member : BotMgr::GetAllGroupMembers(gr)) + { + if (!(!(i & 1) ? member->IsPlayer() : member->IsNPCBot()) || me->GetMap() != member->FindMap() || + !member->IsAlive() || me->GetDistance(member) > 30 || + (member->IsNPCBot() && member->ToCreature()->IsTempBot()) || + (i < 2 && !(i == 0 ? IsTankingClass(member->GetClass()) : IsTank(member))) || + (i == 3 && !member->ToCreature()->GetBotAI()->HasRole(BOT_ROLE_DPS)) || + member->HasAura(VIGILANCE) || member->HasAura(DAMAGE_REDUCTION)) + continue; + targets.insert(member); + } + } + + if (!targets.empty()) + target = targets.size() == 1 ? *targets.begin() : Trinity::Containers::SelectRandomContainerElement(targets); + } + + if (!target && !IAmFree() && master->IsAlive() && me->IsWithinDistInMap(master, 30) && !master->HasAura(VIGILANCE)) + target = master; + + if (target && doCast(target, VIGILANCE)) + return; + } + + void CheckIntervene(uint32 diff) + { + if (!IsSpellReady(INTERVENE_1, diff, false) || HasBotCommandState(BOT_COMMAND_STAY) || me->IsMounted() || + Rand() > (IsTank() ? 40 : 80) || rage < rcost(INTERVENE_1) || IsCasting() || + !(_inStance(2) || stancetimer <= diff || (GetSpec() == BOT_SPEC_WARRIOR_PROTECTION && me->GetLevel() >= 50))) + return; + + Unit* target = nullptr; + if (!me->GetVictim() && master->getAttackers().empty() && master->isMoving()) + { + float mydist = me->GetDistance(master); + if (mydist < 25 && mydist > 18) + target = master; + } + + Group const* gr = GetGroup(); + if (!target && !gr) + { + if (GetHealthPCT(master) < 95 && !master->getAttackers().empty() && + me->getAttackers().size() <= master->getAttackers().size()) + { + float mydist = me->GetDistance(master); + if (mydist < 25 && mydist > 8) + target = master; + } + } + if (!target && gr && (!IsTank() || !me->GetVictim())) + { + std::set targets; + for (Unit* member : BotMgr::GetAllGroupMembers(gr)) + { + if (me->GetMap() == member->FindMap() && member->IsAlive() && GetHealthPCT(member) <= 70 && + !member->HasAuraType(SPELL_AURA_ADD_CASTER_HIT_TRIGGER) && + member->getAttackers().size() >= me->getAttackers().size() && + !(member->IsNPCBot() && member->ToCreature()->IsTempBot())) + { + float dist = me->GetDistance(member); + if (dist < 25 && dist > 8) + targets.insert(member); + } + } + if (!targets.empty()) + target = targets.size() == 1u ? *targets.begin() : Trinity::Containers::SelectRandomContainerElement(targets); + } + + if (target && (_inStance(2) || (GetSpec() == BOT_SPEC_WARRIOR_PROTECTION && me->GetLevel() >= 50) || stanceChange(diff, 2)) && + doCast(target, GetSpell(INTERVENE_1))) + return; + + SetSpellCooldown(INTERVENE_1, 500); //fail + } + + void CheckSpellReflect(uint32 diff) + { + if (!IsSpellReady(SPELL_REFLECTION_1, diff, false) || me->IsMounted() || IsCasting() || + !CanBlock() || !(_inStance(4) || stancetimer <= diff) || + rage < rcost(SPELL_REFLECTION_1) || Rand() > 75) + return; + + //use first match (covers most cases) + if (Unit const* target = FindCastingTarget(70)) + { + if (Spell const* spell = target->GetCurrentSpell(CURRENT_GENERIC_SPELL)) + { + if (spell->GetTimer() < 500/*(4500 - 4000 * (target->GetTypeId() == TYPEID_PLAYER))*/ && + !spell->GetSpellInfo()->IsChanneled() && + spell->GetSpellInfo()->DmgClass == SPELL_DAMAGE_CLASS_MAGIC && + !(spell->GetSpellInfo()->Attributes & (SPELL_ATTR0_ABILITY|SPELL_ATTR0_UNAFFECTED_BY_INVULNERABILITY)) && + !(spell->GetSpellInfo()->AttributesEx & SPELL_ATTR1_CANT_BE_REFLECTED) && + !spell->GetSpellInfo()->IsPassive() && !spell->GetSpellInfo()->IsPositive()) + { + if (Unit const* u = spell->m_targets.GetUnitTarget()) + { + if ((IAmFree() ? (u == me) : (master->GetGroup() && master->GetGroup()->IsMember(spell->m_targets.GetObjectTargetGUID()))) && + me->GetTotalAuraModifier(SPELL_AURA_REFLECT_SPELLS) < 100) + { + if ((_inStance(4) || (stancetimer <= diff && stanceChange(diff, 4))) && + doCast(me, GetSpell(SPELL_REFLECTION_1))) + return; + } + } + } + } + } + + SetSpellCooldown(SPELL_REFLECTION_1, urand(250, 500)); //fail + } + + void CheckShatteringThrow(uint32 diff) + { + if (!IsSpellReady(SHATTERING_THROW_1, diff) || shatterCheckTimer > diff || + !(_inStance(1) || stancetimer <= diff) || rage < rcost(SHATTERING_THROW_1) || + me->getAttackers().size() > 2 || Rand() > 50) + return; + + shatterCheckTimer = urand(500, 1000); + + Unit* unit = FindImmunityShieldDispelTarget(); + if (unit && me->GetDistance(unit) < 30 && (_inStance(1) || (stancetimer <= diff && stanceChange(diff, 1)))) + if (doCast(unit, GetSpell(SHATTERING_THROW_1))) + return; + } + + bool stanceChange(uint32 diff, uint8 stance) + { + if (stancetimer > diff) + return false; + + if (stance == 5) + stance = (me->GetLevel() >= 30 && !IsTank()) ? 3 : 1; + else if (stance == 4) + stance = me->GetLevel() >= 10 && IsTank() ? 2 : 1; + + if (stance == 2 && me->GetLevel() < 10) + return false; + if (stance == 3 && me->GetLevel() < 30) + return false; + + if (_inStance(stance)) + return true; + + rage = me->GetPower(POWER_RAGE); + switch (stance) + { + case 1: + return doCast(me, BATTLE_STANCE_1); + case 2: + return doCast(me, DEFENSIVE_STANCE_1); + case 3: + return doCast(me, BERSERKER_STANCE_1); + default: + return false; + } + } + + void ApplyClassDamageMultiplierMelee(uint32& /*damage*/, CalcDamageInfo& damageinfo) const override + { + float pctbonus = 1.0f; + + if (damageinfo.HitOutCome == MELEE_HIT_CRIT) + { + //!!!Melee spell damage is not yet critical, all reduced by half + //Poleaxe Specialization: 5% additional critical damage for all attacks + if (GetSpec() == BOT_SPEC_WARRIOR_ARMS && me->GetLevel() >= 30) + if (Item const* weap = GetEquips(uint8(damageinfo.AttackType))) + if (ItemTemplate const* proto = weap->GetTemplate()) + if (proto->SubClass == ITEM_SUBCLASS_WEAPON_AXE || proto->SubClass == ITEM_SUBCLASS_WEAPON_AXE2 || + proto->SubClass == ITEM_SUBCLASS_WEAPON_POLEARM) + pctbonus *= 1.025f; + } + + damageinfo.Damages[0].Damage *= pctbonus; + } + + void ApplyClassSpellCritMultiplierAll(Unit const* /*victim*/, float& crit_chance, SpellInfo const* spellInfo, SpellSchoolMask /*schoolMask*/, WeaponAttackType attackType) const override + { + if (spellInfo->DmgClass != SPELL_DAMAGE_CLASS_MELEE) + return; + + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + uint8 lvl = me->GetLevel(); + + //Recklessness: 100% additional critical chance for damaging abilities + if (AuraEffect const* eff = me->GetAuraEffect(RECKLESSNESS_1, EFFECT_0)) + if (eff->IsAffectingSpell(spellInfo)) + crit_chance += 100.f; + //Juggernaught: 25 additional critical chance for Mortal Strike and Slam + if (lvl >= 45 && (baseId == SLAM_1 || baseId == MORTAL_STRIKE_1)) + if (AuraEffect const* jugg = me->GetAuraEffect(JUGGERNAUGHT_BUFF, 0)) + if (jugg->IsAffectingSpell(spellInfo)) + crit_chance += 25.f; + + //Poleaxe Specialization: 5% additional critical chance for all attacks + if (GetSpec() == BOT_SPEC_WARRIOR_ARMS && lvl >= 30) + if (Item const* weap = GetEquips(uint8(attackType))) + if (ItemTemplate const* proto = weap->GetTemplate()) + if (proto->SubClass == ITEM_SUBCLASS_WEAPON_AXE || proto->SubClass == ITEM_SUBCLASS_WEAPON_AXE2 || + proto->SubClass == ITEM_SUBCLASS_WEAPON_POLEARM) + crit_chance += 5.f; + + //Incite: 15% additional critical chance for Cleave, Heroic Strike and Thunder Clap + if (((GetSpec() == BOT_SPEC_WARRIOR_PROTECTION && lvl >= 15) || + ((GetSpec() == BOT_SPEC_WARRIOR_ARMS || GetSpec() == BOT_SPEC_WARRIOR_FURY) && lvl >= 75)) && + (baseId == CLEAVE_1 || baseId == HEROIC_STRIKE_1 || baseId == THUNDER_CLAP_1)) + crit_chance += 15.f; + //Improved Overpower: 50% additional critical chance for Overpower + if ((GetSpec() == BOT_SPEC_WARRIOR_ARMS) && lvl >= 20 && baseId == OVERPOWER_1) + crit_chance += 50.f; + //Critical Block: 15% additional critical chance for Shield Slam + if ((GetSpec() == BOT_SPEC_WARRIOR_PROTECTION) && + lvl >= 50 && baseId == SHIELD_SLAM_1) + crit_chance += 15.f; + //Sword and Board: 15% additional critical chance for Devastate + if (lvl >= 55 && baseId == DEVASTATE_1) + crit_chance += 15.f; + + //Glypg of Victory Rush: 30% additional critical chance for Victory Rush + if (lvl >= 15 && baseId == VICTORY_RUSH_1) + crit_chance += 30.f; + + //Warrior T8 Protection Bonus (id: 64933): 10% additional critical chance for Devastate (tanks only) + if (lvl >= 78 && baseId == DEVASTATE_1) + crit_chance += 10.f; + } + + void ApplyClassDamageMultiplierMeleeSpell(int32& damage, SpellNonMeleeDamage& /*damageinfo*/, SpellInfo const* spellInfo, WeaponAttackType attackType, bool iscrit) const override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + uint8 lvl = me->GetLevel(); + float fdamage = float(damage); + + // apply bonus damage mods + float pctbonus = 1.0f; + if (iscrit) + { + //!!!Melee spell damage is not yet critical, all reduced by half + //Impale: 20% crit damage bonus for all abilities + if (lvl >= 20) + pctbonus *= 1.1f; + //Poleaxe Specialization: 5% additional critical damage for all attacks + if ((GetSpec() == BOT_SPEC_WARRIOR_PROTECTION) && lvl >= 30) + if (Item const* weap = GetEquips(uint8(attackType))) + if (ItemTemplate const* proto = weap->GetTemplate()) + if (proto->SubClass == ITEM_SUBCLASS_WEAPON_AXE || proto->SubClass == ITEM_SUBCLASS_WEAPON_AXE2 || + proto->SubClass == ITEM_SUBCLASS_WEAPON_POLEARM) + pctbonus *= 1.025f; + } + + //Improved Shield Slam (id: 38407): 10% bonus damage for Shield Slam + //if (lvl >= 50 && baseId == SHIELD_SLAM_1) + // pctbonus *= 1.1f; + //Shield Slam Damage Up (id: 60173): 10% bonus damage for Shield Slam + //if (lvl >= 70 && baseId == SHIELD_SLAM_1) + // pctbonus *= 1.1f; + + //Improved Rend: 20% bonus damage for Rend + if (lvl >= 10 && baseId == REND_1) + pctbonus *= 1.2f; + //Improved Thunder Clap (part 2): 30% bonus damage for Thunder Clap + if (lvl >= 10 && baseId == THUNDER_CLAP_1) + pctbonus *= 1.3f; + //Improved Revenge (part 1): 60% bonus damage for Revenge + if ((GetSpec() == BOT_SPEC_WARRIOR_PROTECTION) && lvl >= 20 && baseId == REVENGE_1) + pctbonus *= 1.6f; + //Gag Order (part 2): 10% bonus damage for Shield Slam + if ((GetSpec() == BOT_SPEC_WARRIOR_PROTECTION) && lvl >= 30 && baseId == SHIELD_SLAM_1) + pctbonus *= 1.1f; + //Improved Whirlwind: 20% bonus damage for Whirlwind + if ((GetSpec() == BOT_SPEC_WARRIOR_FURY) && lvl >= 40 && baseId == WHIRLWIND_1) + pctbonus *= 1.2f; + //Improved Mortal Strike (part 1): 10% bonus damage for Mortal Strike + if (lvl >= 45 && baseId == MORTAL_STRIKE_1) + pctbonus *= 1.1f; + //Unrelenting Assault (part 2): 20% bonus damage for Overpower and Revenge + if ((GetSpec() == BOT_SPEC_WARRIOR_ARMS) && + lvl >= 45 && (baseId == OVERPOWER_1 || baseId == REVENGE_1)) + pctbonus *= 1.2f; + //Unending Fury: 10% bonus damage for Whirlwind, Slam and Bloodthirst + if ((GetSpec() == BOT_SPEC_WARRIOR_FURY) && + lvl >= 55 && (baseId == WHIRLWIND_1 || baseId == SLAM_1 || baseId == BLOODTHIRST_1)) + pctbonus *= 1.1f; + + //Glyph of Mocking Blow: 25% bonus damage for Mocking Blow + if (lvl >= 16 && baseId == MOCKING_BLOW_1) + pctbonus *= 1.25f; + //Glyph of Mortal Strike: 10% bonus damage for Mortal Strike + if (lvl >= 40 && baseId == MORTAL_STRIKE_1) + pctbonus *= 1.1f; + + //Warrior T9 Protection 2P Bonus (id: 67269): 5% bonus damage for Devastate + if (lvl >= 77 && baseId == DEVASTATE_1) + pctbonus *= 1.05f; + //Warrior T10 Protection 2P Bonus (id: 70843): 20% bonus damage for Shield Slam and Shockwave + if (lvl >= 78 && (baseId == SHIELD_SLAM_1 || baseId == SHOCKWAVE_1)) + pctbonus *= 1.2f; + + //Improved Cleave: 120% increased '!bonus damage!' done by Cleave (flat mod) + if ((GetSpec() == BOT_SPEC_WARRIOR_FURY) && lvl >= 25 && baseId == CLEAVE_1) + { + float bp = spellInfo->_effects[EFFECT_0].BasePoints; //SPELL_EFFECT_WEAPON_DAMAGE (values: 15 - 222) + fdamage += bp * 1.2; + } + + damage = int32(fdamage * pctbonus); + } + + void ApplyClassSpellCostMods(SpellInfo const* spellInfo, int32& cost) const override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + uint8 lvl = me->GetLevel(); + float fcost = float(cost); + //float pctbonus = 1.0f; + + //100% mods + //Sword and Board: -100% rage cost for Shield Slam + if (baseId == SHIELD_SLAM_1 && me->HasAura(SWORD_AND_BOARD_BUFF)) + fcost = 0; + + //Glyph of Bloodrage: -100% health cost for Bloodrage + if (lvl >= 15 && baseId == BLOODRAGE_1) + fcost = 0; + //Glyph of Revenge: -100% rage cost for Heroic Strike + if (lvl >= 15 && baseId == HEROIC_STRIKE_1 && me->HasAura(GLYPH_REVENGE_BUFF)) + fcost = 0; + //Glyph of Sweeping Strikes: -100% rage cost for Sweeping Strikes + if (lvl >= 30 && baseId == SWEEPING_STRIKES_1) + fcost = 0; + + //flat mods + //Improved Hamstring (id: 24428): -2 rage cost for Hamstring + if (lvl >= 25 && baseId == HAMSTRING_1) + fcost -= 20; + //Bloodthirst and Mortal Strike Discount (id: 37535): -5 rage cost for Bloodthirst and Mortal Strike + if (lvl >= 40 && (baseId == BLOODTHIRST_1 || baseId == MORTAL_STRIKE_1)) + fcost -= 50; + + //Improved Heroic Strike: -3 rage cost for Heroic Strike + if (lvl >= 10 && baseId == HEROIC_STRIKE_1) + fcost -= 30; + //Improved Thunder Clap (part 1): -4 rage cost for Thunder Clap + if (lvl >= 10 && baseId == THUNDER_CLAP_1) + fcost -= 40; + //Improved Execute: -5 rage cost for Execute + if ((GetSpec() == BOT_SPEC_WARRIOR_FURY) && + lvl >= 25 && baseId == EXECUTE_1) + fcost -= 50; + //Puncture: -3 rage cost for Sunder Armor and Devastate + if ((GetSpec() == BOT_SPEC_WARRIOR_PROTECTION) && + lvl >= 25 && (baseId == SUNDER_ARMOR_1 || baseId == DEVASTATE_1)) + fcost -= 30; + //Focused Rage: -3 rage cost for all offensive abilities (using rage) + if ((GetSpec() == BOT_SPEC_WARRIOR_PROTECTION) && + lvl >= 40 && ((spellInfo->SpellFamilyFlags[0] & 0x6E6E4EEE) || (spellInfo->SpellFamilyFlags[1] & 0x40E664))) + fcost -= 30; + + //Glyph of Resonating Power: -5 rage cost for Thunder Clap + if (lvl >= 15 && baseId == THUNDER_CLAP_1) + fcost -= 50; + //Glyph of Shockwave: -3 rage cost for Shockwave + if (lvl >= 60 && baseId == SHOCKWAVE_1) + fcost -= 30; + + //cost can be < 0 + cost = int32(fcost/* * pctbonus*/); + } + + void ApplyClassSpellCastTimeMods(SpellInfo const* spellInfo, int32& casttime) const override + { + //casttime is in milliseconds + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + uint8 lvl = me->GetLevel(); + //int32 timebonus = 0; + //float pctbonus = 0.0f; + + //100% mods + //Bloodsurge: -100% cast time for Slam + if (baseId == SLAM_1 && me->HasAura(BLOODSURGE_BUFF)) + casttime = 0; + + //flat mods + //Improved Slam: -1.0 sec cast time for Slam + if ((GetSpec() == BOT_SPEC_WARRIOR_ARMS) && + lvl >= 40 && baseId == SLAM_1) + casttime -= 1000; + + casttime = std::max(casttime, 0); + } + + void ApplyClassSpellCooldownMods(SpellInfo const* spellInfo, uint32& cooldown) const override + { + //cooldown is in milliseconds + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + uint8 lvl = me->GetLevel(); + float pctbonus = 1.0f; + + //pct mods + //Intensify Rage: -33% cooldown for Bloodrage, Berserker Rage, Recklessness and Death Wish + if ((GetSpec() == BOT_SPEC_WARRIOR_FURY) && lvl >= 40 && + (baseId == BLOODRAGE_1 || baseId == BERSERKER_RAGE_1 || baseId == RECKLESSNESS_1 || baseId == DEATH_WISH_1)) + pctbonus *= 0.67f; + + //flat mods + //zzzOLDImproved Challenging Shout (id: 12327): -2 min cooldown for Challenging Shout (tanks only) + if (lvl >= 30 && IsTank() && baseId == CHALLENGING_SHOUT_1) + cooldown -= 120000; + + //Shield Mastery (part 2): -20 sec cooldown for Shield Block + if ((GetSpec() == BOT_SPEC_WARRIOR_PROTECTION) && + lvl >= 20 && baseId == SHIELD_BLOCK_1) + cooldown -= 20000; + //Improved Disciplines: -60 sec cooldown for Shield Wall, Retaliation and Recklessness + if ((GetSpec() == BOT_SPEC_WARRIOR_PROTECTION) && + lvl >= 35 && (baseId == SHIELD_WALL_1 || baseId == RETALIATION_1 || baseId == RECKLESSNESS_1)) + cooldown -= 60000; + + //Glyph of Bladestorm: -15 sec cooldown for Bladestorm + if (lvl >= 60 && baseId == BLADESTORM_1) + cooldown -= 15000; + //Glyph of Spell Reflection: -1 sec cooldown for Spell Reflection + if (lvl >= 64 && baseId == SPELL_REFLECTION_1) + cooldown -= 1000; + + cooldown = std::max(cooldown * pctbonus, 0.f); + } + + void ApplyClassSpellCategoryCooldownMods(SpellInfo const* spellInfo, uint32& cooldown) const override + { + //cooldown is in milliseconds + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + uint8 lvl = me->GetLevel(); + float pctbonus = 1.0f; + + //pct bonuses + //Glyph of Rapid Charge: -7% cooldown for Charge + if (lvl >= 15 && baseId == CHARGE_1) + pctbonus *= 0.93f; + + //flat bonuses + //Improved Disarm part 1: -20 sec cooldown for Disarm + if ((GetSpec() == BOT_SPEC_WARRIOR_PROTECTION) && + lvl >= 25 && baseId == DISARM_1) + cooldown -= 20000; + //Improved Intercept: -10 sec cooldown for Intercept + if ((GetSpec() == BOT_SPEC_WARRIOR_FURY) && + lvl >= 30 && baseId == INTERCEPT_1) + cooldown -= 10000; + //Improved Mortal Strike (part 2): -1 sec cooldown for Mortal Strike + if (lvl >= 45 && baseId == MORTAL_STRIKE_1) + cooldown -= 1000; + //Unrelenting Assault (part 1): -4 sec cooldown for Overpower and Revenge + if ((GetSpec() == BOT_SPEC_WARRIOR_ARMS) && + lvl >= 45 && (baseId == OVERPOWER_1 || baseId == REVENGE_1)) + cooldown -= 4000; + + //Glyph of Last Stand: -1 min cooldown for Last Stand + if (lvl >= 20 && baseId == LAST_STAND_1) + cooldown -= 60000; + //Glyph of Whirlwind: -2 sec cooldown for Whirlwind + if (lvl >= 36 && baseId == WHIRLWIND_1) + cooldown -= 2000; + + //Warrior T9 2P Bonus (id: 67269): -2 sec cooldown for Taunt (tanks only) + if (lvl >= 80 && IsTank() && baseId == TAUNT_1) + cooldown -= 2000; + + cooldown = std::max(cooldown * pctbonus, 0.f); + } + + void ApplyClassSpellGlobalCooldownMods(SpellInfo const* spellInfo, float& cooldown) const override + { + //cooldown is in milliseconds + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + uint8 lvl = me->GetLevel(); + + //Unrelenting Assault (part 1, special): -0.5 sec global cooldown for Overpower and Revenge + if ((GetSpec() == BOT_SPEC_WARRIOR_ARMS) && + lvl >= 45 && (baseId == OVERPOWER_1 || baseId == REVENGE_1)) + cooldown -= 500.f; + } + + void ApplyClassSpellRadiusMods(SpellInfo const* spellInfo, float& radius) const override + { + //uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + uint8 lvl = me->GetLevel(); + float pctbonus = 1.0f; + + //pct mods + //Booming Voice part 1 (doubled for bots) + if (lvl >= 10 && ((spellInfo->SpellFamilyFlags[0] & 0x30000) || (spellInfo->SpellFamilyFlags[1] & 0x80))) + pctbonus *= 2.0f; //1.5f + + //flat mods + //Glyph of Thunder Clap (doubled for tanks) + if (lvl >= 15 && (spellInfo->SpellFamilyFlags[0] & 0x80)) + radius += IsTank() ? 4.f : 2.f; + + radius = radius * pctbonus; + } + + void ApplyClassSpellRangeMods(SpellInfo const* spellInfo, float& maxrange) const override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + uint8 lvl = me->GetLevel(); + float pctbonus = 1.0f; + + //pct mods + //Holy Reach: +20% range for Holy Spells + //if (lvl >= 25 && (spellInfo->SpellFamilyFlags[0] & 0x100080)) + // pctbonus *= 1.2f; + + //flat mods + //Glyph of Charge: +5 yd range for Charge + if (baseId == CHARGE_1 && lvl >= 15) + maxrange += 5.f; + + maxrange = maxrange * pctbonus; + } + + void ApplyClassSpellMaxTargetsMods(SpellInfo const* spellInfo, uint32& targets) const override + { + //uint32 bonusTargets = 0; + uint8 lvl = me->GetLevel(); + + //Improved Revenge: +1 target (actually 2 in dbc) + if (lvl >= 20 && (spellInfo->SpellFamilyFlags[0] & 0x400)) + targets += 1; + + //Glyph of Sunder Armor: +1 target + if (lvl >= 15 && (spellInfo->SpellFamilyFlags[0] & 0x4000)) + targets += 1; + //Glyph of Cleaving: +1 target + if (lvl >= 20 && (spellInfo->SpellFamilyFlags[0] & 0x400000)) + { + targets += 1; + //double for non-tanks + if (!IsTank()) + targets += 1; + } + } + + void ApplyClassEffectMods(SpellInfo const* spellInfo, uint8 effIndex, float& value) const override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + uint8 lvl = me->GetLevel(); + float pctbonus = 1.0f; + + //Improved Rend: 20% increased effect + if (baseId == REND_1 && effIndex == EFFECT_0 && lvl >= 10) + pctbonus *= 1.2f; + //Improved Bloodrage: 50% increased effect + if ((baseId == BLOODRAGE_1 || baseId == BLOODRAGE_PERIODIC_EFFECT) && effIndex == EFFECT_0 && lvl >= 10) + pctbonus *= 1.5f; + //Improved Charge: +10 rage generated + if (baseId == CHARGE_1 && effIndex == EFFECT_1 && lvl >= 15) + value += 100.f; + //Glyph of Bloodthirst: +100% healing + if (baseId == BLOODTHIRST_1 && effIndex == EFFECT_1 && lvl >= 40) + pctbonus *= 2.0f; + + value = value * pctbonus; + } + + void OnClassSpellGo(SpellInfo const* spellInfo) override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + + if (baseId == LAST_STAND_1 && !IAmFree()) + ReportSpellCast(baseId, LocalizedNpcText(master, BOT_TEXT__USED), master); + if (baseId == SHIELD_WALL_1 && !IAmFree()) + ReportSpellCast(baseId, LocalizedNpcText(master, BOT_TEXT__USED), master); + if (baseId == ENRAGED_REGENERATION_1 && !IAmFree()) + ReportSpellCast(baseId, LocalizedNpcText(master, BOT_TEXT__USED), master); + + if (baseId == SLAM_1) + me->RemoveAura(BLOODSURGE_BUFF); + if (baseId == HEROIC_STRIKE_1) + me->RemoveAura(GLYPH_REVENGE_BUFF); + if (baseId == SHIELD_SLAM_1) + me->RemoveAura(SWORD_AND_BOARD_BUFF); + if (baseId == OVERPOWER_1 && !me->HasReactive(REACTIVE_OVERPOWER)) + me->RemoveAura(TASTE_FOR_BLOOD_BUFF); + if (baseId == BERSERKER_RAGE_1) + { + //Improved Berserker Rage: 20 rage bonus when used + if (me->GetLevel() >= 35) + me->CastSpell(me, IMPROVED_BERSERKER_RAGE_EFFECT, true); + } + } + + void SpellHitTarget(WorldObject* wtarget, SpellInfo const* spell) override + { + Unit* target = wtarget->ToUnit(); + if (!target) + return; + + uint32 spellId = spell->Id; + uint32 baseId = spell->GetFirstRankSpell()->Id; + uint8 lvl = me->GetLevel(); + + if (baseId == VIGILANCE_1) + vigilanceTargetGuid = target->GetGUID(); + + //Recklessness: handle charge drop + AuraEffect const* reck = me->GetAuraEffect(RECKLESSNESS_1, EFFECT_0); + if (reck && reck->IsAffectingSpell(spell)) + reck->GetBase()->DropCharge(); + //Juggernaught: consume buff + if (baseId == SLAM_1 || baseId == MORTAL_STRIKE_1) + if (AuraEffect const* jugg = me->GetAuraEffect(JUGGERNAUGHT_BUFF, 0)) + if (jugg->IsAffectingSpell(spell)) + me->RemoveAurasDueToSpell(JUGGERNAUGHT_BUFF); + + if (baseId == THUNDER_CLAP_1 && lvl >= 10) + { + if (AuraEffect* clap = target->GetAuraEffect(spellId, EFFECT_1, me->GetGUID())) + { + int32 amount = clap->GetAmount(); + //Improved Thunder Clap (part 3): 10% extra slow + amount += (-10); + //Conqueror Thunder Clap Bonus: 50% increased effect + if ((GetSpec() == BOT_SPEC_WARRIOR_PROTECTION) && lvl >= 60) + amount = amount + amount / 2; + + clap->ChangeAmount(amount); + } + } + if (baseId == DEMORALIZING_SHOUT_1 && lvl >= 15) + { + if (AuraEffect* demo = target->GetAuraEffect(spellId, 0, me->GetGUID())) + demo->ChangeAmount(demo->GetAmount() + demo->GetAmount() * 2 / 5); + } + if (baseId == BATTLE_SHOUT_1 || baseId == COMMANDING_SHOUT_1 || baseId == DEMORALIZING_SHOUT_1) + { + if (Aura* shout = target->GetAura(spellId, me->GetGUID())) + { + //Booming Voice part 2 + //Buffs duration 10 min for bots + uint32 dur = baseId == DEMORALIZING_SHOUT_1 ? shout->GetDuration() * 3 / 2 : 600000; + shout->SetDuration(dur); + shout->SetMaxDuration(dur); + + if (baseId == BATTLE_SHOUT_1 || baseId == COMMANDING_SHOUT_1) + { + if (lvl >= 20) + { + //Commanding Presence: +25% increased effect (melee AP / HP) + AuraEffect* bamm = shout->GetEffect(EFFECT_0); + if (bamm) + bamm->ChangeAmount(bamm->GetAmount() * 5 / 4); + } + } + else if (baseId == DEMORALIZING_SHOUT_1) + { + if (lvl >= 15) + { + //Improved Demoralization Shout: +40% effect + AuraEffect* demo = shout->GetEffect(EFFECT_0); + if (demo) + demo->ChangeAmount(demo->GetAmount() * 7 / 5); + } + } + } + } + if (baseId == REVENGE_1) + { + //zzzOLD Revenge Stun (25% chance): skip players + if (lvl >= 25 && target->GetTypeId() != TYPEID_PLAYER && urand(1,100) <= 25) + me->CastSpell(target, REVENGE_STUN_SPELL, true); + } + if (baseId == DISARM_1 && (GetSpec() == BOT_SPEC_WARRIOR_PROTECTION) && lvl >= 25) + { + //Improved Disarm part 2 + if (AuraEffect* disa = target->GetAuraEffect(spellId, 1, me->GetGUID())) + disa->ChangeAmount(disa->GetAmount() + 10); + } + if (baseId == OVERPOWER_1) + { + me->ClearReactive(REACTIVE_OVERPOWER); + //Unrelenting Assault (part 3): reduce spells efficiency on players + if (lvl >= 45 && (GetSpec() == BOT_SPEC_WARRIOR_ARMS) && + target->GetTypeId() == TYPEID_PLAYER && target->IsNonMeleeSpellCast(false, false, true)) + { + CastSpellExtraArgs args(true); + args.SetOriginalCaster(me->GetGUID()); + target->CastSpell(target, UNRELENTING_ASSAULT_SPELL, args); + } + } + if (baseId == REND_1 && lvl >= 15) + { + //Glyph of Rending + 6 sec duration + if (Aura* rend = target->GetAura(spellId, me->GetGUID())) + { + uint32 dur = rend->GetDuration() + 6000; + rend->SetDuration(dur); + rend->SetMaxDuration(dur); + } + } + if (baseId == INTERVENE_1) + { + //Glyph of Intervene + 1 bonus charge + if (Aura* vene = target->GetAura(spellId, me->GetGUID())) + vene->SetCharges(vene->GetCharges() + 1); + } + if (baseId == PIERCING_HOWL_1) + { + //Piercing Howl: 4 sec duraion increase (exclude players controlled) + if (!target->IsControlledByPlayer()) + { + if (Aura* howl = target->GetAura(spellId, me->GetGUID())) + { + uint32 dur = howl->GetDuration() + 4000; + howl->SetDuration(dur); + howl->SetMaxDuration(dur); + } + } + } + if (baseId == SHIELD_BASH_1 && (GetSpec() == BOT_SPEC_WARRIOR_PROTECTION) && lvl >= 30) + { + //Gag Order part 1: silence target + me->CastSpell(target, GAG_ORDER_DEBUFF, true); + } + if (baseId == VICTORY_RUSH_1) + { + //Victory rush disable helper + me->RemoveAura(VICTORIOUS_SPELL); + } + if ((baseId == DEVASTATE_1 || baseId == REVENGE_1) && + (GetSpec() == BOT_SPEC_WARRIOR_PROTECTION) && + lvl >= 55 && urand(1,100) <= 30) + { + //Sword and Board: trigger + me->CastSpell(me, SWORD_AND_BOARD_BUFF, true); + //Sword And Board: remove Shield Slam cooldown + ResetSpellCooldown(SHIELD_SLAM_1); + } + + OnSpellHitTarget(target, spell); + } + + void SpellHit(WorldObject* wcaster, SpellInfo const* spell) override + { + Unit* caster = wcaster->ToUnit(); + if (!caster) + return; + + uint32 spellId = spell->Id; + uint32 baseId = spell->GetFirstRankSpell()->Id; + uint8 lvl = me->GetLevel(); + + //Stances helper + if (spellId == BATTLE_STANCE_1 || spellId == DEFENSIVE_STANCE_1 || spellId == BERSERKER_STANCE_1) + { + stancetimer = 1000; + + //stance mastery, tactical mastery + uint32 temprage = 0; + if (lvl >= 20) + temprage = rage > 250 ? 250 : rage; + else if (lvl >= 15) + temprage = rage > 150 ? 150 : rage; + + _stance = + spellId == BATTLE_STANCE_1 ? STANCE_BATTLE : + spellId == DEFENSIVE_STANCE_1 ? STANCE_DEFENSIVE : + spellId == BERSERKER_STANCE_1 ? STANCE_BERSERKER : STANCE_NONE; + + me->SetPower(POWER_RAGE, temprage); + //Update stength bonus from Improved Berserker Stance + //if (lvl >= 45) + // SetStats(false); + } + + //Iron Will: -20% duration for stuns and charms + if ((GetSpec() == BOT_SPEC_WARRIOR_ARMS || GetSpec() == BOT_SPEC_WARRIOR_FURY) && + lvl >= 15 && !spell->IsPositive() && (spell->Mechanic == MECHANIC_STUN || spell->Mechanic == MECHANIC_CHARM)) + { + if (Aura* chun = me->GetAura(spellId, caster->GetGUID())) + { + uint32 dur = chun->GetDuration() - chun->GetDuration() / 5; + chun->SetDuration(dur); + chun->SetMaxDuration(dur); + } + } + //Glyph of Enduring Victory: +5 sec duration + if (lvl >= 62 && baseId == VICTORIOUS_SPELL) + { + if (Aura* vict = me->GetAura(spellId)) + { + uint32 dur = vict->GetDuration() + 5000; + vict->SetDuration(dur); + vict->SetMaxDuration(dur); + } + } + //Improved Berserker Stance part 2: threat mod + if (baseId == BERSERKER_STANCE_PASSIVE) + { + if (AuraEffect* pass = me->GetAuraEffect(spellId, EFFECT_2)) + pass->ChangeAmount(pass->GetAmount() - 10); + } + if (baseId == RETALIATION_1) + { + //Increase duration by 3 sec for bot + if (Aura* ret = me->GetAura(spellId, me->GetGUID())) + { + uint32 dur = ret->GetDuration() + 3000; + ret->SetDuration(dur); + ret->SetMaxDuration(dur); + } + } + if (baseId == VIGILANCE_PROC) //confirmed right place + { + //Vigilance: remove Taunt cooldown + ResetSpellCooldown(TAUNT_1); + } + if (baseId == SHIELD_WALL_1) + { + //Shield Wall Duration (id: 60175): 3 sec increased Shield Wall duration + if (Aura* wall = me->GetAura(spellId)) + { + int32 dur = wall->GetDuration() + 3000; + wall->SetDuration(dur); + wall->SetMaxDuration(dur); + } + } + + OnSpellHit(caster, spell); + } + + void DamageDealt(Unit* victim, uint32& damage, DamageEffectType damageType) override + { + //Unbridled Wrath + if ((GetSpec() == BOT_SPEC_WARRIOR_FURY || GetSpec() == BOT_SPEC_WARRIOR_ARMS) && + damage && me->GetLevel() >= 15 && me->CanDualWield() && + (damageType == DIRECT_DAMAGE || damageType == SPELL_DIRECT_DAMAGE)) + { + if (roll_chance_f(me->GetPPMProcChance(me->GetFloatValue(UNIT_FIELD_BASEATTACKTIME), 15.f, nullptr))) + me->CastSpell(me, UNBRIDLED_WRATH_EFFECT, true); + } + bot_ai::DamageDealt(victim, damage, damageType); + } + + void DamageTaken(Unit* u, uint32& /*damage*/, DamageEffectType /*damageType*/, SpellInfo const* /*spellInfo*/) override + { + if (!u) + return; + if (!u->IsInCombat() && !me->IsInCombat()) + return; + OnOwnerDamagedBy(u); + } + + void OwnerAttackedBy(Unit* u) override + { + if (!u) + return; + OnOwnerDamagedBy(u); + } + + void SetAIMiscValue(uint32 data, uint32 /*value*/) override + { + switch (data) + { + case BOTAI_MISC_WEAPON_SPEC: + { + //AXE and MACE specs are handled elsewhere + _checkSwordSpec(); + break; + } + default: + break; + } + } + + void Reset() override + { + stancetimer = 0; + ragetimer = 1500; + ragetimer2 = 3000; + shoutCheckTimer = 5000; + shatterCheckTimer = 5000; + vigiCheckTimer = 5000; + + vigilanceTargetGuid = ObjectGuid::Empty; + + _stance = STANCE_NONE; + + rageIncomeMult = sWorld->getRate(RATE_POWER_RAGE_INCOME); + rageLossMult = sWorld->getRate(RATE_POWER_RAGE_LOSS); + rage = 0; + + DefaultInit(); + } + + void ReduceCD(uint32 diff) override + { + if (stancetimer > diff) stancetimer -= diff; + if (ragetimer > diff) ragetimer -= diff; + if (ragetimer2 > diff) ragetimer2 -= diff; + if (shoutCheckTimer > diff) shoutCheckTimer -= diff; + if (shatterCheckTimer > diff) shatterCheckTimer -= diff; + if (vigiCheckTimer > diff) vigiCheckTimer -= diff; + } + + void InitPowers() override + { + me->SetPowerType(POWER_RAGE); + } + + void InitSpells() override + { + uint8 lvl = me->GetLevel(); + bool isArms = GetSpec() == BOT_SPEC_WARRIOR_ARMS; + bool isFury = GetSpec() == BOT_SPEC_WARRIOR_FURY; + bool isProt = GetSpec() == BOT_SPEC_WARRIOR_PROTECTION; + + InitSpellMap(BATTLE_STANCE_1); + /*Quest*/lvl >= 10 ? InitSpellMap(DEFENSIVE_STANCE_1) : RemoveSpell(DEFENSIVE_STANCE_1); + /*Quest*/lvl >= 30 ? InitSpellMap(BERSERKER_STANCE_1) : RemoveSpell(BERSERKER_STANCE_1); + + /*Quest*/lvl >= 10 ? InitSpellMap(TAUNT_1) : RemoveSpell(TAUNT_1); + /*Quest*/lvl >= 10 ? InitSpellMap(SUNDER_ARMOR_1) : RemoveSpell(SUNDER_ARMOR_1); + InitSpellMap(INTIMIDATING_SHOUT_1); + InitSpellMap(ENRAGED_REGENERATION_1); + InitSpellMap(CHARGE_1); + InitSpellMap(OVERPOWER_1); + InitSpellMap(BLOODRAGE_1); + InitSpellMap(BERSERKER_RAGE_1); + InitSpellMap(INTERCEPT_1); + InitSpellMap(CLEAVE_1); + InitSpellMap(HAMSTRING_1); + InitSpellMap(INTERVENE_1); + InitSpellMap(WHIRLWIND_1); + InitSpellMap(BATTLE_SHOUT_1); + InitSpellMap(REND_1); + InitSpellMap(EXECUTE_1); + InitSpellMap(PUMMEL_1); + InitSpellMap(SLAM_1); + InitSpellMap(RECKLESSNESS_1); + InitSpellMap(RETALIATION_1); + InitSpellMap(VICTORY_RUSH_1); + InitSpellMap(THUNDER_CLAP_1); + InitSpellMap(REVENGE_1); + InitSpellMap(SHIELD_BLOCK_1); + InitSpellMap(SHIELD_SLAM_1); + InitSpellMap(SPELL_REFLECTION_1); + InitSpellMap(DISARM_1); + InitSpellMap(SHIELD_WALL_1); + InitSpellMap(SHIELD_BASH_1); + InitSpellMap(HEROIC_THROW_1); + InitSpellMap(MOCKING_BLOW_1); + InitSpellMap(HEROIC_STRIKE_1); + InitSpellMap(CHALLENGING_SHOUT_1); + InitSpellMap(COMMANDING_SHOUT_1); + InitSpellMap(SHATTERING_THROW_1); + InitSpellMap(DEMORALIZING_SHOUT_1); + + /*Talent*/lvl >= 30 && isArms ? InitSpellMap(SWEEPING_STRIKES_1) : RemoveSpell(SWEEPING_STRIKES_1); + /*Talent*/lvl >= 40 && isArms ? InitSpellMap(MORTAL_STRIKE_1) : RemoveSpell(MORTAL_STRIKE_1); + /*Talent*/lvl >= 60 && isArms ? InitSpellMap(BLADESTORM_1) : RemoveSpell(BLADESTORM_1); + + /*Talent*/lvl >= (isFury ? 20 : isArms ? 70 : 99) ? InitSpellMap(PIERCING_HOWL_1) : RemoveSpell(PIERCING_HOWL_1); + /*Talent*/lvl >= 30 && isFury ? InitSpellMap(DEATH_WISH_1) : RemoveSpell(DEATH_WISH_1); + /*Talent*/lvl >= 40 && isFury ? InitSpellMap(BLOODTHIRST_1) : RemoveSpell(BLOODTHIRST_1); + /*Talent*/lvl >= 50 && isFury ? InitSpellMap(HEROIC_FURY_1) : RemoveSpell(HEROIC_FURY_1); + + /*Talent*/lvl >= 20 && isProt ? InitSpellMap(LAST_STAND_1) : RemoveSpell(LAST_STAND_1); + /*Talent*/lvl >= 30 && isProt ? InitSpellMap(CONCUSSION_BLOW_1) : RemoveSpell(CONCUSSION_BLOW_1); + /*Talent*/lvl >= 40 && isProt ? InitSpellMap(VIGILANCE_1) : RemoveSpell(VIGILANCE_1); + /*Talent*/lvl >= 50 && isProt ? InitSpellMap(DEVASTATE_1) : RemoveSpell(DEVASTATE_1); + /*Talent*/lvl >= 60 && isProt ? InitSpellMap(SHOCKWAVE_1) : RemoveSpell(SHOCKWAVE_1); + } + + void ApplyClassPassives() const override + { + uint8 level = master->GetLevel(); + bool isArms = GetSpec() == BOT_SPEC_WARRIOR_ARMS; + bool isFury = GetSpec() == BOT_SPEC_WARRIOR_FURY; + bool isProt = GetSpec() == BOT_SPEC_WARRIOR_PROTECTION; + + RefreshAura(DEEP_WOUNDS_3, (isArms || isFury) && level >= 24 ? 1 : 0); + RefreshAura(DEEP_WOUNDS_2, (isArms || isFury) && level >= 23 && level < 24 ? 1 : 0); + RefreshAura(DEEP_WOUNDS_1, (isArms || isFury) && level >= 22 && level < 23 ? 1 : 0); + RefreshAura(TWO_HANDED_WEAPON_SPECIALIZATION, isArms && level >= 25 ? 1 : 0); + RefreshAura(TASTE_FOR_BLOOD3, isArms && level >= 27 ? 1 : 0); + RefreshAura(TASTE_FOR_BLOOD2, isArms && level >= 26 && level < 27 ? 1 : 0); + RefreshAura(TASTE_FOR_BLOOD1, isArms && level >= 25 && level < 26 ? 1 : 0); + RefreshAura(IMPROVED_HAMSTRING, isArms && level >= 15 ? 1 : 0); + RefreshAura(TRAUMA2, isArms && level >= 36 ? 1 : 0); + RefreshAura(TRAUMA1, isArms && level >= 35 && level < 36 ? 1 : 0); + RefreshAura(SECOND_WIND, isArms && level >= 40 ? 1 : 0); + RefreshAura(JUGGERNAUGHT, isArms && level >= 45 ? 1 : 0); + RefreshAura(SUDDEN_DEATH, isArms && level >= 50 ? 1 : 0); + RefreshAura(ENDLESS_RAGE, isArms && level >= 50 ? 1 : 0); + RefreshAura(BLOOD_FRENZY, isArms && level >= 50 ? 1 : 0); + RefreshAura(WRECKING_CREW, isArms && level >= 55 ? 1 : 0); + _checkSwordSpec(); + + RefreshAura(ARMORED_TO_THE_TEETH, level >= 10 ? 1 : 0); + RefreshAura(BLOOD_CRAZE3, (isArms || isFury) && level >= 22 ? 1 : 0); + RefreshAura(BLOOD_CRAZE2, (isArms || isFury) && level >= 21 && level < 22 ? 1 : 0); + RefreshAura(BLOOD_CRAZE1, (isArms || isFury) && level >= 20 && level < 21 ? 1 : 0); + RefreshAura(DUAL_WIELD_SPECIALIZATION, level >= 25 ? 1 : 0); + RefreshAura(FLURRY5, isFury && level >= 39 ? 1 : 0); + RefreshAura(FLURRY4, isFury && level >= 38 && level < 39 ? 1 : 0); + RefreshAura(FLURRY3, isFury && level >= 37 && level < 38 ? 1 : 0); + RefreshAura(FLURRY2, isFury && level >= 36 && level < 37 ? 1 : 0); + RefreshAura(FLURRY1, isFury && level >= 35 && level < 36 ? 1 : 0); + RefreshAura(FURIOUS_ATTACKS, isFury && level >= 45 ? 1 : 0); + RefreshAura(RAMPAGE, !IAmFree() && isFury && level >= 50 ? 1 : 0); + RefreshAura(BLOODSURGE, isFury && level >= 50 ? 1 : 0); + + RefreshAura(SHIELD_SPECIALIZATION, isProt && level >= 10 ? 1 : 0); + RefreshAura(TOUGHNESS, isProt && level >= 20 ? 1 : 0); + RefreshAura(IMPROVED_SPELL_REFLECTION, isProt && level >= 25 ? 1 : 0); + RefreshAura(ONE_HANDED_WEAPON_SPECIALIZATION, isProt && level >= 35 ? 1 : 0); + RefreshAura(IMPROVED_DEFENSIVE_STANCE, isProt && level >= 40 ? 1 : 0); + RefreshAura(SAFEGUARD, isProt && level >= 45 ? 1 : 0); + RefreshAura(WARBRINGER, isProt && level >= 50 ? 1 : 0); + RefreshAura(CRITICAL_BLOCK, isProt && level >= 50 ? 1 : 0); + RefreshAura(DAMAGE_SHIELD, isProt && level >= 55 ? 1 : 0); + + RefreshAura(GLYPH_HEROIC_STRIKE, level >= 15 ? 1 : 0); + RefreshAura(GLYPH_REVENGE, level >= 15 ? 1 : 0); + RefreshAura(GLYPH_EXECUTION, level >= 24 ? 1 : 0); + RefreshAura(GLYPH_BLOCKING, level >= 40 ? 1 : 0); + RefreshAura(GLYPH_VIGILANCE, level >= 40 ? 1 : 0); + RefreshAura(GLYPH_DEVASTATE, level >= 50 ? 1 : 0); + + RefreshAura(WARRIOR_T10_PROT_4P, level >= 70 ? 1 : 0); + } + + bool CanUseManually(uint32 basespell) const override + { + switch (basespell) + { + case BLOODRAGE_1: + case BERSERKER_RAGE_1: + case BATTLE_SHOUT_1: + case COMMANDING_SHOUT_1: + case DEATH_WISH_1: + return true; + case ENRAGED_REGENERATION_1: + return me->HasAuraWithMechanic(1u<GetLevel() >= 75) + bonus += 6.f; + } + + //Mace Specialization: 15% armor penetration + if (GetSpec() == BOT_SPEC_WARRIOR_ARMS && me->GetLevel() >= 30) + if (Item const* weap = GetEquips(BOT_SLOT_MAINHAND)) + if (ItemTemplate const* proto = weap->GetTemplate()) + if (proto->SubClass == ITEM_SUBCLASS_WEAPON_MACE || proto->SubClass == ITEM_SUBCLASS_WEAPON_MACE2) + bonus += 15.f; + + return bonus + bot_ai::GetBotArmorPenetrationCoef(); + } + + std::vector const* GetDamagingSpellsList() const override + { + return &Warrior_spells_damage; + } + std::vector const* GetCCSpellsList() const override + { + return &Warrior_spells_cc; + } + //std::vector const* GetHealingSpellsList() const override + //{ + // return &Warrior_spells_heal; + //} + std::vector const* GetSupportSpellsList() const override + { + return &Warrior_spells_support; + } + + private: + bool _inStance(uint8 stance) const + { + switch (stance) + { + case 1: return _stance == STANCE_BATTLE; + case 2: return _stance == STANCE_DEFENSIVE; + case 3: return _stance == STANCE_BERSERKER; + case 4: return _stance == STANCE_BATTLE || _stance == STANCE_DEFENSIVE; + case 5: return _stance == STANCE_BATTLE || _stance == STANCE_BERSERKER; + default: return false; + } + } + + void _checkSwordSpec() const + { + uint8 level = me->GetLevel(); + bool isArms = GetSpec() == BOT_SPEC_WARRIOR_ARMS; + Item const* mhWeap = GetEquips(BOT_SLOT_MAINHAND); + uint32 weaponSubClass = mhWeap ? mhWeap->GetTemplate()->SubClass : uint32(ITEM_SUBCLASS_WEAPON_WAND); + bool sword = (weaponSubClass == ITEM_SUBCLASS_WEAPON_SWORD || weaponSubClass == ITEM_SUBCLASS_WEAPON_SWORD2); + RefreshAura(SWORD_SPEC5, isArms && sword && level >= 34 ? 1 : 0); + RefreshAura(SWORD_SPEC4, isArms && sword && level >= 33 && level < 34 ? 1 : 0); + RefreshAura(SWORD_SPEC3, isArms && sword && level >= 32 && level < 33 ? 1 : 0); + RefreshAura(SWORD_SPEC2, isArms && sword && level >= 31 && level < 32 ? 1 : 0); + RefreshAura(SWORD_SPEC1, isArms && sword && level >= 30 && level < 31 ? 1 : 0); + } + +/*tmrs*/uint32 stancetimer, ragetimer, ragetimer2, shoutCheckTimer, shatterCheckTimer, vigiCheckTimer; +/*misc*/int32 rage; +/*misc*/ObjectGuid vigilanceTargetGuid; +/*stnc*/uint8 _stance; + }; +}; + +void AddSC_warrior_bot() +{ + new warrior_bot(); +} diff --git a/src/server/game/AI/NpcBots/botcommands.cpp b/src/server/game/AI/NpcBots/botcommands.cpp new file mode 100644 index 000000000..ced98f03f --- /dev/null +++ b/src/server/game/AI/NpcBots/botcommands.cpp @@ -0,0 +1,4453 @@ +#include "Bag.h" +#include "bot_ai.h" +#include "botdatamgr.h" +#include "botdump.h" +#include "botgearscore.h" +#include "botlog.h" +#include "botmgr.h" +#include "botwanderful.h" +#include "CharacterCache.h" +#include "Chat.h" +#include "Containers.h" +#include "Creature.h" +#include "DatabaseEnv.h" +#include "DBCStores.h" +#include "GameClient.h" +#include "Group.h" +#include "Item.h" +#include "Language.h" +#include "Log.h" +#include "Map.h" +#include "MapManager.h" +#include "ObjectAccessor.h" +#include "ObjectMgr.h" +#include "Player.h" +#include "QueryPackets.h" +#include "RBAC.h" +#include "ScriptMgr.h" +#include "SpellInfo.h" +#include "SpellMgr.h" +#include "Spell.h" +#include "TemporarySummon.h" +#include "Vehicle.h" +#include "World.h" +#include "WorldDatabase.h" +#include "WorldSession.h" + +/* +Name: script_bot_commands +%Complete: ??? +Comment: Npc Bot related commands by Trickerer (onlysuffering@gmail.com) +Category: commandscripts/custom/ +*/ + +#ifdef _MSC_VER +# pragma warning(push, 4) +#endif + +static bool isWPSpawnWarningGiven = false; + +using namespace Trinity::ChatCommands; + +static uint32 last_model_id = 0; + +class script_bot_commands : public CommandScript +{ +private: + static constexpr size_t SOUND_SETS_COUNT = 3; + static constexpr size_t GENDERS_COUNT = 2; + static constexpr size_t RACES_COUNT = 10; + + // model ids with different sound sets tied to them + enum SoundSetModels : uint32 + { + SOUNDSETMODEL_HUMAN_MALE_1 = 1492, + SOUNDSETMODEL_HUMAN_MALE_2 = 1290, + SOUNDSETMODEL_HUMAN_MALE_3 = 1699, + SOUNDSETMODEL_HUMAN_FEMALE_1 = 1295, + SOUNDSETMODEL_HUMAN_FEMALE_2 = 1296, + SOUNDSETMODEL_HUMAN_FEMALE_3 = 1297, + SOUNDSETMODEL_DWARF_MALE_1 = 1280, + SOUNDSETMODEL_DWARF_MALE_2 = 1354, + SOUNDSETMODEL_DWARF_MALE_3 = 1362, + SOUNDSETMODEL_DWARF_FEMALE_1 = 1286, + SOUNDSETMODEL_DWARF_FEMALE_2 = 1407, + SOUNDSETMODEL_DWARF_FEMALE_3 = 2585, + SOUNDSETMODEL_NIGHTELF_MALE_1 = 1285, + SOUNDSETMODEL_NIGHTELF_MALE_2 = 1704, + SOUNDSETMODEL_NIGHTELF_MALE_3 = 1706, + SOUNDSETMODEL_NIGHTELF_FEMALE_1 = 1681, + SOUNDSETMODEL_NIGHTELF_FEMALE_2 = 1682, + SOUNDSETMODEL_NIGHTELF_FEMALE_3 = 1719, + SOUNDSETMODEL_GNOME_MALE_1 = 1832, + SOUNDSETMODEL_GNOME_MALE_2 = 4287, + SOUNDSETMODEL_GNOME_MALE_3 = 4717, + SOUNDSETMODEL_GNOME_FEMALE_1 = 3124, + SOUNDSETMODEL_GNOME_FEMALE_2 = 5378, + SOUNDSETMODEL_GNOME_FEMALE_3 = 3108, + SOUNDSETMODEL_DRAENEI_MALE_1 = 16226, + SOUNDSETMODEL_DRAENEI_MALE_2 = 16589, + SOUNDSETMODEL_DRAENEI_MALE_3 = 16224, + SOUNDSETMODEL_DRAENEI_FEMALE_1 = 16222, + SOUNDSETMODEL_DRAENEI_FEMALE_2 = 16202, + SOUNDSETMODEL_DRAENEI_FEMALE_3 = 16636, + SOUNDSETMODEL_ORC_MALE_1 = 1275, + SOUNDSETMODEL_ORC_MALE_2 = 1326, + SOUNDSETMODEL_ORC_MALE_3 = 1368, + SOUNDSETMODEL_ORC_FEMALE_1 = 1325, + SOUNDSETMODEL_ORC_FEMALE_2 = 1868, + SOUNDSETMODEL_ORC_FEMALE_3 = 1874, + SOUNDSETMODEL_UNDEAD_MALE_1 = 1278, + SOUNDSETMODEL_UNDEAD_MALE_2 = 1562, + SOUNDSETMODEL_UNDEAD_MALE_3 = 1578, + SOUNDSETMODEL_UNDEAD_FEMALE_1 = 1592, + SOUNDSETMODEL_UNDEAD_FEMALE_2 = 1593, + SOUNDSETMODEL_UNDEAD_FEMALE_3 = 1603, + SOUNDSETMODEL_TAUREN_MALE_1 = 2083, + SOUNDSETMODEL_TAUREN_MALE_2 = 2087, + SOUNDSETMODEL_TAUREN_MALE_3 = 2096, + SOUNDSETMODEL_TAUREN_FEMALE_1 = 2113, + SOUNDSETMODEL_TAUREN_FEMALE_2 = 2112, + SOUNDSETMODEL_TAUREN_FEMALE_3 = 2127, + SOUNDSETMODEL_TROLL_MALE_1 = 3608, + SOUNDSETMODEL_TROLL_MALE_2 = 4047, + SOUNDSETMODEL_TROLL_MALE_3 = 4068, + SOUNDSETMODEL_TROLL_FEMALE_1 = 4085, + SOUNDSETMODEL_TROLL_FEMALE_2 = 4231, + SOUNDSETMODEL_TROLL_FEMALE_3 = 4524, + SOUNDSETMODEL_BLOODELF_MALE_1 = 15532, + SOUNDSETMODEL_BLOODELF_MALE_2 = 16700, + SOUNDSETMODEL_BLOODELF_MALE_3 = 16699, + SOUNDSETMODEL_BLOODELF_FEMALE_1 = 15514, + SOUNDSETMODEL_BLOODELF_FEMALE_2 = 15518, + SOUNDSETMODEL_BLOODELF_FEMALE_3 = 15520, + }; + + static constexpr size_t RaceToRaceOffset[MAX_RACES] = { + RACE_NONE, + 0, //RACE_HUMAN + 5, //RACE_ORC + 1, //RACE_DWARF + 2, //RACE_RACE_NIGHTELF + 6, //RACE_RACE_UNDEAD_PLAYER + 7, //RACE_TAUREN + 3, //RACE_GNOME + 8, //RACE_TROLL + RACE_NONE, + 9, //RACE_BLOODELF + 4, //RACE_DRAENEI + }; + + static constexpr uint32 SoundSetModelsArray[RACES_COUNT][GENDERS_COUNT][SOUND_SETS_COUNT] = { + {{SOUNDSETMODEL_HUMAN_MALE_1, SOUNDSETMODEL_HUMAN_MALE_2, SOUNDSETMODEL_HUMAN_MALE_3}, {SOUNDSETMODEL_HUMAN_FEMALE_1, SOUNDSETMODEL_HUMAN_FEMALE_2, SOUNDSETMODEL_HUMAN_FEMALE_3}}, + {{SOUNDSETMODEL_DWARF_MALE_1, SOUNDSETMODEL_DWARF_MALE_2, SOUNDSETMODEL_DWARF_MALE_3}, {SOUNDSETMODEL_DWARF_FEMALE_1, SOUNDSETMODEL_DWARF_FEMALE_2, SOUNDSETMODEL_DWARF_FEMALE_3}}, + {{SOUNDSETMODEL_NIGHTELF_MALE_1, SOUNDSETMODEL_NIGHTELF_MALE_2, SOUNDSETMODEL_NIGHTELF_MALE_3}, {SOUNDSETMODEL_NIGHTELF_FEMALE_1, SOUNDSETMODEL_NIGHTELF_FEMALE_2, SOUNDSETMODEL_NIGHTELF_FEMALE_3}}, + {{SOUNDSETMODEL_GNOME_MALE_1, SOUNDSETMODEL_GNOME_MALE_2, SOUNDSETMODEL_GNOME_MALE_3}, {SOUNDSETMODEL_GNOME_FEMALE_1, SOUNDSETMODEL_GNOME_FEMALE_2, SOUNDSETMODEL_GNOME_FEMALE_3}}, + {{SOUNDSETMODEL_DRAENEI_MALE_1, SOUNDSETMODEL_DRAENEI_MALE_2, SOUNDSETMODEL_DRAENEI_MALE_3}, {SOUNDSETMODEL_DRAENEI_FEMALE_1, SOUNDSETMODEL_DRAENEI_FEMALE_2, SOUNDSETMODEL_DRAENEI_FEMALE_3}}, + {{SOUNDSETMODEL_ORC_MALE_1, SOUNDSETMODEL_ORC_MALE_2, SOUNDSETMODEL_ORC_MALE_3}, {SOUNDSETMODEL_ORC_FEMALE_1, SOUNDSETMODEL_ORC_FEMALE_2, SOUNDSETMODEL_ORC_FEMALE_3}}, + {{SOUNDSETMODEL_UNDEAD_MALE_1, SOUNDSETMODEL_UNDEAD_MALE_2, SOUNDSETMODEL_UNDEAD_MALE_3}, {SOUNDSETMODEL_UNDEAD_FEMALE_1, SOUNDSETMODEL_UNDEAD_FEMALE_2, SOUNDSETMODEL_UNDEAD_FEMALE_3}}, + {{SOUNDSETMODEL_TAUREN_MALE_1, SOUNDSETMODEL_TAUREN_MALE_2, SOUNDSETMODEL_TAUREN_MALE_3}, {SOUNDSETMODEL_TAUREN_FEMALE_1, SOUNDSETMODEL_TAUREN_FEMALE_2, SOUNDSETMODEL_TAUREN_FEMALE_3}}, + {{SOUNDSETMODEL_TROLL_MALE_1, SOUNDSETMODEL_TROLL_MALE_2, SOUNDSETMODEL_TROLL_MALE_3}, {SOUNDSETMODEL_TROLL_FEMALE_1, SOUNDSETMODEL_TROLL_FEMALE_2, SOUNDSETMODEL_TROLL_FEMALE_3}}, + {{SOUNDSETMODEL_BLOODELF_MALE_1, SOUNDSETMODEL_BLOODELF_MALE_2, SOUNDSETMODEL_BLOODELF_MALE_3}, {SOUNDSETMODEL_BLOODELF_FEMALE_1, SOUNDSETMODEL_BLOODELF_FEMALE_2, SOUNDSETMODEL_BLOODELF_FEMALE_3}} + }; + + static void GetBotClassNameAndColor(uint8 botclass, std::string& bot_color_str, std::string& bot_class_str) + { + switch (botclass) + { + case BOT_CLASS_WARRIOR: bot_color_str = "ffc79c6e"; bot_class_str = "Warrior"; break; + case BOT_CLASS_PALADIN: bot_color_str = "fff58cba"; bot_class_str = "Paladin"; break; + case BOT_CLASS_HUNTER: bot_color_str = "ffabd473"; bot_class_str = "Hunter"; break; + case BOT_CLASS_ROGUE: bot_color_str = "fffff569"; bot_class_str = "Rogue"; break; + case BOT_CLASS_PRIEST: bot_color_str = "ffffffff"; bot_class_str = "Priest"; break; + case BOT_CLASS_DEATH_KNIGHT:bot_color_str = "ffc41f3b"; bot_class_str = "Death Knight"; break; + case BOT_CLASS_SHAMAN: bot_color_str = "ff0070de"; bot_class_str = "Shaman"; break; + case BOT_CLASS_MAGE: bot_color_str = "ff69ccf0"; bot_class_str = "Mage"; break; + case BOT_CLASS_WARLOCK: bot_color_str = "ff9482c9"; bot_class_str = "Warlock"; break; + case BOT_CLASS_DRUID: bot_color_str = "ffff7d0a"; bot_class_str = "Druid"; break; + case BOT_CLASS_BM: bot_color_str = "ffa10015"; bot_class_str = "Blademaster"; break; + case BOT_CLASS_SPHYNX: bot_color_str = "ff29004a"; bot_class_str = "Obsidian Destroyer"; break; + case BOT_CLASS_ARCHMAGE: bot_color_str = "ff028a99"; bot_class_str = "Archmage"; break; + case BOT_CLASS_DREADLORD: bot_color_str = "ff534161"; bot_class_str = "Dreadlord"; break; + case BOT_CLASS_SPELLBREAKER:bot_color_str = "ffcf3c1f"; bot_class_str = "Spellbreaker"; break; + case BOT_CLASS_DARK_RANGER: bot_color_str = "ff3e255e"; bot_class_str = "Dark Ranger"; break; + case BOT_CLASS_NECROMANCER: bot_color_str = "ff9900cc"; bot_class_str = "Necromancer"; break; + case BOT_CLASS_SEA_WITCH: bot_color_str = "ff40d7a9"; bot_class_str = "Sea Witch"; break; + case BOT_CLASS_CRYPT_LORD: bot_color_str = "ff19782b"; bot_class_str = "Crypt Lord"; break; + default: bot_color_str = "ffffffff"; bot_class_str = "Unknown"; break; + } + } + + struct PlayerVisuals + { + struct PlayerVisualsBase{}; + struct Skins:PlayerVisualsBase{}; + struct Faces:PlayerVisualsBase{}; + struct HairStyles:PlayerVisualsBase{}; + struct HairColors:PlayerVisualsBase{}; + struct Features:PlayerVisualsBase{}; + }; + + template + static constexpr uint8 GetMaxVisual() + { + static_assert(std::is_base_of_v, "GetMaxVisual() must check PlayerVisuals enums"); + +#define MV_PRED9(skinm,skinf,facem,facef,hairm,hairf,hairc,featm,featf) \ + if constexpr (std::is_same_v) return !F ? skinm : skinf; \ + else if constexpr (std::is_same_v) return !F ? facem : facef; \ + else if constexpr (std::is_same_v) return !F ? hairm : hairf; \ + else if constexpr (std::is_same_v) return !F ? hairc : hairc; \ + else if constexpr (std::is_same_v) return !F ? featm : featf + + constexpr bool F = G == GENDER_FEMALE; + if constexpr (R == RACE_HUMAN) { MV_PRED9(9,9, 11,14, 16,23, 9, 8,6); } + if constexpr (R == RACE_DWARF) { MV_PRED9(8,8, 9,9, 15,18, 9, 10,5); } + if constexpr (R == RACE_NIGHTELF) { MV_PRED9(8,8, 8,8, 11,11, 7, 5,9); } + if constexpr (R == RACE_GNOME) { MV_PRED9(4,4, 6,6, 11,11, 8, 7,6); } + if constexpr (R == RACE_DRAENEI) { MV_PRED9(13,13, 9,9, 13,15, 6, 7,6); } + if constexpr (R == RACE_ORC) { MV_PRED9(8,8, 8,8, 11,12, 7, 10,6); } + if constexpr (R == RACE_UNDEAD_PLAYER) { MV_PRED9(5,5, 9,9, 14,14, 9, 16,7); } + if constexpr (R == RACE_TAUREN) { MV_PRED9(18,10, 4,3, 12,11, 2, 6,4); } + if constexpr (R == RACE_TROLL) { MV_PRED9(5,5, 4,5, 9,9, 9, 10,5); } + if constexpr (R == RACE_BLOODELF) { MV_PRED9(9,9, 9,9, 15,18, 9, 9,10); } + +#undef MV_PRED9 + return 0; + } + + static bool IsValidVisual(uint8 race, uint8 gender, uint8 skin, uint8 face, uint8 hairs, uint8 hairc, uint8 features) + { +#define VISUALS_PRED1(r) (gender == GENDER_FEMALE) ? ( \ + skin <= GetMaxVisual() && \ + face <= GetMaxVisual() && \ + hairs <= GetMaxVisual() && \ + hairc <= GetMaxVisual() && \ + features <= GetMaxVisual()) : ( \ + skin <= GetMaxVisual() && \ + face <= GetMaxVisual() && \ + hairs <= GetMaxVisual() && \ + hairc <= GetMaxVisual() && \ + features <= GetMaxVisual()) + + switch (race) + { + case RACE_HUMAN: return VISUALS_PRED1(RACE_HUMAN); + case RACE_DWARF: return VISUALS_PRED1(RACE_DWARF); + case RACE_NIGHTELF: return VISUALS_PRED1(RACE_NIGHTELF); + case RACE_GNOME: return VISUALS_PRED1(RACE_GNOME); + case RACE_DRAENEI: return VISUALS_PRED1(RACE_DRAENEI); + case RACE_ORC: return VISUALS_PRED1(RACE_ORC); + case RACE_UNDEAD_PLAYER: return VISUALS_PRED1(RACE_UNDEAD_PLAYER); + case RACE_TAUREN: return VISUALS_PRED1(RACE_TAUREN); + case RACE_TROLL: return VISUALS_PRED1(RACE_TROLL); + case RACE_BLOODELF: return VISUALS_PRED1(RACE_BLOODELF); + default: return false; + } +#undef VISUALS_PRED1 + } + + static void ReportVisualRanges(ChatHandler* handler) + { +#define FILL_VISUALS_REPORT2(s,r) s \ + << GetRaceName(r, loc) << " Male:" \ + << " skin 0-" << uint32(GetMaxVisual()) \ + << " face 0-" << uint32(GetMaxVisual()) \ + << " hairstyle 0-" << uint32(GetMaxVisual()) \ + << " haircolor 0-" << uint32(GetMaxVisual()) \ + << " features 0-" << uint32(GetMaxVisual()) \ + << "\n" << GetRaceName(r, loc) << " Female:" \ + << " skin 0-" << uint32(GetMaxVisual()) \ + << " face 0-" << uint32(GetMaxVisual()) \ + << " hairstyle 0-" << uint32(GetMaxVisual()) \ + << " haircolor 0-" << uint32(GetMaxVisual()) \ + << " features 0-" << uint32(GetMaxVisual()) + + LocaleConstant loc = handler->GetSessionDbcLocale(); + handler->SendSysMessage("Ranges:"); + for (uint8 race : { RACE_HUMAN, RACE_DWARF, RACE_NIGHTELF, RACE_GNOME, RACE_DRAENEI, RACE_ORC, RACE_UNDEAD_PLAYER, RACE_TAUREN, RACE_TROLL, RACE_BLOODELF }) + { + std::ostringstream stream; + switch (race) + { + case RACE_HUMAN: FILL_VISUALS_REPORT2(stream, RACE_HUMAN); break; + case RACE_DWARF: FILL_VISUALS_REPORT2(stream, RACE_DWARF); break; + case RACE_NIGHTELF: FILL_VISUALS_REPORT2(stream, RACE_NIGHTELF); break; + case RACE_GNOME: FILL_VISUALS_REPORT2(stream, RACE_GNOME); break; + case RACE_DRAENEI: FILL_VISUALS_REPORT2(stream, RACE_DRAENEI); break; + case RACE_ORC: FILL_VISUALS_REPORT2(stream, RACE_ORC); break; + case RACE_UNDEAD_PLAYER: FILL_VISUALS_REPORT2(stream, RACE_UNDEAD_PLAYER); break; + case RACE_TAUREN: FILL_VISUALS_REPORT2(stream, RACE_TAUREN); break; + case RACE_TROLL: FILL_VISUALS_REPORT2(stream, RACE_TROLL); break; + case RACE_BLOODELF: FILL_VISUALS_REPORT2(stream, RACE_BLOODELF); break; + default: break; + } + + handler->PSendSysMessage(stream.str().c_str()); + } +#undef FILL_VISUALS_REPORT2 + } + + static std::pair GetZoneLevels(uint32 zoneId) + { + //Only maps 0 and 1 are covered + switch (zoneId) + { + case 1: // Dun Morogh + case 12: // Elwynn Forest + case 14: // Durotar + case 85: // Tirisfal Glades + case 141: // Teldrassil + case 215: // Mulgore + case 3430: // Eversong Woods + case 3524: // Azuremyst Isle + return { 1, 10 }; + case 38: // Loch Modan + case 40: // Westfall + case 130: // Silverpine Woods + case 148: // Darkshore + case 3433: // Ghostlands + case 3525: // Bloodmyst Isle + case 721: // Gnomeregan + return { 8, 20 }; + case 17: // Barrens + return { 8, 25 }; + case 44: // Redridge Mountains + case 406: // Stonetalon Mountains + return { 13, 25 }; + case 10: // Duskwood + case 11: // Wetlands + case 267: // Hillsbrad Foothills + case 331: // Ashenvale + return { 18, 30 }; + case 400: // Thousand Needles + return { 23, 35 }; + case 36: // Alterac Mountains + case 45: // Arathi Highlands + case 405: // Desolace + return { 28, 40 }; + case 33: // Stranglethorn Valley + case 3: // Badlands + case 8: // Swamp of Sorrows + case 15: // Dustwallow Marsh + return { 33, 45 }; + case 47: // Hinterlands + case 357: // Feralas + case 440: // Tanaris + return { 38, 50 }; + case 4: // Blasted Lands + case 16: // Azshara + case 51: // Searing Gorge + return { 43, 54 }; + case 490: // Un'Goro Crater + case 361: // Felwood + return { 46, 56 }; + case 28: // Western Plaguelands + case 46: // Burning Steppes + return { 48, 56 }; + case 41: // Deadwind Pass + return { 50, 60 }; + case 1377: // Silithus + case 2017: // Stratholme + case 139: // Eastern Plaguelands + case 618: // Winterspring + return { 53, 60 }; + case 25: // BlackrockMountain + case 493: // Moonglade + return { 46, 60 }; + default: + TC_LOG_ERROR("scripts", "GetZoneLevels: no choice for zoneId {}", zoneId); + return { 1, 60 }; + } + } + + static bool IsNoWPZone(uint32 zoneId) + { + //Only maps 0 and 1 are covered + switch (zoneId) + { + case 1477: // Moonglade + case 1519: // Stormwind + case 1537: // Ironforge + case 1637: // Orgrimmar + case 1638: // Thunder Bluff + case 1657: // Darnassus + case 3487: // Silvermoon + case 3557: // Exodar + case 493: // Moonglade + return true; + default: + return false; + } + } + + static uint32 GetZoneIdOverride(uint32 zoneId) + { + switch (zoneId) + { + case 718: // Wailing Caverns + return 17; // Barrens + case 1337: // Uldaman + return 3; // Badlands + case 2057: // Scholomance + return 139; // EPL + case 2100: // Maraudon + return 405; // Desolace + case 1581: // Deadmines + return 40; // Westfall + default: + return zoneId; + } + } + + struct BotInfo + { + explicit BotInfo(uint32 Id, std::string&& Name, uint8 Race) : id(Id), name(std::move(Name)), race(Race) {} + uint32 id; + std::string name; + uint8 race; + }; +public: + script_bot_commands() : CommandScript("script_bot_commands") { } + + class WanderNode_AI : public CreatureAI + { + public: + WanderNode_AI(Creature* creature, WanderNode* wp) : CreatureAI(creature), _wp(wp) + { _wp->SetCreature(me); } + + //void JustDied(Unit*) override { _wp->SetCreature(nullptr); } + void OnDespawn() override { _wp->SetCreature(nullptr); } + + bool CanAIAttack(Unit const*) const override { return false; } + void MoveInLineOfSight(Unit*) override {} + void AttackStart(Unit*) override {} + void UpdateAI(uint32) override {} + + private: + WanderNode* const _wp; + }; + + ChatCommandTable GetCommands() const override + { + static ChatCommandTable npcbotLogCommandTable = + { + //{ "testwrite", HandleNpcBotLogTestWriteCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_DUMP_WRITE, Console::Yes }, + { "clear", HandleNpcBotLogClearCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_DUMP_WRITE, Console::Yes }, + }; + + static ChatCommandTable npcbotToggleCommandTable = + { + { "flags", HandleNpcBotToggleFlagsCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_TOGGLE_FLAGS, Console::No }, + }; + + static ChatCommandTable npcbotWPCommandTable = + { + //{ "generate", HandleNpcBotWPGenerateCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_SPAWN, Console::Yes }, + { "spawnall", HandleNpcBotWPSpawnAllCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_SPAWN, Console::No }, + { "move", HandleNpcBotWPMoveCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_SPAWN, Console::No }, + { "add", HandleNpcBotWPAddCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_SPAWN, Console::No }, + { "del", HandleNpcBotWPDeleteCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_SPAWN, Console::No }, + { "list", HandleNpcBotWPListCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_SPAWN, Console::No }, + { "list all", HandleNpcBotWPListAllCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_SPAWN, Console::Yes }, + { "go", HandleNpcBotWPGoCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_SPAWN, Console::No }, + { "setlevels", HandleNpcBotWPSetLevelsCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_SPAWN, Console::No }, + { "setlevels z",HandleNpcBotWPSetLevelsZoneCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_SPAWN, Console::Yes }, + { "setflags", HandleNpcBotWPSetFlagsCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_SPAWN, Console::No }, + { "setflags z", HandleNpcBotWPSetFlagsZoneCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_SPAWN, Console::No }, + { "setname", HandleNpcBotWPSetNameCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_SPAWN, Console::No }, + { "setlinks", HandleNpcBotWPLinksSetCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_SPAWN, Console::No }, + { "info", HandleNpcBotWPCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_SPAWN, Console::No }, + { "links", HandleNpcBotWPLinksCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_SPAWN, Console::No }, + }; + + static ChatCommandTable npcbotDebugCommandTable = + { + { "raid", HandleNpcBotDebugRaidCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_DEBUG_RAID, Console::No }, + { "mount", HandleNpcBotDebugMountCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_DEBUG_MOUNT, Console::No }, + { "model", HandleNpcBotDebugModelCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_DEBUG_VISUAL, Console::No }, + { "spellvisual",HandleNpcBotDebugSpellVisualCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_DEBUG_VISUAL, Console::No }, + { "states", HandleNpcBotDebugStatesCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_DEBUG_STATES, Console::No }, + { "names", HandleNpcBotDebugNamesCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_DEBUG_STATES, Console::No }, + { "spells", HandleNpcBotDebugSpellsCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_DEBUG_STATES, Console::No }, + { "guids", HandleNpcBotDebugGuidsCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_DEBUG_STATES, Console::No }, + { "wbequips", HandleNpcBotDebugWBEquipsCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_DEBUG_STATES, Console::Yes }, + }; + + static ChatCommandTable npcbotSetCommandTable = + { + { "faction", HandleNpcBotSetFactionCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_SET_FACTION, Console::No }, + { "owner", HandleNpcBotSetOwnerCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_SET_OWNER, Console::No }, + { "spec", HandleNpcBotSetSpecCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_SET_SPEC, Console::No }, + }; + + static ChatCommandTable npcbotCommandFollowCommandTable = + { + { "", HandleNpcBotCommandFollowCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_COMMAND_FOLLOW, Console::No }, + { "only", HandleNpcBotCommandFollowOnlyCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_COMMAND_FOLLOW, Console::No }, + }; + + static ChatCommandTable npcbotCommandCommandTable = + { + { "standstill", HandleNpcBotCommandStandstillCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_COMMAND_STANDSTILL, Console::No }, + { "stopfully", HandleNpcBotCommandStopfullyCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_COMMAND_STOPFULLY, Console::No }, + { "follow", npcbotCommandFollowCommandTable }, + { "walk", HandleNpcBotCommandWalkCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_COMMAND_MISC, Console::No }, + { "nogossip", HandleNpcBotCommandNoGossipCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_COMMAND_MISC, Console::No }, + { "unbind", HandleNpcBotCommandUnBindCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_COMMAND_MISC, Console::No }, + { "rebind", HandleNpcBotCommandReBindCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_COMMAND_MISC, Console::No }, + { "nocast", HandleNpcBotCommandNoCastCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_COMMAND_MISC, Console::No }, + { "nolongcast", HandleNpcBotCommandNoLongCastCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_COMMAND_MISC, Console::No }, + }; + + static ChatCommandTable npcbotAttackDistanceCommandTable = + { + { "short", HandleNpcBotAttackDistanceShortCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_ATTDISTANCE_SHORT, Console::No }, + { "long", HandleNpcBotAttackDistanceLongCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_ATTDISTANCE_LONG, Console::No }, + { "", HandleNpcBotAttackDistanceExactCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_ATTDISTANCE_EXACT, Console::No }, + }; + + static ChatCommandTable npcbotDistanceCommandTable = + { + { "attack", npcbotAttackDistanceCommandTable }, + { "", HandleNpcBotFollowDistanceCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_FOLDISTANCE_EXACT, Console::No }, + }; + + static ChatCommandTable npcbotOrderCommandTable = + { + { "cast", HandleNpcBotOrderCastCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_ORDER_CAST, Console::No }, + { "pull", HandleNpcBotOrderPullCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_ORDER_CAST, Console::No }, + }; + + static ChatCommandTable npcbotVehicleCommandTable = + { + { "eject", HandleNpcBotVehicleEjectCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_VEHICLE_EJECT, Console::No }, + }; + + static ChatCommandTable npcbotDumpCommandTable = + { + { "load", HandleNpcBotDumpLoadCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_DUMP_LOAD, Console::Yes }, + { "write", HandleNpcBotDumpWriteCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_DUMP_WRITE, Console::Yes }, + }; + + static ChatCommandTable npcbotRecallCommandTable = + { + { "", HandleNpcBotRecallCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_RECALL, Console::No }, + { "spawns", HandleNpcBotRecallSpawnsCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_RECALL, Console::No }, + { "teleport", HandleNpcBotRecallTeleportCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_RECALL, Console::No }, + }; + + static ChatCommandTable npcbotListSpawnedCommandTable = + { + { "", HandleNpcBotSpawnedCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_SPAWNED, Console::Yes }, + { "free", HandleNpcBotSpawnedFreeCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_SPAWNED, Console::Yes }, + }; + + static ChatCommandTable npcbotListCommandTable = + { + { "spawned", npcbotListSpawnedCommandTable }, + }; + + static ChatCommandTable npcbotDeleteCommandTable = + { + { "", HandleNpcBotDeleteCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_DELETE, Console::No }, + { "id", HandleNpcBotDeleteByIdCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_DELETE, Console::Yes }, + { "free", HandleNpcBotDeleteFreeCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_DELETE, Console::Yes }, + }; + + static ChatCommandTable npcbotSendToPointCommandTable = + { + { "", HandleNpcBotSendToPointCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_SEND, Console::No }, + { "set", HandleNpcBotSendToPointSetCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_SEND, Console::No }, + }; + + static ChatCommandTable npcbotSendToCommandTable = + { + { "", HandleNpcBotSendToCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_SEND, Console::No }, + { "last", HandleNpcBotSendToLastCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_SEND, Console::No }, + { "point", npcbotSendToPointCommandTable }, + }; + + static ChatCommandTable npcbotUseOnBotCommandTable = + { + { "spell", HandleNpcBotUseOnBotSpellCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_COMMAND_MISC, Console::No }, + { "item", HandleNpcBotUseOnBotItemCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_COMMAND_MISC, Console::No }, + }; + + static ChatCommandTable npcbotCommandTable = + { + //{ "debug", npcbotDebugCommandTable }, + //{ "toggle", npcbotToggleCommandTable }, + { "set", npcbotSetCommandTable }, + { "add", HandleNpcBotAddCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_ADD, Console::No }, + { "remove", HandleNpcBotRemoveCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_REMOVE, Console::No }, + { "free", HandleNpcBotFreeCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_REMOVE, Console::No }, + { "createnew", HandleNpcBotCreateNewCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_CREATENEW, Console::Yes }, + { "spawn", HandleNpcBotSpawnCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_SPAWN, Console::No }, + { "move", HandleNpcBotMoveCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_MOVE, Console::No }, + { "delete", npcbotDeleteCommandTable }, + { "lookup", HandleNpcBotLookupCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_LOOKUP, Console::Yes }, + { "list", npcbotListCommandTable }, + { "revive", HandleNpcBotReviveCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_REVIVE, Console::No }, + { "reloadconfig",HandleNpcBotReloadConfigCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_RELOADCONFIG, Console::Yes }, + { "useonbot", npcbotUseOnBotCommandTable }, + { "command", npcbotCommandCommandTable }, + { "info", HandleNpcBotInfoCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_INFO, Console::Yes }, + { "hide", HandleNpcBotHideCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_HIDE, Console::No }, + { "unhide", HandleNpcBotUnhideCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_UNHIDE, Console::No }, + { "show", HandleNpcBotUnhideCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_UNHIDE, Console::No }, + { "recall", npcbotRecallCommandTable }, + { "kill", HandleNpcBotKillCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_KILL, Console::No }, + { "suicide", HandleNpcBotKillCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_KILL, Console::No }, + { "go", HandleNpcBotGoCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_MOVE, Console::No }, + { "gs", HandleNpcBotGearScoreCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_COMMAND_MISC, Console::No }, + { "sendto", npcbotSendToCommandTable }, + { "distance", npcbotDistanceCommandTable }, + { "order", npcbotOrderCommandTable }, + { "vehicle", npcbotVehicleCommandTable }, + { "dump", npcbotDumpCommandTable }, + { "wp", npcbotWPCommandTable }, + { "log", npcbotLogCommandTable }, + }; + + static ChatCommandTable commandTable = + { + { "npcbot", npcbotCommandTable }, + }; + return commandTable; + } + + static bool HandleNpcBotLogClearCommand(ChatHandler* handler) + { + CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); + trans->Append("TRUNCATE TABLE `characters_npcbot_logs`"); + trans->Append("ALTER TABLE `characters_npcbot_logs` AUTO_INCREMENT = 0"); + CharacterDatabase.CommitTransaction(trans); + handler->SendSysMessage("Table `characters_npcbot_logs` was cleared and autoincrement was reset"); + return true; + } + + static bool HandleNpcBotLogTestWriteCommand(ChatHandler* handler, Optional> log_type, Optional entry, Optional> extra) + { + if (!log_type || !entry) + { + handler->PSendSysMessage(".npcbot log testwrite #log_type #entry #[owner] #[mapid] #[inmap] #[inworld] #[params[1-%u]]", MAX_BOT_LOG_PARAMS); + handler->SendSysMessage("Test `characters_npcbot_logs` table write 2"); + handler->SetSentErrorMessage(true); + return false; + } + + decltype(extra)::value_type extras{ extra.value_or(decltype(extra)::value_type{}) }; + extras.resize(MAX_BOT_LOG_PARAMS, ""); + BotLogger::Log(*log_type, *entry, extras[0], extras[1], extras[2], extras[3], extras[4]); + return true; + } + + static TempSummon* HandleWPSummon(WanderNode* wp, Map* map) + { + CellCoord c = Trinity::ComputeCellCoord(wp->m_positionX, wp->m_positionY); + GridCoord g = Trinity::ComputeGridCoord(wp->m_positionX, wp->m_positionY); + ASSERT(c.IsCoordValid(), "Invalid Cell coord!"); + ASSERT(g.IsCoordValid(), "Invalid Grid coord!"); + map->LoadGrid(wp->m_positionX, wp->m_positionY); + ASSERT(map->GetEntry()->IsContinent() || map->GetEntry()->IsBattlegroundOrArena(), "%s", map->GetDebugInfo().c_str()); + + TempSummon* wpc = map->SummonCreature(VISUAL_WAYPOINT, *wp); + wpc->SetTempSummonType(TEMPSUMMON_CORPSE_DESPAWN); + wpc->AIM_Destroy(); + wpc->AIM_Create(new WanderNode_AI(wpc, wp)); + wpc->setActive(true); + wpc->SetFarVisible(true); + wpc->SetLevel(wp->GetLevels().first); + wpc->AddUnitState(UNIT_STATE_EVADE); + wpc->SetUnitFlag(UNIT_FLAG_IMMUNE_TO_NPC | UNIT_FLAG_IMMUNE_TO_PC); + wpc->SetMaxHealth(wp->GetWPId()); + wpc->SetFullHealth(); + wpc->SetPowerType(POWER_MANA); + wpc->SetMaxPower(POWER_MANA, uint32(wp->GetLinks().size())); + wpc->SetFullPower(POWER_MANA); + wpc->SetObjectScale(5.0f); + return wpc; + } + + static bool HandleNpcBotWPGenerateCommand(ChatHandler* handler, Optional save) + { + WanderNode::RemoveAllWPs(); + + handler->SendSysMessage("Generating wander nodes from POIs. No levels or flags will be set"); + + uint32 poiId_start = 0; + for (AreaPOIEntry const* aProto : sAreaPOIStore) + { + if (aProto->ContinentID != 0 && aProto->ContinentID != 1/* && aProto->ContinentID != 530 && aProto->ContinentID != 571*/) + continue; + + uint32 poiId = ++poiId_start; + std::string poiName = aProto->Name; + if (strlen(aProto->Description) > 0) + { + poiName += " - "; + poiName += aProto->Description; + } + poiName.erase(std::remove_if(std::begin(poiName), std::end(poiName), [](char c) { return c == '\''; }), poiName.end()); + uint32 poiMapId = aProto->ContinentID; + float x = aProto->Pos.X; + float y = aProto->Pos.Y; + float z = aProto->Pos.Z; + float o = frand(0.001f, float(M_PI + M_PI) - 0.001f); + float ground_z = sMapMgr->CreateBaseMap(poiMapId)->GetHeight(PHASEMASK_NORMAL, x, y, z); + if (ground_z > INVALID_HEIGHT) + z = ground_z; + uint32 poiZoneId, poiAreaId; + sMapMgr->GetZoneAndAreaId(PHASEMASK_NORMAL, poiZoneId, poiAreaId, poiMapId, x, y, z); + + poiZoneId = GetZoneIdOverride(poiZoneId); + if (IsNoWPZone(poiZoneId)) + { + --poiId_start; + continue; + } + + WanderNode* wp = new WanderNode(poiId, poiMapId, x, y, z, o, poiZoneId, poiAreaId, poiName); + auto [minl, maxl] = GetZoneLevels(poiZoneId); + wp->SetLevels(minl, maxl); + BotWPFlags flags = BotWPFlags::BOTWP_FLAG_NONE; + wp->SetFlags(flags); + WanderNode::DoForAllMapWPs(poiMapId, [wp = wp](WanderNode const* mwp) { + if (mwp->GetWPId() != wp->GetWPId() && mwp->GetExactDist2d(wp) < MAX_VISIBILITY_DISTANCE) + wp->Link(const_cast(mwp)); + }); + + handler->SendSysMessage(wp->ToString().c_str()); + } + + handler->PSendSysMessage("Generating wander nodes completed with %u nodes", uint32(WanderNode::GetAllWPsCount())); + + if (!(save && *save)) + return true; + + WorldDatabaseTransaction trans = WorldDatabase.BeginTransaction(); + trans->Append("DROP TABLE IF EXISTS creature_wander_nodes_"); + trans->Append( + "CREATE TABLE creature_wander_nodes_ (" + " `id` int(10) unsigned NOT NULL," + " `name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'RENAME_ME'," + " `mapid` smallint(5) unsigned NOT NULL DEFAULT '0'," + " `zoneid` int(10) unsigned NOT NULL DEFAULT '0'," + " `areaid` int(10) unsigned NOT NULL DEFAULT '0'," + " `minlevel` tinyint(3) unsigned NOT NULL DEFAULT '0'," + " `maxlevel` tinyint(3) unsigned NOT NULL DEFAULT '0'," + " `flags` int(10) unsigned NOT NULL DEFAULT '0'," + " `x` float NOT NULL DEFAULT '0'," + " `y` float NOT NULL DEFAULT '0'," + " `z` float NOT NULL DEFAULT '0'," + " `o` float NOT NULL DEFAULT '0'," + " `links` mediumtext COLLATE utf8mb4_unicode_ci," + " PRIMARY KEY (`id`)" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Bot Wander Map'" + ); + std::ostringstream ss; + ss << "INSERT INTO creature_wander_nodes_ (id,mapid,x,y,z,o,zoneId,areaId,minlevel,maxlevel,flags,name,links) VALUES "; + WanderNode::DoForAllWPs([&ss](WanderNode const* wp) { + auto [minl, maxl] = wp->GetLevels(); + ss << '(' << wp->GetWPId() << ',' << wp->GetMapId() + << ',' << wp->GetPositionX() << ',' << wp->GetPositionY() << ',' << wp->GetPositionZ() << ',' << wp->GetOrientation() + << ',' << wp->GetZoneId() << ',' << wp->GetAreaId() << ',' << uint32(minl) << ',' << uint32(maxl) + << ',' << wp->GetFlags() << ",'" << wp->GetName().c_str() << "','" << wp->FormatLinks() << "'),"; + }); + std::string val_str = ss.str(); + val_str.resize(val_str.size() - 1u); + trans->PAppend("{}", val_str.c_str()); + WorldDatabase.CommitTransaction(trans); + + handler->SendSysMessage("Saved."); + + return true; + } + + static bool HandleNpcBotWPSpawnAllCommand(ChatHandler* handler) + { + if (!isWPSpawnWarningGiven) + { + isWPSpawnWarningGiven = true; + handler->SendSysMessage("Warning! Spawning all wander points in map will load ALL required grids. Repeat to confirm."); + handler->SetSentErrorMessage(true); + return false; + } + else + { + if (WanderNode::GetAllWPsCount() == 0u) + BotDataMgr::LoadWanderMap(); + + Player* player = handler->GetPlayer(); + WanderNode::DoForAllMapWPs(player->GetMapId(), [map = player->GetMap()](WanderNode const* wp) { + if (Creature* wpc = wp->GetCreature()) + wpc->ToTempSummon()->DespawnOrUnsummon(); + ASSERT_NOTNULL(HandleWPSummon(const_cast(wp), map)); + }); + } + + return true; + } + + static bool HandleNpcBotWPLinksCommand(ChatHandler* handler) + { + Player* player = handler->GetPlayer(); + Unit* wpc = player->GetSelectedUnit(); + + WanderNode* wp = wpc ? WanderNode::FindInAllWPs(wpc->ToCreature()) : nullptr; + if (!wp) + { + handler->SendSysMessage("No WP selected"); + handler->SetSentErrorMessage(true); + return false; + } + + auto const& links = wp->GetLinks(); + + std::ostringstream ss; + ss.setf(std::ios_base::fixed); + ss.precision(2); + uint32 counter = 0; + ss << "WP " << wp->GetWPId() << " has " << uint32(links.size()) << " links:"; + WanderNode::DoForContainerWPs(links, [&ss, &counter, wp = wp](WanderNode const* lwp) { + ss << "\n" << ++counter << ") " << lwp->ToString() << " (dist2d: " << wp->GetExactDist2d(lwp) << ")"; + }); + + handler->SendSysMessage(ss.str().c_str()); + + WanderNode::DoForContainerWPs(links, [wp = wp, wpc = wpc, handler = handler](WanderNode const* lwp) { + if (!lwp->GetCreature()) + { + handler->PSendSysMessage("Can't visualise link %u-%u, no creature...", wp->GetWPId(), lwp->GetWPId()); + return; + } + wpc->CastSpell(lwp->GetCreature(), 2400, true); + wpc->CastSpell(lwp->GetCreature(), 41637, true); + }); + + return true; + } + static void HandleWPUpdateLinks(ChatHandler* handler, WanderNode* wp, std::vector linkIds, bool oneway = false) + { + auto const& links = wp->GetLinks(); + std::remove_reference_t linksCopy = links; + + std::set wps_updates; + std::copy(std::cbegin(linksCopy), std::cend(linksCopy), std::inserter(wps_updates, wps_updates.begin())); + + std::set wps_relinks = wps_updates; + + wps_updates.insert(wp); + + if (linksCopy.empty()) + handler->PSendSysMessage("WP %u had no links...", wp->GetWPId()); + else + { + WanderNode::DoForContainerWPs(linksCopy, [wp = wp, handler = handler](WanderNode* lwp) { + handler->PSendSysMessage("Removing link %u<->%u...", wp->GetWPId(), lwp->GetWPId()); + wp->UnLink(lwp); + }); + } + + for (uint32 lid : linkIds) + { + if (lid == wp->GetWPId()) + { + handler->PSendSysMessage("Trying to add WP %u to its own links! Are you dumb?", lid); + continue; + } + + WanderNode* lwp = WanderNode::FindInMapWPs(wp->GetMapId(), lid); + if (!lwp) + { + handler->PSendSysMessage("WP %u is not found in map %u!", lid, wp->GetMapId()); + continue; + } + + if (wps_relinks.contains(lwp)) + wps_relinks.erase(lwp); + + handler->PSendSysMessage("Adding link %u%s%u...", wp->GetWPId(), oneway ? "->" : "<->", lid); + if (wp->GetExactDist2d(lwp) > MAX_VISIBILITY_DISTANCE) + handler->PSendSysMessage("Warning! Link distance is too great (%.2f)", wp->GetExactDist2d(lwp)); + + wp->Link(lwp, oneway); + wps_updates.insert(lwp); + } + + if (oneway) + { + std::vector wps_relinks_vec; + wps_relinks_vec.reserve(wps_relinks.size()); + for (WanderNode* rlwp : wps_relinks) + wps_relinks_vec.push_back(rlwp); + std::sort(wps_relinks_vec.begin(), wps_relinks_vec.end()); + for (WanderNode* rlwp : wps_relinks_vec) + { + handler->PSendSysMessage("Adding link %u<->%u...", wp->GetWPId(), rlwp->GetWPId()); + if (wp->GetExactDist2d(rlwp) > MAX_VISIBILITY_DISTANCE) + handler->PSendSysMessage("Warning! Link distance is too great (%.2f)", wp->GetExactDist2d(rlwp)); + wp->Link(rlwp); + } + } + + WorldDatabaseTransaction trans = WorldDatabase.BeginTransaction(); + WanderNode::DoForContainerWPs(wps_updates, [&trans](WanderNode const* uwp) { + if (Creature* wpc = uwp->GetCreature()) + { + wpc->SetMaxPower(POWER_MANA, uint32(uwp->GetLinks().size())); + wpc->SetFullPower(POWER_MANA); + } + trans->PAppend("UPDATE creature_template_npcbot_wander_nodes SET links='{}' WHERE id={}", uwp->FormatLinks().c_str(), uwp->GetWPId()); + }); + WorldDatabase.DirectCommitTransaction(trans); + } + static bool HandleNpcBotWPLinksSetCommand(ChatHandler* handler, Optional> linkIds, Optional oneway) + { + Player* player = handler->GetPlayer(); + Unit* wpc = player->GetSelectedUnit(); + + WanderNode* wp = wpc ? WanderNode::FindInAllWPs(wpc->ToCreature()) : nullptr; + if (!wp) + { + handler->SendSysMessage("No WP selected"); + handler->SetSentErrorMessage(true); + return false; + } + + if (!linkIds) + { + handler->SendSysMessage("Syntax: npcbot wp links set #[ids...] #[oneway: true/false]"); + handler->SetSentErrorMessage(true); + return false; + } + + HandleWPUpdateLinks(handler, wp, *linkIds, oneway ? *oneway : false); + + return true; + } + + static bool HandleNpcBotWPCommand(ChatHandler* handler, Optional wpId) + { + Player* player = handler->GetPlayer(); + Unit* wpc = player->GetSelectedUnit(); + + WanderNode* wp = wpc ? WanderNode::FindInAllWPs(wpc->ToCreature()) : nullptr; + + if (!wp && wpId) + wp = WanderNode::FindInAllWPs(*wpId); + + if (!wp) + { + handler->SendSysMessage("Syntax: npcbot wp info #[id_or_selection]"); + handler->SetSentErrorMessage(true); + return false; + } + + handler->SendSysMessage(wp->ToString()); + + return true; + } + static bool HandleNpcBotWPSetLevelsZoneCommand(ChatHandler* handler, Optional minlevel, Optional maxlevel) + { + Player* player = handler->GetPlayer(); + + if (!minlevel || !maxlevel) + { + handler->SendSysMessage("Syntax: npcbot wp info setlevels z #[minlevel] #[maxlevel]"); + handler->SetSentErrorMessage(true); + return false; + } + + if (!*minlevel || !*maxlevel || *minlevel > DEFAULT_MAX_LEVEL || *maxlevel > DEFAULT_MAX_LEVEL || *minlevel > *maxlevel) + { + handler->PSendSysMessage("WP levels must be within bounds 1-%u, min <= max", uint32(DEFAULT_MAX_LEVEL)); + handler->SetSentErrorMessage(true); + return false; + } + + uint32 zoneId, areaId; + player->GetZoneAndAreaId(zoneId, areaId); + handler->PSendSysMessage("Setting levels min=%u max=%u for zone %u", uint32(*minlevel), uint32(*maxlevel), zoneId); + WanderNode::DoForAllZoneWPs(zoneId, [handler = handler, minl = *minlevel, maxl = *maxlevel](WanderNode const* wp) { + handler->PSendSysMessage("Setting levels min=%u max=%u for node %u '%s'", + uint32(minl), uint32(maxl), wp->GetWPId(), wp->GetName().c_str()); + const_cast(wp)->SetLevels(minl, maxl); + if (Creature* creature = wp->GetCreature()) + if (creature->GetLevel() != minl) + creature->SetLevel(minl); + WorldDatabase.PExecute("UPDATE creature_template_npcbot_wander_nodes SET minlevel={}, maxlevel={} WHERE id={}", + uint32(minl), uint32(maxl), wp->GetWPId()); + }); + + return true; + } + static bool HandleNpcBotWPSetLevelsCommand(ChatHandler* handler, Optional minlevel, Optional maxlevel) + { + Player* player = handler->GetPlayer(); + Unit* wpc = player->GetSelectedUnit(); + + WanderNode* wp = wpc ? WanderNode::FindInAllWPs(wpc->ToCreature()) : nullptr; + if (!wp) + { + handler->SendSysMessage("No WP selected"); + handler->SetSentErrorMessage(true); + return false; + } + + if (!minlevel || !maxlevel) + { + handler->SendSysMessage("Syntax: npcbot wp info setlevels #[minlevel] #[maxlevel]"); + handler->SetSentErrorMessage(true); + return false; + } + + if (!*minlevel || !*maxlevel || *minlevel > DEFAULT_MAX_LEVEL || *maxlevel > DEFAULT_MAX_LEVEL || *minlevel > *maxlevel) + { + handler->PSendSysMessage("WP levels must be within bounds 1-%u, min <= max", uint32(DEFAULT_MAX_LEVEL)); + handler->SetSentErrorMessage(true); + return false; + } + + uint32 wpId = wp->GetWPId(); + auto [minlevel_cur, maxlevel_cur] = wp->GetLevels(); + + handler->PSendSysMessage("Changing WP %u '%s' levels from %u-%u to %u-%u", + wpId, wp->GetName().c_str(), uint32(minlevel_cur), uint32(maxlevel_cur), uint32(*minlevel), uint32(*maxlevel)); + wp->SetLevels(*minlevel, *maxlevel); + if (Creature* creature = wp->GetCreature()) + if (creature->GetLevel() != *minlevel) + creature->SetLevel(*minlevel); + + WorldDatabase.PExecute("UPDATE creature_template_npcbot_wander_nodes SET minlevel={}, maxlevel={} WHERE id={}", + uint32(*minlevel), uint32(*maxlevel), wpId); + + return true; + } + + static bool HandleNpcBotWPSetFlagsZoneCommand(ChatHandler* handler, Optional flags) + { + Player* player = handler->GetPlayer(); + + if (!flags) + { + handler->SendSysMessage("Syntax: npcbot wp info setflags z #[flags]"); + handler->SetSentErrorMessage(true); + return false; + } + + uint32 zoneId, areaId; + player->GetZoneAndAreaId(zoneId, areaId); + WanderNode::DoForAllZoneWPs(zoneId, [handler = handler, flags = *flags](WanderNode const* wp) { + uint32 wpId = wp->GetWPId(); + if (flags < 0) + { + handler->PSendSysMessage("Removing WP %u '%s' flag %u", wpId, wp->GetName().c_str(), uint32(-flags)); + const_cast(wp)->RemoveFlags(BotWPFlags(-flags)); + } + else + { + handler->PSendSysMessage("Adding WP %u '%s' flag %u", wpId, wp->GetName().c_str(), uint32(flags)); + const_cast(wp)->SetFlags(BotWPFlags(flags)); + } + WorldDatabase.PExecute("UPDATE creature_template_npcbot_wander_nodes SET flags={} WHERE id={}", wp->GetFlags(), wpId); + }); + + return true; + } + static bool HandleNpcBotWPSetFlagsCommand(ChatHandler* handler, Optional flags) + { + Player* player = handler->GetPlayer(); + Unit* wpc = player->GetSelectedUnit(); + + WanderNode* wp = wpc ? WanderNode::FindInAllWPs(wpc->ToCreature()) : nullptr; + if (!wp) + { + handler->SendSysMessage("No WP selected"); + handler->SetSentErrorMessage(true); + return false; + } + + if (!flags) + { + handler->SendSysMessage("Syntax: npcbot wp info setflags #[flag]. Use negative value to remove"); + handler->SetSentErrorMessage(true); + return false; + } + + uint32 wpId = wp->GetWPId(); + + if (*flags < 0) + { + handler->PSendSysMessage("Removing WP %u '%s' flag %u", wpId, wp->GetName().c_str(), uint32(-*flags)); + wp->RemoveFlags(BotWPFlags(-*flags)); + } + else + { + handler->PSendSysMessage("Adding WP %u '%s' flag %u", wpId, wp->GetName().c_str(), uint32(*flags)); + wp->SetFlags(BotWPFlags(*flags)); + } + + WorldDatabase.PExecute("UPDATE creature_template_npcbot_wander_nodes SET flags={} WHERE id={}", wp->GetFlags(), wpId); + + return true; + } + static bool HandleNpcBotWPSetNameCommand(ChatHandler* handler, Optional newname) + { + Player* player = handler->GetPlayer(); + Unit* wpc = player->GetSelectedUnit(); + + WanderNode* wp = wpc ? WanderNode::FindInAllWPs(wpc->ToCreature()) : nullptr; + if (!wp) + { + handler->SendSysMessage("No WP selected"); + handler->SetSentErrorMessage(true); + return false; + } + + if (!newname) + { + handler->SendSysMessage("Syntax: npcbot wp info setname #[name]"); + handler->SetSentErrorMessage(true); + return false; + } + + uint32 wpId = wp->GetWPId(); + + handler->PSendSysMessage("Changing WP %u '%s' name to '%s'", wpId, wp->GetName().c_str(), newname->c_str()); + wp->SetName(*newname); + + WorldDatabase.PExecute("UPDATE creature_template_npcbot_wander_nodes SET name='{}' WHERE id={}", wp->GetName().c_str(), wpId); + + return true; + } + + static bool HandleNpcBotWPMoveCommand(ChatHandler* handler, Optional wpId) + { + Player* player = handler->GetPlayer(); + Unit* wpc = player->GetSelectedUnit(); + + WanderNode* wp = (wpc && wpc->GetTypeId() == TYPEID_UNIT) ? WanderNode::FindInAllWPs(wpc->ToCreature()) : + wpId ? WanderNode::FindInAllWPs(*wpId) : nullptr; + if (!wp) + { + handler->SendSysMessage("No WP selected or id provided"); + handler->SetSentErrorMessage(true); + return false; + } + + if (wp->GetMapId() != player->GetMapId()) + { + handler->SendSysMessage("Can't move WP to a different map!"); + handler->SetSentErrorMessage(true); + return false; + } + + wp->Relocate(player); + if (Creature* creature = wp->GetCreature()) + creature->NearTeleportTo(*player); + + WorldDatabase.PExecute("UPDATE creature_template_npcbot_wander_nodes SET x={},y={},z={},o={} WHERE id={}", + wp->m_positionX, wp->m_positionY, wp->m_positionZ, wp->GetOrientation(), wp->GetWPId()); + + handler->PSendSysMessage("WP %u '%s' was successfully moved.", wp->GetWPId(), wp->GetName().c_str()); + + return true; + } + + static bool HandleNpcBotWPAddCommand(ChatHandler* handler, Optional flags, Optional name, + Optional minlevel, Optional maxlevel) + { + Player* player = handler->GetPlayer(); + + if (!flags || !name || (!player->GetMap()->GetEntry()->IsContinent() && !player->GetMap()->GetEntry()->IsBattlegroundOrArena())) + { + handler->SendSysMessage("Syntax: npcbot wp add #[flags] #[name] #[minlevel #[maxlevel]]. World maps only"); + handler->SetSentErrorMessage(true); + return false; + } + + if (minlevel) + { + if (!*minlevel || *minlevel > DEFAULT_MAX_LEVEL) + { + handler->PSendSysMessage("Minlevel must be between 1 and %u!", uint32(DEFAULT_MAX_LEVEL)); + handler->SetSentErrorMessage(true); + return false; + } + if (maxlevel) + { + if (!*maxlevel || *maxlevel > DEFAULT_MAX_LEVEL) + { + handler->PSendSysMessage("Maxlevel must be between 1 and %u!", uint32(DEFAULT_MAX_LEVEL)); + handler->SetSentErrorMessage(true); + return false; + } + if (*minlevel > *maxlevel) + { + handler->SendSysMessage("Minlevel can't be greater than maxlevel"); + handler->SetSentErrorMessage(true); + return false; + } + } + } + + if (*flags >= AsUnderlyingType(BotWPFlags::BOTWP_FLAG_END)) + { + handler->PSendSysMessage("Flags must below %u!", AsUnderlyingType(BotWPFlags::BOTWP_FLAG_END)); + handler->SetSentErrorMessage(true); + return false; + } + + uint32 zoneId, areaId; + player->GetZoneAndAreaId(zoneId, areaId); + WanderNode* wp = new WanderNode(++WanderNode::nextWPId, player->GetMapId(), player->m_positionX, player->m_positionY, player->m_positionZ, + player->GetOrientation(), zoneId, areaId, *name); + + wp->SetLevels((!minlevel && !maxlevel) ? GetZoneLevels(GetZoneIdOverride(zoneId)) : std::pair{minlevel ? *minlevel : uint8(1), maxlevel ? *maxlevel : uint8(DEFAULT_MAX_LEVEL)}); + wp->SetFlags(BotWPFlags(*flags)); + + std::vector linkIds; + if (Unit* twpc = player->GetSelectedUnit()) + if (WanderNode const* twp = WanderNode::FindInMapWPs(player->GetMapId(), twpc->ToCreature())) + if (twp->GetWPId() != wp->GetWPId() - 1) + linkIds.push_back(twp->GetWPId()); + if (linkIds.empty()) + { + if (WanderNode const* pwp = WanderNode::FindInMapWPs(player->GetMapId(), wp->GetWPId() - 1)) + if (wp->GetExactDist2d(pwp) < MAX_VISIBILITY_DISTANCE) + linkIds.push_back(pwp->GetWPId()); + } + if (linkIds.empty()) + { + WanderNode::DoForAllMapWPs(wp->GetMapId(), [wp = wp, &linkIds](WanderNode const* mwp) { + if (wp->GetWPId() != mwp->GetWPId() && wp->GetExactDist2d(mwp) < MAX_VISIBILITY_DISTANCE) + linkIds.push_back(mwp->GetWPId()); + }); + } + HandleWPUpdateLinks(handler, wp, linkIds); + + ASSERT_NOTNULL(HandleWPSummon(wp, player->GetMap())); + + uint32 wpId = wp->GetWPId(); + std::string wpName = wp->GetName(); + auto [minl, maxl] = wp->GetLevels(); + uint32 wpFlags = wp->GetFlags(); + + std::ostringstream ss; + ss << "INSERT INTO creature_template_npcbot_wander_nodes (id,mapid,x,y,z,o,zoneId,areaId,minlevel,maxlevel,flags,name,links)" + << " VALUES " + << '(' << wpId << ',' << wp->GetMapId() + << ',' << wp->GetPositionX() << ',' << wp->GetPositionY() << ',' << wp->GetPositionZ() << ',' << wp->GetOrientation() + << ',' << wp->GetZoneId() << ',' << wp->GetAreaId() << ',' << uint32(minl) << ',' << uint32(maxl) + << ',' << wpFlags << ",'" << wpName << "','" << wp->FormatLinks() << "')"; + + WorldDatabase.Execute(ss.str().c_str()); + + handler->PSendSysMessage("Created WP %u '%s' levels %u-%u flags %u", wpId, wpName.c_str(), uint32(minl), uint32(maxl), wpFlags); + + return true; + } + static bool HandleNpcBotWPDeleteCommand(ChatHandler* handler) + { + Player* player = handler->GetPlayer(); + Unit* wpc = player->GetSelectedUnit(); + + WanderNode* wp = wpc ? WanderNode::FindInAllWPs(wpc->ToCreature()) : nullptr; + if (!wp) + { + handler->SendSysMessage("No WP selected"); + handler->SetSentErrorMessage(true); + return false; + } + + uint32 wpId = wp->GetWPId(); + std::string wpName = wp->GetName(); + + HandleWPUpdateLinks(handler, wp, {}); + wpc->ToCreature()->AIM_Destroy(); + if (wpc->IsInWorld()) + wpc->ToTempSummon()->DespawnOrUnsummon(); + WanderNode::RemoveWP(wp); + + WorldDatabase.PExecute("DELETE FROM creature_template_npcbot_wander_nodes WHERE id={}", wpId); + + handler->PSendSysMessage("WP %u '%s' was successfully deleted.", wpId, wpName.c_str()); + + return true; + } + + static bool HandleNpcBotWPListCommand(ChatHandler* handler, Optional ozoneId, Optional oareaId) + { + Player* player = handler->GetPlayer(); + + uint32 zoneId = 0, areaId = 0; + if (!ozoneId && !oareaId) + player->GetZoneAndAreaId(zoneId, areaId); + else + { + if (ozoneId) + zoneId = *ozoneId; + if (oareaId) + areaId = *oareaId; + } + + AreaTableEntry const* zone = sAreaTableStore.LookupEntry(zoneId); + AreaTableEntry const* area = sAreaTableStore.LookupEntry(areaId); + + std::ostringstream ss; + ss << "Zone " << zoneId << " (" << std::string(zone ? zone->AreaName[0] : "unknown") << ") wps:"; + WanderNode::DoForAllZoneWPs(zoneId, [&ss](WanderNode const* wp) { + ss << "\n" << wp->ToString(); + }); + ss << "\nArea " << areaId << " (" << std::string(area ? area->AreaName[0] : "unknown") << ") wps:"; + WanderNode::DoForAllAreaWPs(areaId, [&ss](WanderNode const* wp) { + ss << "\n" << wp->ToString(); + }); + + handler->SendSysMessage(ss.str().c_str()); + return true; + } + static bool HandleNpcBotWPListAllCommand(ChatHandler* handler) + { + WanderNode::DoForAllWPs([handler = handler](WanderNode* wp) { + handler->SendSysMessage(wp->ToString().c_str()); + }); + + return true; + } + + static bool HandleNpcBotWPGoCommand(ChatHandler* handler, uint32 wpId) + { + Player* player = handler->GetPlayer(); + + WanderNode const* wp = WanderNode::FindInAllWPs(wpId); + if (!wp) + { + handler->PSendSysMessage("WP %u not found", wpId); + handler->SetSentErrorMessage(true); + return false; + } + + player->TeleportTo(WorldLocation(wp->GetMapId(), *wp), TELE_TO_GM_MODE); + + return true; + } + + static bool HandleNpcBotDebugWBEquipsCommand(ChatHandler* handler, Optional bc, Optional bs, Optional ids) + { + const std::array snames { + "MHAND", "OHAND", "RANGED", "HEAD", "SHOULDERS", "CHEST", "WAIST", "LEGS", "FEET", "WRIST", "HANDS", "BACK", "BODY", "FINGER", "FINGER", "TRINKET", "TRINKET", "NECK" + }; + + if (!bc || !bs || *bc >= BOT_CLASS_END || *bs >= BOT_INVENTORY_SIZE) + { + handler->SendSysMessage("Syntax: .npcbot debug wbequips #class #slot #['ids']"); + handler->SendSysMessage("List all generated equip templates (or just ids) for wandering bots of class #botclass"); + handler->SetSentErrorMessage(true); + return false; + } + + std::ostringstream ss; + for (uint32 c = BOT_CLASS_WARRIOR; c < BOT_CLASS_END; ++c) + { + if (c != *bc) + continue; + std::string cname, dummy; + GetBotClassNameAndColor(c, dummy, cname); + ItemPerBotClassMap const& bot_gear = BotDataMgr::GetWanderingBotsSortedGearMap(); + ItemPerSlot const& ips_arr = bot_gear.at(c); + for (uint32 s = BOT_SLOT_MAINHAND; s < BOT_INVENTORY_SIZE; ++s) + { + if (s != *bs) + continue; + ItemLeveledArr const& il_arr = ips_arr[s]; + for (uint32 lstep = 0; lstep < LEVEL_STEPS; ++lstep) + { + uint32 minlvl = std::max(lstep * ITEM_SORTING_LEVEL_STEP, 1); + uint32 maxlvl = (lstep + 1) * ITEM_SORTING_LEVEL_STEP - 1; + ItemIdVector const& vec = il_arr[lstep]; + ss << cname << ' ' << snames[s] << ' ' << minlvl << '-' << maxlvl << " (" << uint32(vec.size()) << "):"; + for (uint32 itemId : vec) + { + if (ids != std::nullopt) + ss << "\n " << itemId; + else + { + ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId); + if (!proto) + ss << "\n [Invalid] " << itemId; + else + { + ss << "\n |c"; + switch (proto->Quality) + { + case ITEM_QUALITY_POOR: ss << "ff9d9d9d"; break; //GREY + case ITEM_QUALITY_NORMAL: ss << "ffffffff"; break; //WHITE + case ITEM_QUALITY_UNCOMMON: ss << "ff1eff00"; break; //GREEN + case ITEM_QUALITY_RARE: ss << "ff0070dd"; break; //BLUE + case ITEM_QUALITY_EPIC: ss << "ffa335ee"; break; //PURPLE + case ITEM_QUALITY_LEGENDARY:ss << "ffff8000"; break; //ORANGE + case ITEM_QUALITY_ARTIFACT: ss << "ffe6cc80"; break; //LIGHT YELLOW + case ITEM_QUALITY_HEIRLOOM: ss << "ffe6cc80"; break; //LIGHT YELLOW + default: ss << "ff000000"; break; //UNK BLACK + } + ss << "|Hitem:" << uint32(proto->ItemId) << ":0:0:0:0:0:0:0:0:0|h[" << proto->Name1 << "]|h|r"; + } + } + } + handler->PSendSysMessage("%s", ss.str().c_str()); + ss.str(""); + } + } + } + + return true; + } + + static bool HandleNpcBotDebugGuidsCommand(ChatHandler* handler) + { + Unit* target = handler->getSelectedUnit(); + if (!target) + target = handler->GetPlayer(); + + std::ostringstream gss; + gss << target->GetName() << "'s guids:" + << "\n own guid:\n" << target->GetGUID().ToString() + << "\n combo target guid:\n" << target->GetComboTargetGUID().ToString() + << "\n pet guid:\n" << target->GetPetGUID().ToString() + << "\n minion guid:\n" << target->GetMinionGUID().ToString() + << "\n critter guid:\n" << target->GetCritterGUID().ToString() + << "\n charmed guid:\n" << target->GetCharmedGUID().ToString() + << "\n charmer guid:\n" << target->GetCharmerGUID().ToString() + << "\n creator guid:\n" << target->GetCreatorGUID().ToString() + << "\n creator2 guid:\n" << (target->GetCreator() ? target->GetCreator()->GetGUID().ToString() : std::string{}) + << "\n owner guid:\n" << target->GetOwnerGUID().ToString(); + + handler->SendSysMessage(gss.str().c_str()); + return true; + } + + static bool HandleNpcBotDebugNamesCommand(ChatHandler* handler, Optional name) + { + Creature* target = handler->getSelectedCreature(); + if (!target || !name) + { + handler->SendSysMessage("Syntax: .npcbot debug names #name"); + return true; + } + + CreatureTemplate const* ci = target->GetCreatureTemplate(); + LocaleConstant loc = LocaleConstant(handler->GetSessionDbLocaleIndex()); + + WorldPackets::Query::QueryCreatureResponse queryTemp; + std::string locName(*name), locTitle = ci->Title; + if (CreatureLocale const* cl = sObjectMgr->GetCreatureLocale(ci->Entry)) + { + //ObjectMgr::GetLocaleString(cl->Name, loc, locName); + ObjectMgr::GetLocaleString(cl->Title, loc, locTitle); + } + queryTemp.CreatureID = ci->Entry; + queryTemp.Allow = true; + queryTemp.Stats.Name = locName; + queryTemp.Stats.NameAlt = locTitle; + queryTemp.Stats.CursorName = ci->IconName; + queryTemp.Stats.Flags = ci->type_flags; + queryTemp.Stats.CreatureType = ci->type; + queryTemp.Stats.CreatureFamily = ci->family; + queryTemp.Stats.Classification = ci->rank; + memcpy(queryTemp.Stats.ProxyCreatureID, ci->KillCredit, sizeof(uint32) * MAX_KILL_CREDIT); + queryTemp.Stats.CreatureDisplayID[0] = ci->Modelid1; + queryTemp.Stats.CreatureDisplayID[1] = ci->Modelid2; + queryTemp.Stats.CreatureDisplayID[2] = ci->Modelid3; + queryTemp.Stats.CreatureDisplayID[3] = ci->Modelid4; + queryTemp.Stats.HpMulti = ci->ModHealth; + queryTemp.Stats.EnergyMulti = ci->ModMana; + queryTemp.Stats.Leader = ci->RacialLeader; + for (uint32 i = 0; i < MAX_CREATURE_QUEST_ITEMS; ++i) + queryTemp.Stats.QuestItems[i] = 0; + if (std::vector const* items = sObjectMgr->GetCreatureQuestItemList(ci->Entry)) + for (uint32 i = 0; i < MAX_CREATURE_QUEST_ITEMS; ++i) + if (i < items->size()) + queryTemp.Stats.QuestItems[i] = (*items)[i]; + queryTemp.Stats.CreatureMovementInfoID = ci->movementId; + queryTemp.Write(); + queryTemp.ShrinkToFit(); + + WorldPacket response = queryTemp.Move(); + handler->GetSession()->SendPacket(&response); + + handler->SendSysMessage("Done."); + return true; + } + + static bool HandleNpcBotDebugSpellsCommand(ChatHandler* handler) + { + Unit* target = handler->getSelectedUnit(); + if (!target) + { + handler->SendSysMessage("No target selected"); + return true; + } + + std::ostringstream ostr; + ostr << "Listing spells for " << target->GetName() << ':'; + for (uint8 i = 0; i < CURRENT_MAX_SPELL; ++i) + { + if (Spell const* curSpell = target->GetCurrentSpell(CurrentSpellTypes(i))) + ostr << "\nSpell type " << uint32(i) << ":\n" << curSpell->GetDebugInfo(); + } + + handler->SendSysMessage(ostr.str().c_str()); + return true; + } + + static bool HandleNpcBotDebugStatesCommand(ChatHandler* handler) + { + Unit const* target = handler->getSelectedUnit(); + if (!target) + { + handler->SendSysMessage("No target selected"); + return true; + } + + std::ostringstream ostr; + ostr << "Listing states for " << target->GetName() << ":"; + for (uint32 state = 1u; state != 1u << 31; state <<= 1) + { + if (target->HasUnitState(state)) + ostr << "\n 0x" << std::hex << (state); + } + + handler->SendSysMessage(ostr.str().c_str()); + return true; + } + + static bool HandleNpcBotDebugRaidCommand(ChatHandler* handler) + { + Player* owner = handler->GetSession()->GetPlayer(); + Group const* gr = owner->GetGroup(); + if (!owner->HaveBot() || !gr) + { + handler->SendSysMessage(".npcbot debug raid"); + handler->SendSysMessage("prints your raid bots info"); + return true; + } + if (!gr->isRaidGroup()) + { + handler->SendSysMessage("only usable in raid"); + return true; + } + + uint8 counter = 0; + uint8* subBots = new uint8[MAX_RAID_SUBGROUPS]; + memset((void*)subBots, 0, (MAX_RAID_SUBGROUPS)*sizeof(uint8)); + std::ostringstream sstr; + BotMap const* map = owner->GetBotMgr()->GetBotMap(); + for (BotMap::const_iterator itr = map->begin(); itr != map->end(); ++itr) + { + Creature* bot = itr->second; + if (!bot || !gr->IsMember(itr->second->GetGUID())) + continue; + + uint8 subGroup = gr->GetMemberGroup(itr->second->GetGUID()); + ++subBots[subGroup]; + sstr << uint32(++counter) << ": " << bot->GetGUID().GetCounter() << " " << bot->GetName() + << " subgr: " << uint32(subGroup + 1) << "\n"; + } + + for (uint8 i = 0; i != MAX_RAID_SUBGROUPS; ++i) + if (subBots[i] > 0) + sstr << uint32(subBots[i]) << " bots in subgroup " << uint32(i + 1) << "\n"; + + handler->SendSysMessage(sstr.str().c_str()); + delete[] subBots; + return true; + } + + static bool HandleNpcBotDebugMountCommand(ChatHandler* handler, Optional mountId) + { + if (!mountId) + return false; + + Unit* target = handler->getSelectedUnit(); + if (!target) + { + handler->SendSysMessage("No target selected"); + return true; + } + + target->Mount(*mountId); + return true; + } + + static bool HandleNpcBotDebugModelCommand(ChatHandler* handler, Optional setId) + { + Player* owner = handler->GetSession()->GetPlayer(); + Unit* target = owner->GetSelectedUnit(); + if (!target) + { + handler->SendSysMessage("No target selected"); + return true; + } + + if (setId) + last_model_id = *setId; + + handler->PSendSysMessage("Setting model %u...", last_model_id); + target->SetDisplayId(last_model_id++); + + return true; + } + + static bool HandleNpcBotDebugSpellVisualCommand(ChatHandler* handler, Optional kit) + { + Player* owner = handler->GetSession()->GetPlayer(); + Unit* target = owner->GetSelectedUnit(); + if (!target) + { + handler->SendSysMessage("No target selected"); + return true; + } + + target->SendPlaySpellVisual(kit.value_or(0)); + return true; + } + + static bool HandleNpcBotDumpLoadCommand(ChatHandler* handler, Optional file_str, Optional forceKick) + { + bool force_kick = forceKick.value_or(false); + if (!file_str || (!force_kick && sWorld->GetPlayerCount() > 0)) + { + handler->SendSysMessage(".npcbot dump load"); + handler->SendSysMessage("Imports NPCBots from a backup SQL file created with '.npcbot dump write' command."); + handler->SendSysMessage("Syntax: .npcbot dump load #file_name [#force_kick_all]"); + if (!force_kick && sWorld->GetPlayerCount() > 0) + handler->SendSysMessage("Make sure no players are online before importing."); + handler->SetSentErrorMessage(true); + return false; + } + + sWorld->SetPlayerAmountLimit(0); + if (force_kick) + sWorld->KickAll(); + + //omit file ext if needed + if (file_str->find('.') == std::string::npos) + *file_str += ".sql"; + + switch (NPCBotsDump().Load(*file_str)) + { + case BOT_DUMP_SUCCESS: + handler->SendSysMessage("Import successful."); + handler->SendSysMessage("Server will be restarted now to prevent DB corruption."); + sWorld->ShutdownServ(4, SHUTDOWN_MASK_RESTART, 70); + break; + case BOT_DUMP_FAIL_FILE_NOT_EXIST: + handler->PSendSysMessage("Can't open %s or the file doesn't exist!", file_str->c_str()); + handler->SetSentErrorMessage(true); + return false; + case BOT_DUMP_FAIL_FILE_CORRUPTED: + handler->SendSysMessage("File data integrity check failed!"); + handler->SetSentErrorMessage(true); + return false; + case BOT_DUMP_FAIL_DATA_OCCUPIED: + handler->PSendSysMessage("Table data contained in %s overlaps with existing table entries!", file_str->c_str()); + handler->SetSentErrorMessage(true); + return false; + default: + handler->SendSysMessage("Error!"); + handler->SetSentErrorMessage(true); + return false; + } + + return true; + } + + static bool HandleNpcBotDumpWriteCommand(ChatHandler* handler, Optional file_str) + { + if (!file_str) + { + handler->SendSysMessage(".npcbot dump write\nExports spawned NPCBots into a SQL file.\nSyntax: .npcbot dump write #file_name"); + handler->SetSentErrorMessage(true); + return false; + } + + //omit file ext if needed + if (file_str->find('.') == std::string::npos) + *file_str += ".sql"; + + switch (NPCBotsDump().Write(*file_str)) + { + case BOT_DUMP_SUCCESS: + handler->SendSysMessage("Export successful."); + break; + case BOT_DUMP_FAIL_FILE_ALREADY_EXISTS: + handler->PSendSysMessage("File %s already exists!", file_str->c_str()); + handler->SetSentErrorMessage(true); + return false; + case BOT_DUMP_FAIL_CANT_WRITE_TO_FILE: + handler->SendSysMessage("Can't open file for write!"); + handler->SetSentErrorMessage(true); + return false; + case BOT_DUMP_FAIL_INCOMPLETE: + handler->SendSysMessage("Export was not completed!"); + handler->SetSentErrorMessage(true); + return false; + default: + handler->SendSysMessage("Error!"); + handler->SetSentErrorMessage(true); + return false; + } + + return true; + } + + static bool HandleNpcBotOrderPullCommand(ChatHandler* handler, Optional bot_name, Optional target_token) + { + Player* owner = handler->GetSession()->GetPlayer(); + if (!owner->HaveBot() || !bot_name) + { + handler->SendSysMessage(".npcbot order pull #bot_name #[target_token]"); + handler->SendSysMessage("Orders bot to pull target immediately"); + return true; + } + + if (owner->GetBotMgr()->IsPartyInCombat()) + { + handler->SendSysMessage("Can't do that while in combat!"); + return true; + } + + for (std::decay_t::size_type i = 0u; i < bot_name->size(); ++i) + if ((*bot_name)[i] == '_') + (*bot_name)[i] = ' '; + + Creature* bot = owner->GetBotMgr()->GetBotByName(*bot_name); + if (bot) + { + if (!bot->IsInWorld()) + { + handler->PSendSysMessage("Bot %s is not found!", bot_name->c_str()); + return true; + } + if (!bot->IsAlive()) + { + handler->PSendSysMessage("%s is dead!", bot->GetName().c_str()); + return true; + } + if (!bot->GetBotAI()->HasRole(BOT_ROLE_DPS) || bot->GetVictim() || bot->IsInCombat() || !bot->getAttackers().empty()) + { + handler->PSendSysMessage("%s cannot pull target! Must be idle and have DPS role", bot->GetName().c_str()); + return true; + } + } + else + { + auto const& class_name = *bot_name; + for (auto const c : class_name) + { + if (!std::islower(c)) + { + handler->SendSysMessage("Bot class name must be in lower case!"); + return true; + } + } + + uint8 bot_class = BotMgr::BotClassByClassName(class_name); + if (bot_class == BOT_CLASS_NONE) + { + handler->PSendSysMessage("Unknown bot name or class %s!", class_name.c_str()); + return true; + } + + std::list cBots = owner->GetBotMgr()->GetAllBotsByClass(bot_class); + + if (cBots.empty()) + { + handler->PSendSysMessage("No bots of class %u found!", bot_class); + return true; + } + + bot = cBots.size() == 1 ? cBots.front() : Trinity::Containers::SelectRandomContainerElement(cBots); + + if (!bot) + { + handler->SendSysMessage("None of %u found bots can use pull yet!", cBots.size()); + return true; + } + } + + ObjectGuid target_guid = ObjectGuid::Empty; + bool token_valid = true; + if (!target_token || target_token == "mytarget") + target_guid = owner->GetTarget(); + else if (Group const* group = owner->GetGroup()) + { + if (target_token == "star") + target_guid = group->GetTargetIcons()[0]; + else if (target_token == "circle") + target_guid = group->GetTargetIcons()[1]; + else if (target_token == "diamond") + target_guid = group->GetTargetIcons()[2]; + else if (target_token == "triangle") + target_guid = group->GetTargetIcons()[3]; + else if (target_token == "moon") + target_guid = group->GetTargetIcons()[4]; + else if (target_token == "square") + target_guid = group->GetTargetIcons()[5]; + else if (target_token == "cross") + target_guid = group->GetTargetIcons()[6]; + else if (target_token == "skull") + target_guid = group->GetTargetIcons()[7]; + else if (target_token->size() == 1u && std::isdigit(target_token->front())) + { + uint8 digit = static_cast(std::stoi(std::string(*target_token))); + switch (digit) + { + case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8: + target_guid = group->GetTargetIcons()[digit - 1]; + break; + default: + token_valid = false; + break; + } + } + else + token_valid = false; + } + else + token_valid = false; + + if (!token_valid) + { + handler->PSendSysMessage("Invalid target token '%s'!", *target_token); + handler->SendSysMessage("Valid target tokens:\n '','mytarget', " + "'star','1', 'circle','2', 'diamond','3', 'triangle','4', 'moon','5', 'square','6', 'cross','7', 'skull','8'" + "\nNote that target icons tokens are only available while in group"); + return true; + } + + Unit* target = target_guid ? ObjectAccessor::GetUnit(*owner, target_guid) : nullptr; + if (!target || !bot->FindMap() || target->FindMap() != bot->FindMap()) + { + handler->PSendSysMessage("Invalid target '%s'!", target ? target->GetName().c_str() : "unknown"); + return true; + } + + bot_ai::BotOrder order(BOT_ORDER_PULL); + order.params.pullParams.targetGuid = target_guid.GetRawValue(); + + if (bot->GetBotAI()->AddOrder(std::move(order))) + { + if (DEBUG_BOT_ORDERS) + handler->PSendSysMessage("Order given: %s: pull %s", bot->GetName().c_str(), target ? target->GetName().c_str() : "unknown"); + } + else + { + if (DEBUG_BOT_ORDERS) + handler->PSendSysMessage("Order failed: %s: pull %s", bot->GetName().c_str(), target ? target->GetName().c_str() : "unknown"); + } + + return true; + } + + static bool HandleNpcBotOrderCastCommand(ChatHandler* handler, Optional bot_name, Optional spell_name, Optional target_token) + { + Player* owner = handler->GetSession()->GetPlayer(); + if (!owner->HaveBot() || !bot_name || !spell_name) + { + handler->SendSysMessage(".npcbot order cast #bot_name #spell_underscored_name #[target_token]"); + handler->SendSysMessage("Orders bot to cast a spell immediately"); + return true; + } + + for (std::decay_t::size_type i = 0u; i < spell_name->size(); ++i) + if ((*spell_name)[i] == '_') + (*spell_name)[i] = ' '; + + for (std::decay_t::size_type i = 0u; i < bot_name->size(); ++i) + if ((*bot_name)[i] == '_') + (*bot_name)[i] = ' '; + + auto canBotUseSpell = [=](Creature const* tbot, uint32 bspell) { + //we ignore GCD for now + return bspell && (tbot->GetBotAI()->GetSpellCooldown(bspell) <= tbot->GetBotAI()->GetLastDiff()); + }; + + uint32 base_spell = 0; + Creature* bot = owner->GetBotMgr()->GetBotByName(*bot_name); + if (bot) + { + if (!bot->IsInWorld()) + { + handler->PSendSysMessage("Bot %s is not found!", bot_name->c_str()); + return true; + } + if (!bot->IsAlive()) + { + handler->PSendSysMessage("%s is dead!", bot->GetName().c_str()); + return true; + } + + base_spell = bot->GetBotAI()->GetBaseSpell(*spell_name, handler->GetSessionDbcLocale()); + if (!base_spell) + { + handler->PSendSysMessage("%s doesn't have spell named '%s'!", bot->GetName().c_str(), spell_name->c_str()); + return true; + } + if (!canBotUseSpell(bot, base_spell)) + { + handler->PSendSysMessage("%s's %s is not ready yet!", bot->GetName().c_str(), sSpellMgr->GetSpellInfo(base_spell)->SpellName[handler->GetSessionDbcLocale()]); + return true; + } + } + else + { + auto const& class_name = *bot_name; + for (auto const c : class_name) + { + if (!std::islower(c)) + { + handler->SendSysMessage("Bot class name must be in lower case!"); + return true; + } + } + + uint8 bot_class = BotMgr::BotClassByClassName(class_name); + if (bot_class == BOT_CLASS_NONE) + { + handler->PSendSysMessage("Unknown bot name or class %s!", class_name.c_str()); + return true; + } + + std::list cBots = owner->GetBotMgr()->GetAllBotsByClass(bot_class); + + if (cBots.empty()) + { + handler->PSendSysMessage("No bots of class %u found!", bot_class); + return true; + } + + for (Creature const* fbot : cBots) + { + base_spell = fbot->GetBotAI()->GetBaseSpell(*spell_name, handler->GetSessionDbcLocale()); + if (base_spell) + break; + } + + if (!base_spell) + { + handler->PSendSysMessage("None of %u found bots have spell named '%s'!", cBots.size(), spell_name->c_str()); + return true; + } + + cBots.erase(std::remove_if(cBots.begin(), cBots.end(), + [=](Creature const* tbot) { + if (tbot->GetBotAI()->GetOrdersCount() >= MAX_BOT_ORDERS_QUEUE_SIZE) + return true; + return !canBotUseSpell(tbot, base_spell); + }), + cBots.end()); + + decltype(cBots) ccBots; + for (decltype(cBots)::const_iterator it = cBots.begin(); it != cBots.end();) + { + if (!(*it)->GetCurrentSpell(CURRENT_CHANNELED_SPELL) && !(*it)->IsNonMeleeSpellCast(false, false, true, false, false)) + { + ccBots.push_back(*it); + it = cBots.erase(it); + } + else + ++it; + } + + bot = ccBots.empty() ? nullptr : ccBots.size() == 1 ? ccBots.front() : Trinity::Containers::SelectRandomContainerElement(ccBots); + if (!bot) + bot = cBots.empty() ? nullptr : cBots.size() == 1 ? cBots.front() : Trinity::Containers::SelectRandomContainerElement(cBots); + + if (!bot) + { + handler->PSendSysMessage("None of %u found bots can use %s yet!", cBots.size(), spell_name->c_str()); + return true; + } + } + + ObjectGuid target_guid = ObjectGuid::Empty; + bool token_valid = true; + if (!target_token || target_token == "bot" || target_token == "self") + target_guid = bot->GetGUID(); + else if (target_token == "me" || target_token == "master") + target_guid = owner->GetGUID(); + else if (target_token == "mypet") + target_guid = owner->GetPetGUID(); + else if (target_token == "myvehicle") + target_guid = owner->GetVehicle() ? owner->GetVehicleBase()->GetGUID() : ObjectGuid::Empty; + else if (target_token == "target") + target_guid = bot->GetTarget(); + else if (target_token == "mytarget") + target_guid = owner->GetTarget(); + else if (Group const* group = owner->GetGroup()) + { + if (target_token == "star") + target_guid = group->GetTargetIcons()[0]; + else if (target_token == "circle") + target_guid = group->GetTargetIcons()[1]; + else if (target_token == "diamond") + target_guid = group->GetTargetIcons()[2]; + else if (target_token == "triangle") + target_guid = group->GetTargetIcons()[3]; + else if (target_token == "moon") + target_guid = group->GetTargetIcons()[4]; + else if (target_token == "square") + target_guid = group->GetTargetIcons()[5]; + else if (target_token == "cross") + target_guid = group->GetTargetIcons()[6]; + else if (target_token == "skull") + target_guid = group->GetTargetIcons()[7]; + else if (target_token->size() == 1u && std::isdigit(target_token->front())) + { + uint8 digit = static_cast(std::stoi(std::string(*target_token))); + switch (digit) + { + case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8: + target_guid = group->GetTargetIcons()[digit - 1]; + break; + default: + token_valid = false; + break; + } + } + else + token_valid = false; + } + else + token_valid = false; + + if (!token_valid) + { + handler->PSendSysMessage("Invalid target token '%s'!", *target_token); + handler->SendSysMessage("Valid target tokens:\n '','bot','self', 'me','master', 'mypet', 'myvehicle', 'target', 'mytarget', " + "'star','1', 'circle','2', 'diamond','3', 'triangle','4', 'moon','5', 'square','6', 'cross','7', 'skull','8'" + "\nNote that target icons tokens are only available while in group"); + return true; + } + + Unit* target = target_guid ? ObjectAccessor::GetUnit(*owner, target_guid) : nullptr; + if (!target || !bot->FindMap() || target->FindMap() != bot->FindMap()) + { + handler->PSendSysMessage("Invalid target '%s'!", target ? target->GetName().c_str() : "unknown"); + return true; + } + + bot_ai::BotOrder order(BOT_ORDER_SPELLCAST); + order.params.spellCastParams.baseSpell = base_spell; + order.params.spellCastParams.targetGuid = target_guid.GetRawValue(); + + if (bot->GetBotAI()->AddOrder(std::move(order))) + { + if (DEBUG_BOT_ORDERS) + handler->PSendSysMessage("Order given: %s: %s on %s", bot->GetName().c_str(), + sSpellMgr->GetSpellInfo(base_spell)->SpellName[handler->GetSessionDbcLocale()], target ? target->GetName().c_str() : "unknown"); + } + else + { + if (DEBUG_BOT_ORDERS) + handler->PSendSysMessage("Order failed: %s: %s on %s", bot->GetName().c_str(), + sSpellMgr->GetSpellInfo(base_spell)->SpellName[handler->GetSessionDbcLocale()], target ? target->GetName().c_str() : "unknown"); + } + + return true; + } + + static bool HandleNpcBotVehicleEjectCommand(ChatHandler* handler) + { + Player const* owner = handler->GetSession()->GetPlayer(); + Unit const* target = handler->getSelectedUnit(); + + bool hasBotsInVehicles = false; + bool botsInSelVehicle = 0; + BotMap const* bmap = nullptr; + if (owner->HaveBot()) + { + bmap = owner->GetBotMgr()->GetBotMap(); + for (BotMap::const_iterator ci = bmap->begin(); ci != bmap->end(); ++ci) + { + if (ci->second && ci->second->GetVehicle()) + { + if (!hasBotsInVehicles) + hasBotsInVehicles = true; + if (!botsInSelVehicle && target && target->IsVehicle() && target->GetVehicleKit()->GetSeatForPassenger(ci->second)) + botsInSelVehicle = true; + } + if (hasBotsInVehicles && botsInSelVehicle) + break; + } + } + + if (bmap && hasBotsInVehicles) + { + for (BotMap::const_iterator ci = bmap->begin(); ci != bmap->end(); ++ci) + { + Creature* bot = ci->second; + if (bot && bot->GetVehicle()) + { + bool doeject = false; + if (!botsInSelVehicle) + doeject = true; + else if (target) + if (/*VehicleSeatEntry const* seat = */target->GetVehicleKit()->GetSeatForPassenger(bot)) + //if (seat->CanEnterOrExit()) + doeject = true; + + if (doeject) + { + bot->GetVehicle()->GetBase()->StopMoving(); + //handler->PSendSysMessage("Removing %s from %s", bot->GetName().c_str(), bot->GetVehicle()->GetBase()->GetName().c_str()); + bot->ExitVehicle(); + //bot->BotStopMovement(); + } + } + } + return true; + } + + handler->SendSysMessage(".npcbot eject"); + handler->SendSysMessage("Removes your bots from selected vehicle, or, all bots from any vehicles if no vehicle selected"); + handler->SetSentErrorMessage(true); + return false; + } + + static bool HandleNpcBotFollowDistanceCommand(ChatHandler* handler, Optional dist) + { + Player* owner = handler->GetSession()->GetPlayer(); + + if (!owner->HaveBot() || !dist) + { + handler->SendSysMessage(".npcbot distance #[attack] #newdist"); + handler->SendSysMessage("Sets follow / attack distance for bots"); + return true; + } + + uint8 newdist = uint8(std::min(std::max(*dist, 0), 100)); + owner->GetBotMgr()->SetBotFollowDist(newdist); + + handler->PSendSysMessage("Bots' follow distance is set to %u", uint32(newdist)); + return true; + } + + static bool HandleNpcBotAttackDistanceShortCommand(ChatHandler* handler) + { + Player* owner = handler->GetSession()->GetPlayer(); + if (!owner->HaveBot()) + { + handler->SendSysMessage(".npcbot distance attack short"); + handler->SendSysMessage("Sets attack distance for bots"); + return true; + } + + owner->GetBotMgr()->SetBotAttackRangeMode(BOT_ATTACK_RANGE_SHORT); + + handler->SendSysMessage("Bots' attack distance is set to 'short'"); + return true; + } + + static bool HandleNpcBotAttackDistanceLongCommand(ChatHandler* handler) + { + Player* owner = handler->GetSession()->GetPlayer(); + if (!owner->HaveBot()) + { + handler->SendSysMessage(".npcbot distance attack long"); + handler->SendSysMessage("Sets attack distance for bots"); + return true; + } + + owner->GetBotMgr()->SetBotAttackRangeMode(BOT_ATTACK_RANGE_LONG); + + handler->SendSysMessage("Bots' attack distance is set to 'long'"); + return true; + } + + static bool HandleNpcBotAttackDistanceExactCommand(ChatHandler* handler, Optional dist) + { + Player* owner = handler->GetSession()->GetPlayer(); + + if (!owner->HaveBot() || !dist) + { + handler->SendSysMessage(".npcbot distance attack #newdist"); + handler->SendSysMessage("Sets attack distance for bots"); + return true; + } + + uint8 newdist = uint8(std::min(std::max(*dist, 0), 50)); + owner->GetBotMgr()->SetBotAttackRangeMode(BOT_ATTACK_RANGE_EXACT, newdist); + + handler->PSendSysMessage("Bots' attack distance is set to %u", uint32(newdist)); + return true; + } + + static bool HandleNpcBotHideCommand(ChatHandler* handler) + { + // Hiding/unhiding bots should be allowed only out of combat + // Currenly bots can teleport to master in combat + // This creates potential for some serious trolls + Player* owner = handler->GetSession()->GetPlayer(); + if (!owner->HaveBot()) + { + handler->SendSysMessage(".npcbot hide"); + handler->SendSysMessage("Removes your owned npcbots from world temporarily"); + //handler->SendSysMessage("You have no bots!"); + handler->SetSentErrorMessage(true); + return false; + } + if (!owner->IsAlive()) + { + handler->GetSession()->SendNotification("You are dead"); + handler->SetSentErrorMessage(true); + return false; + } + if (owner->GetBotMgr()->IsPartyInCombat()) + { + handler->GetSession()->SendNotification(LANG_YOU_IN_COMBAT); + handler->SetSentErrorMessage(true); + return false; + } + + owner->GetBotMgr()->SetBotsHidden(true); + handler->SendSysMessage("Bots hidden"); + return true; + } + + static bool HandleNpcBotUnhideCommand(ChatHandler* handler) + { + Player* owner = handler->GetSession()->GetPlayer(); + if (!owner->HaveBot()) + { + handler->SendSysMessage(".npcbot unhide | show"); + handler->SendSysMessage("Returns your temporarily hidden bots back"); + //handler->SendSysMessage("You have no bots!"); + handler->SetSentErrorMessage(true); + return false; + } + if (!owner->IsAlive()) + { + handler->GetSession()->SendNotification("You are dead"); + handler->SetSentErrorMessage(true); + return false; + } + if (owner->GetBotMgr()->IsPartyInCombat() && (owner->IsPvP() || owner->IsFFAPvP())) + { + handler->GetSession()->SendNotification("You can't do that while in PvP combat"); + handler->SetSentErrorMessage(true); + return false; + } + + owner->GetBotMgr()->SetBotsHidden(false); + handler->SendSysMessage("Bots unhidden"); + return true; + } + + static bool HandleNpcBotKillCommand(ChatHandler* handler) + { + Player* owner = handler->GetSession()->GetPlayer(); + + ObjectGuid guid = owner->GetTarget(); + if (!guid || !owner->HaveBot()) + { + handler->SendSysMessage(".npcbot kill | suicide"); + handler->SendSysMessage("Makes your npcbot just drop dead. If you select yourself ALL your bots will die"); + handler->SetSentErrorMessage(true); + return false; + } + + if (guid == owner->GetGUID()) + { + owner->GetBotMgr()->KillAllBots(); + return true; + } + if (Creature* bot = owner->GetBotMgr()->GetBot(guid)) + { + owner->GetBotMgr()->KillBot(bot); + return true; + } + + handler->SendSysMessage("You must select one of your bots or yourself"); + handler->SetSentErrorMessage(true); + return false; + } + + static bool HandleNpcBotGoCommand(ChatHandler* handler, Optional creatureId) + { + Player* player = handler->GetSession()->GetPlayer(); + + if (!creatureId) + { + handler->SendSysMessage(".npcbot go #[ID]"); + handler->SendSysMessage("Teleports to npcbot's current location"); + handler->SetSentErrorMessage(true); + return false; + } + + Creature const* bot = BotDataMgr::FindBot(*creatureId); + if (!bot) + { + handler->PSendSysMessage("NpcBot %u is not found!", *creatureId); + handler->SetSentErrorMessage(true); + return false; + } + + handler->PSendSysMessage(LANG_APPEARING_AT, bot->GetName().c_str()); + + if (player->IsInFlight()) + player->FinishTaxiFlight(); + else + player->SaveRecallPosition(); // save only in non-flight case + + WorldLocation wloc = *bot; + wloc.m_positionZ += 1.5f; + + player->TeleportTo(wloc, TELE_TO_GM_MODE); + return true; + } + + static bool HandleNpcBotSendToCommand(ChatHandler* handler, Optional> names) + { + static auto return_syntax = [](ChatHandler* chandler) -> bool { + chandler->SendSysMessage("Syntax: .npcbot sendto #names..."); + chandler->SendSysMessage("Makes selected/named bot(s) wait 30 sec for your next DEST spell, assume that position and hold it"); + chandler->SendSysMessage("Max distance is 70 yds"); + chandler->SetSentErrorMessage(true); + return false; + }; + + static auto return_success = [](ChatHandler* chandler, Variant name_or_count) -> bool { + if (name_or_count.holds_alternative()) + chandler->PSendSysMessage("Your next dest spell will send %u bot(s) to position...", name_or_count.get()); + else + chandler->PSendSysMessage("Your next dest spell will send %s to position...", name_or_count.get().c_str()); + return true; + }; + + Player const* owner = handler->GetSession()->GetPlayer(); + + if (!owner->HaveBot()) + return return_syntax(handler); + + if (!names || names->empty()) + { + Unit const* target = handler->getSelectedCreature(); + Creature const* bot = target ? owner->GetBotMgr()->GetBot(target->GetGUID()) : nullptr; + if (bot && bot->IsAlive() && !bot->GetBotAI()->HasBotCommandState(BOT_COMMAND_FULLSTOP)) + { + bot->GetBotAI()->SetBotAwaitState(BOT_AWAIT_SEND); + return return_success(handler, { bot->GetName() }); + } + return return_syntax(handler); + } + + uint32 count = 0; + for (decltype(names)::value_type::value_type name : *names) + { + for (decltype(name)::size_type i = 0u; i < name.size(); ++i) + if (name[i] == '_') + name[i] = ' '; + + Creature const* bot = owner->GetBotMgr()->GetBotByName(name); + if (bot && bot->IsAlive() && !bot->GetBotAI()->HasBotCommandState(BOT_COMMAND_FULLSTOP)) + { + ++count; + bot->GetBotAI()->SetBotAwaitState(BOT_AWAIT_SEND); + } + } + + if (count == 0) + { + handler->PSendSysMessage("Unable to send any of %u bots!", uint32(names->size())); + handler->SetSentErrorMessage(true); + return false; + } + + return return_success(handler, { count }); + } + + static bool HandleNpcBotSendToLastCommand(ChatHandler* handler, Optional> names) + { + static auto return_syntax = [](ChatHandler* chandler) -> bool { + chandler->SendSysMessage("Syntax: .npcbot sendto last #names..."); + chandler->SendSysMessage("Makes selected/named bot(s) assume previous position they were sent from"); + chandler->SendSysMessage("This will cancel current sendto await state"); + chandler->SendSysMessage("Max distance is 70 yds"); + chandler->SetSentErrorMessage(true); + return false; + }; + + static auto return_success = [](ChatHandler* chandler, Variant name_or_count) -> bool { + if (name_or_count.holds_alternative()) + chandler->PSendSysMessage("Moving %u bot(s) to previous position...", name_or_count.get()); + else + chandler->PSendSysMessage("Moving %s to previous position...", name_or_count.get().c_str()); + return true; + }; + + Player const* owner = handler->GetSession()->GetPlayer(); + + if (!owner->HaveBot()) + return return_syntax(handler); + + if (!names || names->empty()) + { + Unit const* target = handler->getSelectedCreature(); + Creature const* bot = target ? owner->GetBotMgr()->GetBot(target->GetGUID()) : nullptr; + if (bot && bot->IsAlive() && !bot->GetBotAI()->HasBotCommandState(BOT_COMMAND_FULLSTOP)) + { + bot->GetBotAI()->MoveToLastSendPosition(); + return return_success(handler, { bot->GetName() }); + } + return return_syntax(handler); + } + + uint32 count = 0; + for (decltype(names)::value_type::value_type name : *names) + { + for (decltype(name)::size_type i = 0u; i < name.size(); ++i) + if (name[i] == '_') + name[i] = ' '; + + Creature const* bot = owner->GetBotMgr()->GetBotByName(name); + if (bot && bot->IsAlive() && !bot->GetBotAI()->HasBotCommandState(BOT_COMMAND_FULLSTOP)) + { + ++count; + bot->GetBotAI()->MoveToLastSendPosition(); + } + } + + if (count == 0) + { + handler->PSendSysMessage("Unable to send any of %u bots!", uint32(names->size())); + handler->SetSentErrorMessage(true); + return false; + } + + return return_success(handler, { count }); + } + + static bool HandleNpcBotSendToPointSetCommand(ChatHandler* handler, Optional point_id, Optional> names) + { + static auto return_syntax = [](ChatHandler* chandler) -> bool { + chandler->SendSysMessage("Syntax: .npcbot sendto point set #number #names..."); + chandler->SendSysMessage("Marks selected/named bots' current position as send point by #number"); + chandler->PSendSysMessage("Point number must be in range 1 ... %u", uint32(MAX_SEND_POINTS)); + chandler->SetSentErrorMessage(true); + return false; + }; + + static auto return_success = [&](ChatHandler* chandler, Variant name_or_count) -> bool { + if (name_or_count.holds_alternative()) + chandler->PSendSysMessage("Marked send point %u for %u bot(s)", *point_id, name_or_count.get()); + else + chandler->PSendSysMessage("Marked send point %u for %s", *point_id, name_or_count.get().c_str()); + return true; + }; + + Player const* owner = handler->GetSession()->GetPlayer(); + + if (!point_id || !*point_id || *point_id > MAX_SEND_POINTS || !owner->HaveBot()) + return return_syntax(handler); + + if (!names || names->empty()) + { + Unit const* target = handler->getSelectedCreature(); + Creature const* bot = target ? owner->GetBotMgr()->GetBot(target->GetGUID()) : nullptr; + if (bot && bot->IsAlive()) + { + bot->GetBotAI()->MarkSendPosition(*point_id - 1); + return return_success(handler, { bot->GetName() }); + } + return return_syntax(handler); + } + + uint32 count = 0; + for (decltype(names)::value_type::value_type name : *names) + { + for (decltype(name)::size_type i = 0u; i < name.size(); ++i) + if (name[i] == '_') + name[i] = ' '; + + Creature const* bot = owner->GetBotMgr()->GetBotByName(name); + if (bot && bot->IsAlive()) + { + ++count; + bot->GetBotAI()->MarkSendPosition(*point_id - 1); + } + } + + if (count == 0) + { + handler->PSendSysMessage("Unable to mark send point for any of %u bots!", uint32(names->size())); + handler->SetSentErrorMessage(true); + return false; + } + + return return_success(handler, { count }); + } + + static bool HandleNpcBotSendToPointCommand(ChatHandler* handler, Optional point_id, Optional> names) + { + static auto return_syntax = [](ChatHandler* chandler) -> bool { + chandler->SendSysMessage("Syntax: .npcbot sendto point #number #names..."); + chandler->SendSysMessage("Makes selected/named bot(s) assume previously set point by #number"); + chandler->SendSysMessage("This will cancel current sendto await state"); + chandler->SendSysMessage("Max distance is 70 yds"); + chandler->SetSentErrorMessage(true); + return false; + }; + + static auto return_success = [&](ChatHandler* chandler, Variant name_or_count) -> bool { + if (name_or_count.holds_alternative()) + chandler->PSendSysMessage("Moving %u bot(s) to point %u...", name_or_count.get(), *point_id); + else + chandler->PSendSysMessage("Moving %s to point %u...", name_or_count.get().c_str(), *point_id); + return true; + }; + + Player const* owner = handler->GetSession()->GetPlayer(); + + if (!point_id || !*point_id || *point_id > MAX_SEND_POINTS || !owner->HaveBot()) + return return_syntax(handler); + + if (!names || names->empty()) + { + Unit const* target = handler->getSelectedCreature(); + Creature const* bot = target ? owner->GetBotMgr()->GetBot(target->GetGUID()) : nullptr; + if (bot && bot->IsAlive() && !bot->GetBotAI()->HasBotCommandState(BOT_COMMAND_FULLSTOP)) + { + bot->GetBotAI()->MoveToSendPosition(*point_id - 1); + return return_success(handler, { bot->GetName() }); + } + return return_syntax(handler); + } + + uint32 count = 0; + for (decltype(names)::value_type::value_type name : *names) + { + for (decltype(name)::size_type i = 0u; i < name.size(); ++i) + if (name[i] == '_') + name[i] = ' '; + + Creature const* bot = owner->GetBotMgr()->GetBotByName(name); + if (bot && bot->IsAlive() && !bot->GetBotAI()->HasBotCommandState(BOT_COMMAND_FULLSTOP)) + { + ++count; + bot->GetBotAI()->MoveToSendPosition(*point_id - 1); + } + } + + if (count == 0) + { + handler->PSendSysMessage("Unable to send any of %u bots!", uint32(names->size())); + handler->SetSentErrorMessage(true); + return false; + } + + return return_success(handler, { count }); + } + + static bool HandleNpcBotRecallCommand(ChatHandler* handler) + { + Player* owner = handler->GetSession()->GetPlayer(); + + ObjectGuid guid = owner->GetTarget(); + if (!guid || !owner->HaveBot()) + { + handler->SendSysMessage(".npcbot recall"); + handler->SendSysMessage("Forces npcbots to move directly on your position. Select a npcbot you want to move or select yourself to move all bots"); + handler->SetSentErrorMessage(true); + return false; + } + if (owner->GetBotMgr()->IsPartyInCombat()) + { + handler->GetSession()->SendNotification(LANG_YOU_IN_COMBAT); + handler->SetSentErrorMessage(true); + return false; + } + + if (guid == owner->GetGUID()) + { + owner->GetBotMgr()->RecallAllBots(); + return true; + } + if (Creature* bot = owner->GetBotMgr()->GetBot(guid)) + { + owner->GetBotMgr()->RecallBot(bot); + return true; + } + + handler->SendSysMessage("You must select one of your bots or yourself"); + handler->SetSentErrorMessage(true); + return false; + } + + static bool HandleNpcBotRecallSpawnsCommand(ChatHandler* handler) + { + Player const* owner = handler->GetSession()->GetPlayer(); + + std::vector botvec; + BotDataMgr::GetNPCBotGuidsByOwner(botvec, owner->GetGUID()); + if (owner->HaveBot()) + botvec.erase(std::remove_if(botvec.begin(), botvec.end(), [=](ObjectGuid botguid) { return owner->GetBotMgr()->GetBot(botguid); }), botvec.end()); + + uint32 recalled_count = 0; + for (ObjectGuid botguid : botvec) + { + if (Creature const* bot = BotDataMgr::FindBot(botguid.GetEntry())) + { + bot->GetBotAI()->ResetBotAI(BOTAI_RESET_FORCERECALL); + ++recalled_count; + } + } + + if (recalled_count == 0) + { + handler->SendSysMessage(".npcbot recall spawns"); + handler->SendSysMessage("Forces all your owned inactive npcbots to teleport to their spawn locations immediatelly"); + handler->SetSentErrorMessage(true); + return false; + } + + return true; + } + + static bool HandleNpcBotRecallTeleportCommand(ChatHandler* handler) + { + Player* owner = handler->GetSession()->GetPlayer(); + + if (!owner->HaveBot()) + { + handler->SendSysMessage(".npcbot recall teleport"); + handler->SendSysMessage("Forces all your npcbots to teleport to your position"); + handler->SetSentErrorMessage(true); + return false; + } + if (!owner->IsAlive()) + { + handler->GetSession()->SendNotification("You are dead"); + handler->SetSentErrorMessage(true); + return false; + } + if (owner->GetBotMgr()->IsPartyInCombat() && (owner->IsPvP() || owner->IsFFAPvP())) + { + handler->GetSession()->SendNotification("You can't do that while in PvP combat"); + handler->SetSentErrorMessage(true); + return false; + } + + owner->GetBotMgr()->RecallAllBots(true); + return true; + } + + static bool HandleNpcBotToggleFlagsCommand(ChatHandler* handler, Optional flag) + { + Player* chr = handler->GetSession()->GetPlayer(); + Unit* unit = chr->GetSelectedUnit(); + if (!unit || unit->GetTypeId() != TYPEID_UNIT || !flag) + { + handler->SendSysMessage(".npcbot toggle flags #flag"); + handler->SendSysMessage("This is a debug command"); + handler->SetSentErrorMessage(true); + return false; + } + + uint32 setFlags = 0; + switch (*flag) + { + case 6: + setFlags = UNIT_FLAG_UNK_6; + break; + case 14: + setFlags = UNIT_FLAG_CANNOT_SWIM; + break; + case 15: + setFlags = UNIT_FLAG_CAN_SWIM; + break; + case 16: + setFlags = UNIT_FLAG_NON_ATTACKABLE_2; + break; + default: + break; + } + + if (!setFlags) + return false; + + handler->PSendSysMessage("Toggling flag %u on %s", setFlags, unit->GetName().c_str()); + unit->ToggleFlag(UNIT_FIELD_FLAGS, setFlags); + return true; + } + + static bool HandleNpcBotSetFactionCommand(ChatHandler* handler, Optional factionStr) + { + Player* chr = handler->GetSession()->GetPlayer(); + Unit* ubot = chr->GetSelectedUnit(); + if (!ubot || !factionStr) + { + handler->SendSysMessage(".npcbot set faction #faction"); + handler->SendSysMessage("Sets faction for selected npcbot (saved in DB)"); + handler->SendSysMessage("Use 'a', 'h', 'm' or 'f' as argument to set faction to alliance, horde, monsters (hostile to all) or friends (friendly to all)"); + handler->SetSentErrorMessage(true); + return false; + } + + Creature* bot = ubot->ToCreature(); + if (!bot || !bot->IsNPCBot() || !bot->IsFreeBot()) + { + handler->SendSysMessage("You must select uncontrolled npcbot"); + handler->SetSentErrorMessage(true); + return false; + } + + uint32 factionId = 0; + + if ((*factionStr)[0] == 'a') + factionId = 1802; //Alliance + else if ((*factionStr)[0] == 'h') + factionId = 1801; //Horde + else if ((*factionStr)[0] == 'm') + factionId = 14; //Monsters + else if ((*factionStr)[0] == 'f') + factionId = 35; //Friendly to all + + if (!factionId) + { + char* pfactionid = handler->extractKeyFromLink((char*)factionStr->c_str(), "Hfaction"); + factionId = atoi(pfactionid); + } + + if (!sFactionTemplateStore.LookupEntry(factionId)) + { + handler->PSendSysMessage(LANG_WRONG_FACTION, factionId); + handler->SetSentErrorMessage(true); + return false; + } + + BotDataMgr::UpdateNpcBotData(bot->GetEntry(), NPCBOT_UPDATE_FACTION, &factionId); + bot->GetBotAI()->ReInitFaction(); + + handler->PSendSysMessage("%s's faction set to %u", bot->GetName().c_str(), factionId); + return true; + } + + static bool HandleNpcBotSetOwnerCommand(ChatHandler* handler, Optional charVal) + { + Player* chr = handler->GetSession()->GetPlayer(); + Unit* ubot = chr->GetSelectedUnit(); + if (!ubot || !charVal) + { + handler->SendSysMessage(".npcbot set owner #guid | #name"); + handler->SendSysMessage("Binds selected npcbot to new player owner using guid or name and updates owner in DB"); + handler->SetSentErrorMessage(true); + return false; + } + + Creature* bot = ubot->ToCreature(); + if (!bot || !bot->IsNPCBot() || bot->GetBotAI()->IsWanderer()) + { + handler->SendSysMessage("You must select a non-wandering npcbot"); + handler->SetSentErrorMessage(true); + return false; + } + + if (bot->GetBotAI()->GetBotOwnerGuid()) + { + handler->SendSysMessage("This npcbot already has owner"); + handler->SetSentErrorMessage(true); + return false; + } + + char* characterName_str = strtok((char*)charVal->c_str(), " "); + if (!characterName_str) + return false; + + std::string characterName = characterName_str; + uint32 guidlow = (uint32)atoi(characterName_str); + + bool found = true; + if (guidlow) + found = sCharacterCache->GetCharacterNameByGuid(ObjectGuid(HighGuid::Player, 0, guidlow), characterName); + else + guidlow = sCharacterCache->GetCharacterGuidByName(characterName).GetCounter(); + + if (!guidlow || !found) + { + handler->SendSysMessage("Player not found"); + handler->SetSentErrorMessage(true); + return false; + } + + BotDataMgr::UpdateNpcBotData(bot->GetEntry(), NPCBOT_UPDATE_OWNER, &guidlow); + bot->GetBotAI()->ReinitOwner(); + //bot->GetBotAI()->Reset(); + + handler->PSendSysMessage("%s's new owner is %s (guidlow: %u)", bot->GetName().c_str(), characterName.c_str(), guidlow); + return true; + } + + static bool HandleNpcBotSetSpecCommand(ChatHandler* handler, Optional spec) + { + Player* chr = handler->GetSession()->GetPlayer(); + Unit* ubot = chr->GetSelectedUnit(); + if (!ubot || !spec) + { + handler->SendSysMessage(".npcbot set spec #specnumber"); + handler->SendSysMessage("Changes talent spec for selected npcbot"); + handler->SetSentErrorMessage(true); + return false; + } + + Creature* bot = ubot->ToCreature(); + if (!bot || !bot->IsNPCBot()) + { + handler->SendSysMessage("You must select a npcbot"); + handler->SetSentErrorMessage(true); + return false; + } + + if (!bot_ai::IsValidSpecForClass(bot->GetBotClass(), *spec)) + { + handler->PSendSysMessage("%s is not a valid spec for bot class %u!", + bot_ai::LocalizedNpcText(chr, bot_ai::TextForSpec(*spec)), uint32(bot->GetBotClass())); + handler->SetSentErrorMessage(true); + return false; + } + + bot->GetBotAI()->SetSpec(*spec); + + handler->PSendSysMessage("%s's new spec is %u", bot->GetName().c_str(), uint32(*spec)); + return true; + } + + static bool HandleNpcBotLookupCommand(ChatHandler* handler, Optional botclass, Optional unspawned, Optional teamid) + { + //this is just a modified '.lookup creature' command + if (!botclass) + { + handler->SendSysMessage(".npcbot lookup #class #[not_spawned_only] #[team_id]"); + handler->SendSysMessage("Looks up npcbots by #class, and returns all matches with their creature ID's"); + handler->SendSysMessage("If #not_spawned_only is set to 1 shows only bots which don't exist in world"); + handler->SendSysMessage("If #team_id is provided, will also filter by team: Alliance = 0, Horde = 1, Neutral = 2"); + handler->PSendSysMessage("BOT_CLASS_WARRIOR = %u", uint32(BOT_CLASS_WARRIOR)); + handler->PSendSysMessage("BOT_CLASS_PALADIN = %u", uint32(BOT_CLASS_PALADIN)); + handler->PSendSysMessage("BOT_CLASS_HUNTER = %u", uint32(BOT_CLASS_HUNTER)); + handler->PSendSysMessage("BOT_CLASS_ROGUE = %u", uint32(BOT_CLASS_ROGUE)); + handler->PSendSysMessage("BOT_CLASS_PRIEST = %u", uint32(BOT_CLASS_PRIEST)); + handler->PSendSysMessage("BOT_CLASS_DEATH_KNIGHT = %u", uint32(BOT_CLASS_DEATH_KNIGHT)); + handler->PSendSysMessage("BOT_CLASS_SHAMAN = %u", uint32(BOT_CLASS_SHAMAN)); + handler->PSendSysMessage("BOT_CLASS_MAGE = %u", uint32(BOT_CLASS_MAGE)); + handler->PSendSysMessage("BOT_CLASS_WARLOCK = %u", uint32(BOT_CLASS_WARLOCK)); + handler->PSendSysMessage("BOT_CLASS_DRUID = %u", uint32(BOT_CLASS_DRUID)); + handler->PSendSysMessage("BOT_CLASS_BLADEMASTER = %u", uint32(BOT_CLASS_BM)); + handler->PSendSysMessage("BOT_CLASS_SPHYNX = %u", uint32(BOT_CLASS_SPHYNX)); + handler->PSendSysMessage("BOT_CLASS_ARCHMAGE = %u", uint32(BOT_CLASS_ARCHMAGE)); + handler->PSendSysMessage("BOT_CLASS_DREADLORD = %u", uint32(BOT_CLASS_DREADLORD)); + handler->PSendSysMessage("BOT_CLASS_SPELLBREAKER = %u", uint32(BOT_CLASS_SPELLBREAKER)); + handler->PSendSysMessage("BOT_CLASS_DARK_RANGER = %u", uint32(BOT_CLASS_DARK_RANGER)); + handler->PSendSysMessage("BOT_CLASS_NECROMANCER = %u", uint32(BOT_CLASS_NECROMANCER)); + handler->PSendSysMessage("BOT_CLASS_SEA_WITCH = %u", uint32(BOT_CLASS_SEA_WITCH)); + handler->PSendSysMessage("BOT_CLASS_CRYPT_LORD = %u", uint32(BOT_CLASS_CRYPT_LORD)); + handler->SetSentErrorMessage(true); + return false; + } + + if (botclass == BOT_CLASS_NONE || botclass >= BOT_CLASS_END) + { + handler->PSendSysMessage("Unknown bot class %u", uint32(*botclass)); + handler->SetSentErrorMessage(true); + return false; + } + + if (teamid && *teamid > uint8(TEAM_NEUTRAL)) + { + handler->PSendSysMessage("Unknown team %u", uint32(*teamid)); + handler->SetSentErrorMessage(true); + return false; + } + + handler->PSendSysMessage("Looking for bots of class %u...", uint32(*botclass)); + + uint8 localeIndex = handler->GetSessionDbLocaleIndex(); + CreatureTemplateContainer const& ctc = sObjectMgr->GetCreatureTemplates(); + typedef std::vector BotList; + BotList botlist; + for (CreatureTemplateContainer::const_iterator itr = ctc.begin(); itr != ctc.end(); ++itr) + { + uint32 id = itr->second.Entry; + + if (id == BOT_ENTRY_MIRROR_IMAGE_BM) + continue; + //Blademaster disabled + if (botclass == BOT_CLASS_BM) + continue; + + NpcBotExtras const* _botExtras = BotDataMgr::SelectNpcBotExtras(id); + if (!_botExtras || _botExtras->bclass != botclass) + continue; + + if (unspawned && *unspawned && BotDataMgr::SelectNpcBotData(id)) + continue; + + uint8 race = _botExtras->race; + + if (teamid) + { + ChrRacesEntry const* rentry = sChrRacesStore.LookupEntry(race); + uint32 faction = rentry ? rentry->FactionID : 14; + TeamId team = BotDataMgr::GetTeamIdForFaction(faction); + + if (*teamid != uint8(team)) + continue; + } + + if (CreatureLocale const* creatureLocale = sObjectMgr->GetCreatureLocale(id)) + { + if (creatureLocale->Name.size() > localeIndex && !creatureLocale->Name[localeIndex].empty()) + { + botlist.emplace_back(id, std::string(creatureLocale->Name[localeIndex]), race); + continue; + } + } + + std::string name = itr->second.Name; + if (name.empty()) + continue; + + botlist.emplace_back(id, std::move(name), race); + } + + if (botlist.empty()) + { + handler->SendSysMessage(LANG_COMMAND_NOCREATUREFOUND); + handler->SetSentErrorMessage(true); + return false; + } + + std::sort(botlist.begin(), botlist.end(), [](BotInfo const& bi1, BotInfo const& bi2) { return bi1.id < bi2.id; }); + + for (BotList::const_iterator itr = botlist.begin(); itr != botlist.end(); ++itr) + { + uint8 race = itr->race; + if (race >= MAX_RACES) + race = RACE_NONE; + + std::string_view raceName; + switch (race) + { + case RACE_HUMAN: raceName = "Human"; break; + case RACE_ORC: raceName = "Orc"; break; + case RACE_DWARF: raceName = "Dwarf"; break; + case RACE_NIGHTELF: raceName = "Night Elf"; break; + case RACE_UNDEAD_PLAYER:raceName = "Forsaken"; break; + case RACE_TAUREN: raceName = "Tauren"; break; + case RACE_GNOME: raceName = "Gnome"; break; + case RACE_TROLL: raceName = "Troll"; break; + case RACE_BLOODELF: raceName = "Blood Elf"; break; + case RACE_DRAENEI: raceName = "Draenei"; break; + case RACE_NONE: raceName = "No Race"; break; + default: raceName = "Unknown"; break; + } + + handler->PSendSysMessage("%d - |cffffffff|Hcreature_entry:%d|h[%s]|h|r %s", itr->id, itr->id, itr->name.c_str(), raceName); + } + + return true; + } + + static bool HandeNpcBotCleanUpAndRemoval(ChatHandler* handler, Creature* bot, Player const* chr/* = nullptr*/) + { + Player const* botowner = bot->GetBotOwner()->ToPlayer(); + + if (bot->GetBotAI()->HasRealEquipment()) + { + ObjectGuid receiver = + botowner ? botowner->GetGUID() : + bot->GetBotAI()->GetBotOwnerGuid() != 0 ? ObjectGuid(HighGuid::Player, 0, bot->GetBotAI()->GetBotOwnerGuid()) : + chr ? chr->GetGUID() : ObjectGuid::Empty; + + if (!botowner && chr && receiver != chr->GetGUID() && !sCharacterCache->HasCharacterCacheEntry(receiver)) + receiver = chr->GetGUID(); + + if (receiver == ObjectGuid::Empty) + { + handler->PSendSysMessage("Cannot delete bot %s from console: has gear but no player to give it back to! Can only delete this bot in-game.", bot->GetName().c_str()); + return false; + } + if (!bot->GetBotAI()->UnEquipAll(receiver)) + { + handler->PSendSysMessage("%s is unable to unequip some gear. Please remove equips manually first!", bot->GetName().c_str()); + return false; + } + } + + if (botowner) + botowner->GetBotMgr()->RemoveBot(bot->GetGUID(), BOT_REMOVE_DISMISS); + + return true; + } + + static bool HandleNpcBotFreeCommand(ChatHandler* handler) + { + Player* chr = handler->GetSession()->GetPlayer(); + Unit* ubot = chr->GetSelectedUnit(); + if (!ubot) + { + handler->SendSysMessage(".npcbot free"); + handler->SendSysMessage("Immediately cancels selected npcbot's ownership"); + handler->SetSentErrorMessage(true); + return false; + } + + Creature* bot = ubot->ToCreature(); + if (!bot || !bot->IsNPCBot() || !bot->GetBotAI()->GetBotOwnerGuid() || bot->IsTempBot()) + { + handler->SendSysMessage("No owned npcbot selected"); + handler->SetSentErrorMessage(true); + return false; + } + + uint32 owner_guid = bot->GetBotAI()->GetBotOwnerGuid(); + Player const* botowner = bot->GetBotOwner()->ToPlayer(); + if (!HandeNpcBotCleanUpAndRemoval(handler, bot, chr)) + { + handler->SetSentErrorMessage(true); + return false; + } + + uint8 spec = bot_ai::SelectSpecForClass(bot->GetBotClass()); + BotDataMgr::UpdateNpcBotData(bot->GetEntry(), NPCBOT_UPDATE_SPEC, &spec); + uint32 roleMask = bot_ai::DefaultRolesForClass(bot->GetBotClass(), spec); + BotDataMgr::UpdateNpcBotData(bot->GetEntry(), NPCBOT_UPDATE_ROLES, &roleMask); + + if (!botowner) + { + uint32 newOwner = 0; + BotDataMgr::UpdateNpcBotData(bot->GetEntry(), NPCBOT_UPDATE_OWNER, &newOwner); + + if (Group* gr = bot->GetBotGroup()) + gr->RemoveMember(bot->GetGUID()); + + bot->GetBotAI()->ResetBotAI(BOTAI_RESET_DISMISS); + } + + handler->PSendSysMessage("Npcbot %s successfully freed, owner was %u", bot->GetName().c_str(), owner_guid); + return true; + } + + static bool HandleNpcBotDeleteCommand(ChatHandler* handler) + { + Player* chr = handler->GetSession()->GetPlayer(); + Unit* ubot = chr->GetSelectedUnit(); + if (!ubot) + { + handler->SendSysMessage(".npcbot delete"); + handler->SendSysMessage("Deletes selected npcbot spawn from world and DB"); + handler->SetSentErrorMessage(true); + return false; + } + + Creature* bot = ubot->ToCreature(); + if (!bot || !bot->IsNPCBot()) + { + handler->SendSysMessage("No npcbot selected"); + handler->SetSentErrorMessage(true); + return false; + } + + if (bot->GetBotAI()->IsWanderer()) + { + BotDataMgr::DespawnWandererBot(bot->GetEntry()); + handler->PSendSysMessage("Wandering bot %u '%s' successfully deleted", bot->GetEntry(), bot->GetName().c_str()); + return true; + } + + if (!HandeNpcBotCleanUpAndRemoval(handler, bot, chr)) + { + handler->SetSentErrorMessage(true); + return false; + } + + bot->CombatStop(); + bot->GetBotAI()->Reset(); + bot->GetBotAI()->canUpdate = false; + + CreatureData const* data = ASSERT_NOTNULL(sObjectMgr->GetCreatureData(bot->GetSpawnId())); + if (bot->IsInWorld() && data->mapId != bot->GetMap()->GetId()) + bot->GetMap()->AddObjectToRemoveList(bot); + + Creature::DeleteFromDB(bot->GetSpawnId()); + + BotDataMgr::UpdateNpcBotData(bot->GetEntry(), NPCBOT_UPDATE_ERASE); + + handler->PSendSysMessage("Npcbot %s successfully deleted", bot->GetName().c_str()); + return true; + } + + static bool HandleNpcBotDeleteByIdCommand(ChatHandler* handler, Optional creature_id) + { + if (!creature_id) + { + handler->SendSysMessage(".npcbot delete id"); + handler->SendSysMessage("Deletes npcbot spawn from world and DB using creature id"); + handler->SetSentErrorMessage(true); + return false; + } + + Creature const* bot = BotDataMgr::FindBot(*creature_id); + if (!bot) + { + handler->PSendSysMessage("npcbot %u not found!", *creature_id); + handler->SetSentErrorMessage(true); + return false; + } + + if (bot->GetBotAI()->IsWanderer()) + { + BotDataMgr::DespawnWandererBot(bot->GetEntry()); + handler->PSendSysMessage("Wandering bot %u '%s' successfully deleted", bot->GetEntry(), bot->GetName().c_str()); + return true; + } + + Player* chr = !handler->IsConsole() ? handler->GetSession()->GetPlayer() : nullptr; + + if (!HandeNpcBotCleanUpAndRemoval(handler, const_cast(bot), chr)) + { + handler->SetSentErrorMessage(true); + return false; + } + + const_cast(bot)->CombatStop(); + bot->GetBotAI()->Reset(); + bot->GetBotAI()->canUpdate = false; + + CreatureData const* data = ASSERT_NOTNULL(sObjectMgr->GetCreatureData(bot->GetSpawnId())); + if (bot->IsInWorld() && data->mapId != bot->GetMap()->GetId()) + bot->GetMap()->AddObjectToRemoveList(const_cast(bot)); + + Creature::DeleteFromDB(bot->GetSpawnId()); + + BotDataMgr::UpdateNpcBotData(bot->GetEntry(), NPCBOT_UPDATE_ERASE); + + handler->PSendSysMessage("Npcbot %s successfully deleted", bot->GetName().c_str()); + return true; + } + + static bool HandleNpcBotDeleteFreeCommand(ChatHandler* handler) + { + uint32 count = 0; + for (uint32 creature_id : BotDataMgr::GetExistingNPCBotIds()) + if (NpcBotData const* botData = BotDataMgr::SelectNpcBotData(creature_id)) + if (botData->owner == 0) + if (HandleNpcBotDeleteByIdCommand(handler, creature_id)) + ++count; + + handler->PSendSysMessage("%u free npcbots deleted", count); + return true; + } + + static bool HandleNpcBotMoveCommand(ChatHandler* handler, Optional creVal) + { + Player* player = handler->GetSession()->GetPlayer(); + Creature* creature = handler->getSelectedCreature(); + + if ((!creature && !creVal) || player->GetMap()->Instanceable()) + { + handler->SendSysMessage(".npcbot move"); + handler->SendSysMessage("Moves npcbot to your location. World maps only"); + handler->SendSysMessage("Syntax: .npcbot move [#ID]"); + handler->SetSentErrorMessage(true); + return false; + } + + char* charID = creVal ? handler->extractKeyFromLink((char*)creVal->c_str(), "Hcreature_entry") : nullptr; + if (!charID && !creature) + return false; + + uint32 id = charID ? atoi(charID) : creature->GetEntry(); + + CreatureTemplate const* creInfo = sObjectMgr->GetCreatureTemplate(id); + if (!creInfo) + { + handler->PSendSysMessage("creature id %u does not exist!", id); + handler->SetSentErrorMessage(true); + return false; + } + + if (!creInfo->IsNPCBot()) + { + handler->PSendSysMessage("creature id %u is not a npcbot!", id); + handler->SetSentErrorMessage(true); + return false; + } + + if (!BotDataMgr::SelectNpcBotData(id)) + { + handler->PSendSysMessage("NpcBot %u is not spawned!", id); + handler->SetSentErrorMessage(true); + return false; + } + + Creature const* bot = BotDataMgr::FindBot(id); + ASSERT(bot); + + uint32 lowguid = bot->GetSpawnId(); + + CreatureData const* data = sObjectMgr->GetCreatureData(lowguid); + if (!data) + { + handler->PSendSysMessage(LANG_COMMAND_CREATGUIDNOTFOUND, lowguid); + handler->SetSentErrorMessage(true); + return false; + } + + CreatureData* cdata = const_cast(data); + cdata->spawnPoint.Relocate(player); + cdata->spawnPoint.SetOrientation(player->GetOrientation()); + cdata->mapId = player->GetMapId(); + + WorldDatabase.PExecute( + "UPDATE creature SET position_x = {}, position_y = {}, position_z = {}, orientation = {}, map = {} WHERE guid = {}", + cdata->spawnPoint.GetPositionX(), cdata->spawnPoint.GetPositionY(), cdata->spawnPoint.GetPositionZ(), cdata->spawnPoint.GetOrientation(), uint32(cdata->mapId), lowguid); + + if (bot->GetBotAI()->IAmFree() && bot->IsInWorld() && !bot->IsInCombat() && bot->IsAlive()) + BotMgr::TeleportBot(const_cast(bot), player->GetMap(), player); + + handler->PSendSysMessage("NpcBot %u (guid %u) was moved", id, lowguid); + return true; + } + + static bool HandleNpcBotCreateNewCommand(ChatHandler* handler, Optional name, Optional bclass, Optional race, Optional gender, Optional skin, Optional face, Optional hairstyle, Optional haircolor, Optional features, Optional soundset) + { + static auto const ret_err = [](ChatHandler* handler, bool report_ranges = false) { + if (report_ranges) + ReportVisualRanges(handler); + else + { + handler->SendSysMessage(".npcbot createnew"); + handler->SendSysMessage("Creates a new npcbot creature entry"); + handler->SendSysMessage("Syntax: .npcbot createnew #name #class ##race ##gender ##skin ##face ##hairstyle ##haircolor ##features ##[sound_variant = {1,2,3}]"); + handler->SendSysMessage("In case of class that cannot change appearance all extra arguments must be omitted"); + handler->SendSysMessage("Use '.npcbot createnew ranges' to print visuals constraints for all races"); + } + handler->SetSentErrorMessage(true); + return false; + }; + static auto const ret_err_invalid_arg = [](ChatHandler* handler, char const* argname, Optional argval = {}) { + handler->PSendSysMessage("Invalid %s%s!", argname, argval ? (" " + std::to_string(*argval)).c_str() : ""); + handler->SetSentErrorMessage(true); + return false; + }; + static auto const ret_err_invalid_args_for = [](ChatHandler* handler, char const* argname1, char const* argname2) { + handler->PSendSysMessage("Invalid arguments for %s '%s'!", argname1, argname2); + handler->SetSentErrorMessage(true); + return false; + }; + + if (!bclass || !name) + return ret_err(handler, name && *name == "ranges"); + + for (std::decay_t::size_type i = 0u; i < name->size(); ++i) + if ((*name)[i] == '_') + (*name)[i] = ' '; + + bool const can_change_appearance = (*bclass < BOT_CLASS_EX_START || *bclass == BOT_CLASS_ARCHMAGE); + + if (can_change_appearance && (!race || !gender || !skin || !face || !hairstyle || !haircolor || !features)) + return ret_err(handler); + if (!can_change_appearance && (race || gender || skin || face || hairstyle || haircolor || features)) + return ret_err(handler); + if (soundset && (*soundset < 1 || *soundset > SOUND_SETS_COUNT)) + return ret_err(handler); + + if (*bclass >= BOT_CLASS_END || (*bclass < BOT_CLASS_EX_START && !((1u << (*bclass - 1)) & CLASSMASK_ALL_PLAYABLE))) + return ret_err_invalid_arg(handler, "class", bclass); + + std::string namestr; + normalizePlayerName(namestr); + if (!consoleToUtf8(*name, namestr)) + return ret_err_invalid_arg(handler, "name"); + namestr[0] = std::toupper(namestr[0]); + + if (race && !((1u << (*race - 1)) & RACEMASK_ALL_PLAYABLE)) + return ret_err_invalid_arg(handler, "race", race); + + if (can_change_appearance && *gender != GENDER_MALE && *gender != GENDER_FEMALE) + return ret_err_invalid_arg(handler, "gender", gender); + + // class / race combination check + if ((*bclass < BOT_CLASS_EX_START && !sObjectMgr->GetPlayerInfo(*race, *bclass)) || + (*bclass == BOT_CLASS_ARCHMAGE && *race != RACE_HUMAN)) + return ret_err_invalid_args_for(handler, "class", GetClassName(*bclass, handler->GetSessionDbcLocale())); + + if (can_change_appearance && !IsValidVisual(*race, *gender, *skin, *face, *hairstyle, *haircolor, *features)) + return ret_err_invalid_args_for(handler, "race", GetRaceName(*race, handler->GetSessionDbcLocale())); + + //here we force races for custom classes + switch (*bclass) + { + case BOT_CLASS_BM: + case BOT_CLASS_SPHYNX: + case BOT_CLASS_DREADLORD: + case BOT_CLASS_SPELLBREAKER: + case BOT_CLASS_CRYPT_LORD: + race = 15; //RACE_SKELETON + break; + case BOT_CLASS_NECROMANCER: + race = RACE_HUMAN; + break; + case BOT_CLASS_DARK_RANGER: + race = RACE_BLOODELF; + break; + case BOT_CLASS_SEA_WITCH: + race = 13; //RACE_NAGA + break; + } + + //get normalized modelID + uint32 modelId = can_change_appearance ? SoundSetModelsArray[RaceToRaceOffset[*race]][*gender][soundset ? *soundset - 1 : urand(0u, 2u)] : 0; + + uint32 newentry = 0; + QueryResult creres = WorldDatabase.PQuery("SELECT entry FROM creature_template WHERE entry = {}", BOT_ENTRY_CREATE_BEGIN); + if (!creres) + newentry = BOT_ENTRY_CREATE_BEGIN; + else + { + creres = WorldDatabase.PQuery("SELECT MIN(entry) FROM creature_template WHERE entry >= {} AND entry IN (SELECT entry FROM creature_template) AND entry+1 NOT IN (SELECT entry FROM creature_template)", BOT_ENTRY_CREATE_BEGIN); + ASSERT(creres); + Field* field = creres->Fetch(); + newentry = field[0].GetUInt32() + 1; + } + + WorldDatabaseTransaction trans = WorldDatabase.BeginTransaction(); + trans->Append("DROP TEMPORARY TABLE IF EXISTS creature_template_temp_npcbot_create"); + trans->PAppend("CREATE TEMPORARY TABLE creature_template_temp_npcbot_create ENGINE=MEMORY SELECT * FROM creature_template WHERE entry = (SELECT entry FROM creature_template_npcbot_extras WHERE class = {} LIMIT 1)", uint32(*bclass)); + trans->PAppend("UPDATE creature_template_temp_npcbot_create SET entry = {}, name = \"{}\"", newentry, namestr.c_str()); + if (modelId) + trans->PAppend("UPDATE creature_template_temp_npcbot_create SET modelid1 = {}", modelId); + trans->Append("INSERT INTO creature_template SELECT * FROM creature_template_temp_npcbot_create"); + trans->Append("DROP TEMPORARY TABLE creature_template_temp_npcbot_create"); + trans->PAppend("REPLACE INTO creature_template_npcbot_extras VALUES ({}, {}, {})", newentry, uint32(*bclass), uint32(*race)); + trans->PAppend("REPLACE INTO creature_equip_template SELECT {}, 1, ids.itemID1, ids.itemID2, ids.itemID3, -1 FROM (SELECT itemID1, itemID2, itemID3 FROM creature_equip_template WHERE CreatureID = (SELECT entry FROM creature_template_npcbot_extras WHERE class = {} LIMIT 1)) ids", newentry, uint32(*bclass)); + if (can_change_appearance) + trans->PAppend("REPLACE INTO creature_template_npcbot_appearance VALUES ({}, \"{}\", {}, {}, {}, {}, {}, {})", + newentry, namestr.c_str(), uint32(*gender), uint32(*skin), uint32(*face), uint32(*hairstyle), uint32(*haircolor), uint32(*features)); + WorldDatabase.DirectCommitTransaction(trans); + + handler->PSendSysMessage("New NPCBot %s (class %u) is created with entry %u and will be available for spawning after server restart.", namestr.c_str(), uint32(*bclass), newentry); + return true; + } + + static bool HandleNpcBotSpawnCommand(ChatHandler* handler, Optional creVal) + { + if (!creVal) + { + handler->SendSysMessage(".npcbot spawn"); + handler->SendSysMessage("Adds new npcbot spawn of given entry in world. You can shift-link the npc"); + handler->SendSysMessage("Syntax: .npcbot spawn #entry"); + handler->SetSentErrorMessage(true); + return false; + } + + char* charID = handler->extractKeyFromLink((char*)creVal->c_str(), "Hcreature_entry"); + if (!charID) + return false; + + uint32 id = uint32(atoi(charID)); + + CreatureTemplate const* creInfo = sObjectMgr->GetCreatureTemplate(id); + + if (!creInfo) + { + handler->PSendSysMessage("creature %u does not exist!", id); + handler->SetSentErrorMessage(true); + return false; + } + + if (!creInfo->IsNPCBot()) + { + handler->PSendSysMessage("creature %u is not a npcbot!", id); + handler->SetSentErrorMessage(true); + return false; + } + + if (id == BOT_ENTRY_MIRROR_IMAGE_BM) + { + handler->PSendSysMessage("creature %u is a mirror image and cannot be spawned!", id); + handler->SetSentErrorMessage(true); + return false; + } + + if (BotDataMgr::SelectNpcBotData(id)) + { + handler->PSendSysMessage("Npcbot %u already exists in `characters_npcbot` table!", id); + handler->SendSysMessage("If you want to move this bot to a new location use '.npcbot move' command"); + handler->SetSentErrorMessage(true); + return false; + } + + WorldDatabasePreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_SEL_CREATURE_BY_ID); + //"SELECT guid FROM creature WHERE id = ?", CONNECTION_SYNCH + stmt->setUInt32(0, id); + PreparedQueryResult res2 = WorldDatabase.Query(stmt); + if (res2) + { + handler->PSendSysMessage("Npcbot %u already exists in `creature` table!", id); + handler->SetSentErrorMessage(true); + return false; + } + + Player* chr = handler->GetSession()->GetPlayer(); + + if (/*Transport* trans = */chr->GetTransport()) + { + handler->SendSysMessage("Cannot spawn bots on transport!"); + handler->SetSentErrorMessage(true); + return false; + } + + //float x = chr->GetPositionX(); + //float y = chr->GetPositionY(); + //float z = chr->GetPositionZ(); + //float o = chr->GetOrientation(); + Map* map = chr->GetMap(); + + if (map->Instanceable()) + { + handler->SendSysMessage("Cannot spawn bots in instances!"); + handler->SetSentErrorMessage(true); + return false; + } + + Creature* creature = new Creature(); + if (!creature->Create(map->GenerateLowGuid(), map, chr->GetPhaseMaskForSpawn(), id, *chr)) + { + delete creature; + handler->SendSysMessage("Creature is not created!"); + handler->SetSentErrorMessage(true); + return false; + } + + NpcBotExtras const* _botExtras = BotDataMgr::SelectNpcBotExtras(id); + if (!_botExtras) + { + delete creature; + handler->PSendSysMessage("No class/race data found for bot %u!", id); + handler->SetSentErrorMessage(true); + return false; + } + + uint8 bot_spec = bot_ai::SelectSpecForClass(_botExtras->bclass); + BotDataMgr::AddNpcBotData(id, bot_ai::DefaultRolesForClass(_botExtras->bclass, bot_spec), bot_spec, creature->GetCreatureTemplate()->faction); + + creature->SaveToDB(map->GetId(), (1 << map->GetSpawnMode()), chr->GetPhaseMaskForSpawn()); + + uint32 db_guid = creature->GetSpawnId(); + if (!creature->LoadBotCreatureFromDB(db_guid, map)) + { + delete creature; + handler->SendSysMessage("Cannot load npcbot from DB!"); + handler->SetSentErrorMessage(true); + return false; + } + + sObjectMgr->AddCreatureToGrid(db_guid, sObjectMgr->GetCreatureData(db_guid)); + + handler->SendSysMessage("NpcBot successfully spawned"); + return true; + } + + static bool HandleNpcBotSpawnedCommand(ChatHandler* handler) + { + std::unique_lock lock(*BotDataMgr::GetLock()); + NpcBotRegistry const& all_bots = BotDataMgr::GetExistingNPCBots(); + std::stringstream ss; + if (all_bots.empty()) + ss << "No spawned bots found!"; + else + { + ss << "Found " << uint32(all_bots.size()) << " bots:"; + uint32 counter = 0; + for (Creature const* bot : all_bots) + { + ++counter; + + std::string bot_color_str; + std::string bot_class_str; + GetBotClassNameAndColor(bot->GetBotClass(), bot_color_str, bot_class_str); + + AreaTableEntry const* zone = sAreaTableStore.LookupEntry(bot->GetBotAI()->GetLastZoneId() ? bot->GetBotAI()->GetLastZoneId() : bot->GetZoneId()); + std::string zone_name = zone ? zone->AreaName[handler->GetSession() ? handler->GetSessionDbLocaleIndex() : 0] : "Unknown"; + + ss << "\n" << counter << ") " << bot->GetEntry() << ": " + << bot->GetName() << " - |c" << bot_color_str << bot_class_str << "|r - " + << "level " << uint32(bot->GetLevel()) << " - \"" << zone_name << "\" - " + << (bot->IsFreeBot() ? bot->GetBotAI()->GetBotOwnerGuid() ? "inactive (owned)" : bot->GetBotAI()->IsWanderer() ? "wandering" : "free" : "active"); + } + } + + handler->SendSysMessage(ss.str().c_str()); + return true; + } + + static bool HandleNpcBotSpawnedFreeCommand(ChatHandler* handler) + { + std::unique_lock lock(*BotDataMgr::GetLock()); + NpcBotRegistry const& all_bots = BotDataMgr::GetExistingNPCBots(); + //using std::remove_if with sets requires c++20 + std::vector free_bots; + free_bots.reserve(all_bots.size()); + for (Creature const* bot : all_bots) + if (BotDataMgr::SelectNpcBotData(bot->GetEntry())->owner == 0) + free_bots.push_back(bot); + std::stringstream ss; + if (free_bots.empty()) + ss << "No free bots found!"; + else + { + ss << "Found " << uint32(free_bots.size()) << " free bots:"; + uint32 counter = 0; + for (Creature const* bot : free_bots) + { + ++counter; + + std::string bot_color_str; + std::string bot_class_str; + GetBotClassNameAndColor(bot->GetBotClass(), bot_color_str, bot_class_str); + + AreaTableEntry const* zone = sAreaTableStore.LookupEntry(bot->GetBotAI()->GetLastZoneId() ? bot->GetBotAI()->GetLastZoneId() : bot->GetZoneId()); + std::string zone_name = zone ? zone->AreaName[handler->GetSession() ? handler->GetSessionDbLocaleIndex() : 0] : "Unknown"; + + ss << '\n' << counter << ") " << bot->GetEntry() << ": " + << bot->GetName() << " - |c" << bot_color_str << bot_class_str << "|r - " + << "level " << uint32(bot->GetLevel()) << " - \"" << zone_name << '"' + << (bot->GetBotAI()->HasRealEquipment() ? " |cff00ffff(has equipment!)|r" : ""); + } + } + + handler->SendSysMessage(ss.str().c_str()); + return true; + } + + static bool HandleNpcBotGearScoreCommand(ChatHandler* handler, Optional class_name) + { + Player* owner = handler->GetSession()->GetPlayer(); + Unit* unit = owner->GetSelectedUnit(); + if (!(unit && owner->GetBotMgr()->GetBot(unit->GetGUID())) && !class_name) + { + handler->SendSysMessage(".npcbot gs [#class_name]"); + handler->SendSysMessage("Lists GearScore of your selected NPCBot or all bots by #class_name"); + handler->SetSentErrorMessage(true); + return false; + } + std::list bots; + if (class_name) + { + std::string cname(*class_name); + for (auto const c : cname) + { + if (!std::islower(c)) + { + handler->SendSysMessage("Bot class name must be in lower case!"); + return true; + } + } + + uint8 bot_class = BotMgr::BotClassByClassName(cname); + if (bot_class == BOT_CLASS_NONE) + { + handler->PSendSysMessage("Unknown bot name or class %s!", cname.c_str()); + return true; + } + + bots = owner->GetBotMgr()->GetAllBotsByClass(bot_class); + if (bots.empty()) + { + handler->PSendSysMessage("No bots of class %u found!", uint32(bot_class)); + return true; + } + } + else + bots.push_back(unit->ToCreature()); + + for (Creature const* bot : bots) + { + auto scores = bot->GetBotAI()->GetBotGearScores(); + handler->PSendSysMessage("%s's GearScore total: %u, average: %u", bot->GetName().c_str(), uint32(scores.first), uint32(scores.second)); + } + + return true; + } + + static bool HandleNpcBotUseOnBotSpellCommand(ChatHandler* handler, Optional>> spell_name_parts_or_info) + { + Player* player = handler->GetSession()->GetPlayer(); + Creature* target = handler->getSelectedCreature(); + + if (!spell_name_parts_or_info) + { + handler->SendSysMessage(".npcbot useonbot spell [#spell_name]"); + handler->SendSysMessage("Attempts to cast spell by name on selected bot, bypassing client restrictions"); + handler->SetSentErrorMessage(true); + return false; + } + + if (!target || !target->IsNPCBot()) + { + handler->SendSysMessage("No NPCBot selected"); + handler->SetSentErrorMessage(true); + return false; + } + + uint32 spellId = 0; + std::string spellname; + if (spell_name_parts_or_info->holds_alternative()) + spellId = spell_name_parts_or_info->get()->Id; + else + { + auto const& vec = spell_name_parts_or_info->get>(); + spellname = vec[0]; + for (std::size_t i = 1; i < vec.size(); ++i) + spellname += ' ' + vec[i]; + + if (spellname.size() >= 2 && spellname[0] == '[' && spellname[spellname.size() - 1] == ']') + spellname = spellname.substr(1, spellname.size() - 2); + + LocaleConstant locale = handler->GetSession()->GetSessionDbcLocale(); + for (auto const& kv : player->GetSpellMap()) + { + if (kv.second.state != PLAYERSPELL_REMOVED && kv.second.active && !kv.second.disabled) + { + SpellInfo const* info = sSpellMgr->GetSpellInfo(kv.first); + if (info && info->SpellName[locale] == spellname) + { + spellId = info->Id; + break; + } + } + } + } + + SpellInfo const* spellInfo = spellId ? sSpellMgr->AssertSpellInfo(spellId) : nullptr; + if (!spellInfo) + { + handler->PSendSysMessage(LANG_COMMAND_NOSPELLFOUND); + handler->SetSentErrorMessage(true); + return false; + } + + // silently cancel + Unit* mover = handler->GetSession()->GetGameClient()->GetActivelyMovedUnit(); + if (spellInfo->IsPassive() || !spellInfo->IsPositive() || player->isPossessing() || player->IsInFlight() || + !mover || (mover != player && mover->GetTypeId() == TYPEID_PLAYER)) + return true; + + SpellInfo const* actualSpellInfo = spellInfo->GetAuraRankForLevel(target->GetLevel()); + if (actualSpellInfo) + spellInfo = actualSpellInfo; + + SpellCastTargets targets; + targets.SetUnitTarget(target); + Spell* spell = new Spell(player, spellInfo, TRIGGERED_NONE); + spell->m_cast_count = 1; + spell->prepare(targets); + + return true; + } + + static bool HandleNpcBotUseOnBotItemCommand(ChatHandler* handler, Optional>> item_name_parts_or_template) + { + Player* player = handler->GetSession()->GetPlayer(); + Creature* target = handler->getSelectedCreature(); + + if (!item_name_parts_or_template) + { + handler->SendSysMessage(".npcbot useonbot item [#item_name]"); + handler->SendSysMessage("Attempts to cast item spell by item name on selected bot, bypassing client restrictions"); + handler->SetSentErrorMessage(true); + return false; + } + + if (!target || !target->IsNPCBot()) + { + handler->SendSysMessage("No NPCBot selected"); + handler->SetSentErrorMessage(true); + return false; + } + + Item* item = nullptr; + if (item_name_parts_or_template->holds_alternative()) + item = player->GetItemByEntry(item_name_parts_or_template->get()->ItemId); + else + { + auto const& vec = item_name_parts_or_template->get>(); + std::string itemname = vec[0]; + for (std::size_t i = 1; i < vec.size(); ++i) + itemname += ' ' + vec[i]; + + if (itemname.size() >= 2 && itemname[0] == '[' && itemname[itemname.size() - 1] == ']') + itemname = itemname.substr(1, itemname.size() - 2); + + LocaleConstant locale = handler->GetSession()->GetSessionDbcLocale(); + + // find the item + for (uint8 i = EQUIPMENT_SLOT_START; i < INVENTORY_SLOT_ITEM_END && !item; ++i) + { + Item* pItem = player->GetItemByPos(INVENTORY_SLOT_BAG_0, i); + if (!pItem || pItem->IsInTrade()) + continue; + + ItemTemplate const* pItemTemplate = pItem->GetTemplate(); + std::string pItemName = pItemTemplate->Name1; + if (ItemLocale const* il = sObjectMgr->GetItemLocale(pItemTemplate->ItemId)) + ObjectMgr::GetLocaleString(il->Name, locale, pItemName); + if (pItemName == itemname) + item = pItem; + } + for (uint8 i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END && !item; ++i) + { + if (Bag* pBag = player->GetBagByPos(i)) + { + for (uint32 j = 0; j < pBag->GetBagSize() && !item; ++j) + { + Item* pItem = player->GetItemByPos(i, j); + if (!pItem || pItem->IsInTrade()) + continue; + + ItemTemplate const* pItemTemplate = pItem->GetTemplate(); + std::string pItemName = pItemTemplate->Name1; + if (ItemLocale const* il = sObjectMgr->GetItemLocale(pItemTemplate->ItemId)) + ObjectMgr::GetLocaleString(il->Name, locale, pItemName); + if (pItemName == itemname) + item = pItem; + } + } + } + } + + if (!item) + { + handler->SendSysMessage(LANG_COMMAND_NOITEMFOUND); + handler->SetSentErrorMessage(true); + return false; + } + + // find usable spell + ItemTemplate const* itemtemplate = item->GetTemplate(); + uint32 spellId = 0; + for (auto const& itemspell : itemtemplate->Spells) + { + if (itemspell.SpellId > 0 && itemspell.SpellTrigger == ITEM_SPELLTRIGGER_ON_USE) + { + spellId = itemspell.SpellId; + break; + } + } + + SpellInfo const* spellInfo = spellId ? sSpellMgr->GetSpellInfo(spellId) : nullptr; + if (!spellInfo) + { + handler->PSendSysMessage(LANG_COMMAND_NOSPELLFOUND); + handler->SetSentErrorMessage(true); + return false; + } + + if (itemtemplate->InventoryType != INVTYPE_NON_EQUIP && !item->IsEquipped()) + { + player->SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, item, nullptr); + handler->SetSentErrorMessage(true); + return false; + } + + InventoryResult msg = player->CanUseItem(item); + if (msg != EQUIP_ERR_OK) + { + player->SendEquipError(msg, item, nullptr); + handler->SetSentErrorMessage(true); + return false; + } + + if (itemtemplate->Class == ITEM_CLASS_CONSUMABLE && !itemtemplate->HasFlag(ITEM_FLAG_IGNORE_DEFAULT_ARENA_RESTRICTIONS) && player->InArena()) + { + player->SendEquipError(EQUIP_ERR_NOT_DURING_ARENA_MATCH, item, nullptr); + handler->SetSentErrorMessage(true); + return false; + } + + if (itemtemplate->HasFlag(ITEM_FLAG_NOT_USEABLE_IN_ARENA) && player->InArena()) + { + player->SendEquipError(EQUIP_ERR_NOT_DURING_ARENA_MATCH, item, nullptr); + handler->SetSentErrorMessage(true); + return false; + } + + if (player->IsInCombat() && !spellInfo->CanBeUsedInCombat()) + { + player->SendEquipError(EQUIP_ERR_NOT_IN_COMBAT, item, nullptr); + handler->SetSentErrorMessage(true); + return false; + } + + // silently cancel + Unit* mover = handler->GetSession()->GetGameClient()->GetActivelyMovedUnit(); + if (spellInfo->IsPassive() || !spellInfo->IsPositive() || player->isPossessing() || player->IsInFlight() || + !mover || (mover != player && mover->GetTypeId() == TYPEID_PLAYER)) + return true; + + SpellInfo const* actualSpellInfo = spellInfo->GetAuraRankForLevel(target->GetLevel()); + if (actualSpellInfo) + spellInfo = actualSpellInfo; + + SpellCastTargets targets; + targets.SetUnitTarget(target); + Spell* spell = new Spell(player, spellInfo, TRIGGERED_NONE); + spell->m_CastItem = item; + spell->m_cast_count = 1; + spell->m_glyphIndex = 0; + spell->prepare(targets); + + return true; + } + + static bool HandleNpcBotInfoCommand(ChatHandler* handler, Optional> player_lg_name) + { + Player* player = handler->GetSession() ? handler->GetSession()->GetPlayer() : nullptr; + std::string master_name = (player_lg_name && player_lg_name->holds_alternative()) ? player_lg_name->get() : ""; + if (!master_name.empty()) + normalizePlayerName(master_name); + ObjectGuid cached_guid = !master_name.empty() ? sCharacterCache->GetCharacterGuidByName(master_name) : ObjectGuid::Empty; + ObjectGuid master_guid = cached_guid ? cached_guid : + (player_lg_name && player_lg_name->holds_alternative()) ? ObjectGuid::Create(player_lg_name->get()) : + player && player->GetTarget().IsPlayer() ? player->GetTarget() : ObjectGuid::Empty; + + if (master_guid.IsEmpty()) + { + if (!master_name.empty()) + { + handler->PSendSysMessage("Player '%s' is not found!", master_name.c_str()); + handler->SetSentErrorMessage(true); + return false; + } + + handler->SendSysMessage(".npcbot info"); + handler->SendSysMessage("Lists NpcBots count of each class owned by selected player. You can use this on self and your party members"); + handler->SetSentErrorMessage(true); + return false; + } + if (master_name.empty() && !sCharacterCache->GetCharacterNameByGuid(master_guid, master_name)) + { + handler->PSendSysMessage("Player %u is not found!", master_guid.GetCounter()); + handler->SetSentErrorMessage(true); + return false; + } + if (BotDataMgr::GetOwnedBotsCount(master_guid) == 0) + { + handler->PSendSysMessage("%s (%u) has no NpcBots!", master_name.c_str(), master_guid.GetCounter()); + handler->SetSentErrorMessage(true); + return false; + } + + std::vector guidvec; + BotDataMgr::GetNPCBotGuidsByOwner(guidvec, master_guid); + Player* master = ObjectAccessor::FindConnectedPlayer(master_guid); + BotMap const* map = master ? master->GetBotMgr()->GetBotMap() : nullptr; + uint32 map_size = map ? uint32(map->size()) : 0u; + if (map) + { + guidvec.erase(std::remove_if(std::begin(guidvec), std::end(guidvec), + [bmap = map](ObjectGuid guid) { return bmap->find(guid) != bmap->end(); } + ), std::end(guidvec)); + } + + handler->PSendSysMessage("Listing NpcBots for %s, guid %u%s:", master_name.c_str(), master_guid.GetCounter(), !master ? " (offline)" : ""); + handler->PSendSysMessage("Owned NpcBots: %u (active: %u)", uint32(guidvec.size()) + map_size, map_size); + if (map) + { + for (uint8 i = BOT_CLASS_WARRIOR; i != BOT_CLASS_END; ++i) + { + uint8 count = 0; + uint8 alivecount = 0; + for (BotMap::const_iterator itr = map->begin(); itr != map->end(); ++itr) + { + if (Creature* cre = itr->second) + { + if (cre->GetBotClass() == i) + { + ++count; + if (cre->IsAlive()) + ++alivecount; + } + } + } + if (count == 0) + continue; + + char const* bclass; + switch (i) + { + case BOT_CLASS_WARRIOR: bclass = "Warriors"; break; + case BOT_CLASS_PALADIN: bclass = "Paladins"; break; + case BOT_CLASS_MAGE: bclass = "Mages"; break; + case BOT_CLASS_PRIEST: bclass = "Priests"; break; + case BOT_CLASS_WARLOCK: bclass = "Warlocks"; break; + case BOT_CLASS_DRUID: bclass = "Druids"; break; + case BOT_CLASS_DEATH_KNIGHT: bclass = "Death Knights"; break; + case BOT_CLASS_ROGUE: bclass = "Rogues"; break; + case BOT_CLASS_SHAMAN: bclass = "Shamans"; break; + case BOT_CLASS_HUNTER: bclass = "Hunters"; break; + case BOT_CLASS_BM: bclass = "Blademasters"; break; + case BOT_CLASS_SPHYNX: bclass = "Destroyers"; break; + case BOT_CLASS_ARCHMAGE: bclass = "Archmagi"; break; + case BOT_CLASS_DREADLORD: bclass = "Dreadlords"; break; + case BOT_CLASS_SPELLBREAKER: bclass = "Spell Breakers"; break; + case BOT_CLASS_DARK_RANGER: bclass = "Dark Rangers"; break; + case BOT_CLASS_NECROMANCER: bclass = "Necromancers"; break; + case BOT_CLASS_SEA_WITCH: bclass = "Sea Witches"; break; + case BOT_CLASS_CRYPT_LORD: bclass = "Crypt Lords"; break; + default: bclass = "Unknown Class"; break; + } + handler->PSendSysMessage("%s: %u (alive: %u)", bclass, count, alivecount); + } + } + + handler->PSendSysMessage("%u inactive bots:", uint32(guidvec.size())); + for (ObjectGuid guid : guidvec) + { + Creature const* bot = BotDataMgr::FindBot(guid.GetEntry()); + std::string ccolor, cname; + GetBotClassNameAndColor(bot ? bot->GetBotClass() : uint8(BOT_CLASS_NONE), ccolor, cname); + handler->PSendSysMessage("%s (%s)", bot ? bot->GetName().c_str() : "Unknown", "|c" + ccolor + cname + "|r"); + } + + return true; + } + + static bool HandleNpcBotCommandStandstillCommand(ChatHandler* handler) + { + Player* owner = handler->GetSession()->GetPlayer(); + + if (!owner->HaveBot()) + { + handler->SendSysMessage(".npcbot command standstill"); + handler->SendSysMessage("Forces your npcbots to stop all movement and remain stationed"); + handler->SetSentErrorMessage(true); + return false; + } + + std::string msg; + Unit* target = owner->GetSelectedUnit(); + if (target && owner->GetBotMgr()->GetBot(target->GetGUID())) + { + target->ToCreature()->GetBotAI()->SetBotCommandState(BOT_COMMAND_STAY); + msg = target->GetName() + "'s command state set to 'STAY'"; + } + else + { + owner->GetBotMgr()->SendBotCommandState(BOT_COMMAND_STAY); + msg = "Bots' command state set to 'STAY'"; + } + + handler->SendSysMessage(msg.c_str()); + return true; + } + + static bool HandleNpcBotCommandStopfullyCommand(ChatHandler* handler) + { + Player* owner = handler->GetSession()->GetPlayer(); + + if (!owner->HaveBot()) + { + handler->SendSysMessage(".npcbot command stopfully"); + handler->SendSysMessage("Forces your npcbots to stop all activity"); + handler->SetSentErrorMessage(true); + return false; + } + + std::string msg; + Unit* target = owner->GetSelectedUnit(); + if (target && owner->GetBotMgr()->GetBot(target->GetGUID())) + { + target->ToCreature()->GetBotAI()->SetBotCommandState(BOT_COMMAND_FULLSTOP); + msg = target->GetName() + "'s command state set to 'FULLSTOP'"; + } + else + { + owner->GetBotMgr()->SendBotCommandState(BOT_COMMAND_FULLSTOP); + msg = "Bots' command state set to 'FULLSTOP'"; + } + + handler->SendSysMessage(msg.c_str()); + return true; + } + + static bool HandleNpcBotCommandNoLongCastCommand(ChatHandler* handler) + { + Player* owner = handler->GetSession()->GetPlayer(); + + if (!owner->HaveBot()) + { + handler->SendSysMessage(".npcbot command nolongcast"); + handler->SendSysMessage("Makes npcbots unable to cast spells with non-zero cast time"); + handler->SetSentErrorMessage(true); + return false; + } + + std::string msg; + if (!owner->GetBotMgr()->GetBotMap()->begin()->second->GetBotAI()->HasBotCommandState(BOT_COMMAND_NO_CAST_LONG)) + { + owner->GetBotMgr()->SendBotCommandState(BOT_COMMAND_NO_CAST_LONG); + msg = "Bots' command state set to 'NOLONGCAST'"; + } + else + { + owner->GetBotMgr()->SendBotCommandStateRemove(BOT_COMMAND_NO_CAST_LONG); + msg = "Bots' command state 'NOLONGCAST' was removed"; + } + + handler->SendSysMessage(msg.c_str()); + return true; + } + + static bool HandleNpcBotCommandNoCastCommand(ChatHandler* handler) + { + Player* owner = handler->GetSession()->GetPlayer(); + + if (!owner->HaveBot()) + { + handler->SendSysMessage(".npcbot command nocast"); + handler->SendSysMessage("Makes npcbots unable to cast ANY spells"); + handler->SetSentErrorMessage(true); + return false; + } + + std::string msg; + if (!owner->GetBotMgr()->GetBotMap()->begin()->second->GetBotAI()->HasBotCommandState(BOT_COMMAND_NO_CAST)) + { + owner->GetBotMgr()->SendBotCommandState(BOT_COMMAND_NO_CAST); + msg = "Bots' command state set to 'NOCAST'"; + } + else + { + owner->GetBotMgr()->SendBotCommandStateRemove(BOT_COMMAND_NO_CAST); + msg = "Bots' command state 'NOCAST' was removed"; + } + + handler->SendSysMessage(msg.c_str()); + return true; + } + + static bool HandleNpcBotCommandFollowOnlyCommand(ChatHandler* handler) + { + Player* owner = handler->GetSession()->GetPlayer(); + + if (!owner->HaveBot()) + { + handler->SendSysMessage(".npcbot command follow only"); + handler->SendSysMessage("Makes npcbots follow you and do nothing else"); + handler->SetSentErrorMessage(true); + return false; + } + + std::string msg; + if (!owner->GetBotMgr()->GetBotMap()->begin()->second->GetBotAI()->HasBotCommandState(BOT_COMMAND_INACTION)) + { + owner->GetBotMgr()->SendBotCommandState(BOT_COMMAND_INACTION); + msg = "Bots' command state set to 'INACTION'"; + } + else + { + owner->GetBotMgr()->SendBotCommandStateRemove(BOT_COMMAND_INACTION); + msg = "Bots' command state 'INACTION' was removed"; + } + + handler->SendSysMessage(msg.c_str()); + return true; + } + + static bool HandleNpcBotCommandFollowCommand(ChatHandler* handler) + { + Player* owner = handler->GetSession()->GetPlayer(); + + if (!owner->HaveBot()) + { + handler->SendSysMessage(".npcbot command follow"); + handler->SendSysMessage("Allows npcbots to follow you again if stopped"); + handler->SetSentErrorMessage(true); + return false; + } + + std::string msg; + Unit* target = owner->GetSelectedUnit(); + if (target && owner->GetBotMgr()->GetBot(target->GetGUID())) + { + target->ToCreature()->GetBotAI()->SetBotCommandState(BOT_COMMAND_FOLLOW); + msg = target->GetName() + "'s command state set to 'FOLLOW'"; + } + else + { + owner->GetBotMgr()->SendBotCommandState(BOT_COMMAND_FOLLOW); + msg = "Bots' command state set to 'FOLLOW'"; + } + + handler->SendSysMessage(msg.c_str()); + return true; + } + + static bool HandleNpcBotCommandWalkCommand(ChatHandler* handler) + { + Player* owner = handler->GetSession()->GetPlayer(); + + if (!owner->HaveBot()) + { + handler->SendSysMessage(".npcbot command walk"); + handler->SendSysMessage("Toggles walk mode for your npcbots"); + handler->SetSentErrorMessage(true); + return false; + } + + std::string msg; + bool isWalking = owner->GetBotMgr()->GetBotMap()->begin()->second->GetBotAI()->HasBotCommandState(BOT_COMMAND_WALK); + if (!isWalking) + { + owner->GetBotMgr()->SendBotCommandState(BOT_COMMAND_WALK); + msg = "Bots' movement mode is set to 'WALK'"; + } + else + { + owner->GetBotMgr()->SendBotCommandStateRemove(BOT_COMMAND_WALK); + msg = "Bots' movement mode is set to 'RUN'"; + } + + handler->SendSysMessage(msg.c_str()); + return true; + } + + static bool HandleNpcBotCommandNoGossipCommand(ChatHandler* handler) + { + Player* owner = handler->GetSession()->GetPlayer(); + + if (!owner->HaveBot()) + { + handler->SendSysMessage(".npcbot command nogossip"); + handler->SendSysMessage("Toggles gossip availability for your npcbots"); + handler->SetSentErrorMessage(true); + return false; + } + + std::string msg; + bool isNoGossipEnabled = owner->GetBotMgr()->GetBotMap()->begin()->second->GetBotAI()->HasBotCommandState(BOT_COMMAND_NOGOSSIP); + if (!isNoGossipEnabled) + { + owner->GetBotMgr()->SendBotCommandState(BOT_COMMAND_NOGOSSIP); + msg = "Bots' gossip is DISABLED"; + } + else + { + owner->GetBotMgr()->SendBotCommandStateRemove(BOT_COMMAND_NOGOSSIP); + msg = "Bots' gossip is ENABLED"; + } + + handler->SendSysMessage(msg.c_str()); + return true; + } + + static bool HandleNpcBotCommandReBindCommand(ChatHandler* handler, Optional> names) + { + static auto return_syntax = [](ChatHandler* chandler) -> bool { + chandler->SendSysMessage(".npcbot command rebind [#names...]"); + chandler->SendSysMessage("Re-binds selected/named unbound npcbot"); + chandler->SetSentErrorMessage(true); + return false; + }; + + static auto return_success = [](ChatHandler* chandler, Variant name_or_count) -> bool { + if (name_or_count.holds_alternative()) + chandler->PSendSysMessage("Successfully re-bound %u bot(s)", name_or_count.get()); + else + chandler->PSendSysMessage("Successfully re-bound %s", name_or_count.get().c_str()); + return true; + }; + + Player const* owner = handler->GetSession()->GetPlayer(); + + if (!owner->HaveBot() && BotDataMgr::GetOwnedBotsCount(owner->GetGUID()) == 0) + return return_syntax(handler); + + BotMgr* mgr = owner->GetBotMgr(); + + if (!names || names->empty()) + { + Creature const* bot = handler->getSelectedCreature(); + if (bot && bot->IsNPCBot() && !bot->IsTempBot() && !mgr->GetBot(bot->GetGUID()) && bot->GetBotAI()->HasBotCommandState(BOT_COMMAND_UNBIND) && + BotDataMgr::SelectNpcBotData(bot->GetEntry())->owner == owner->GetGUID().GetCounter()) + { + if (mgr->RebindBot(const_cast(bot)) != BOT_ADD_SUCCESS) + { + handler->PSendSysMessage("Failed to re-bind %s for some reason!", bot->GetName().c_str()); + handler->SetSentErrorMessage(true); + return false; + } + return return_success(handler, { bot->GetName() }); + } + return return_syntax(handler); + } + + uint32 count = 0; + for (decltype(names)::value_type::value_type name : *names) + { + for (decltype(name)::size_type i = 0u; i < name.size(); ++i) + if (name[i] == '_') + name[i] = ' '; + + std::vector bot_ids; + bot_ids.reserve(owner->GetBotMgr()->GetNpcBotsCount()); + for (auto const& kv : *owner->GetBotMgr()->GetBotMap()) + bot_ids.push_back(kv.first.GetEntry()); + + Creature const* bot = BotDataMgr::FindBot(name, owner->GetSession()->GetSessionDbLocaleIndex(), &bot_ids); + if (bot && bot->IsNPCBot() && !bot->IsTempBot() && !mgr->GetBot(bot->GetGUID()) && bot->GetBotAI()->HasBotCommandState(BOT_COMMAND_UNBIND) && + BotDataMgr::SelectNpcBotData(bot->GetEntry())->owner == owner->GetGUID().GetCounter()) + { + if (mgr->RebindBot(const_cast(bot)) != BOT_ADD_SUCCESS) + { + handler->PSendSysMessage("Failed to re-bind %s for some reason!", name.c_str()); + handler->SetSentErrorMessage(true); + continue; + } + ++count; + } + } + + if (count == 0) + { + handler->PSendSysMessage("Unable to re-bind any of %u bots!", uint32(names->size())); + handler->SetSentErrorMessage(true); + return false; + } + + return return_success(handler, { count }); + } + + static bool HandleNpcBotCommandUnBindCommand(ChatHandler* handler, Optional> names) + { + static auto return_syntax = [](ChatHandler* chandler) -> bool { + chandler->SendSysMessage(".npcbot command unbind [#names...]"); + chandler->SendSysMessage("Frees selected/named npcbot(s) temporarily. The bot will return to home location and wait until re-bound"); + chandler->SetSentErrorMessage(true); + return false; + }; + + static auto return_success = [](ChatHandler* chandler, Variant name_or_count) -> bool { + if (name_or_count.holds_alternative()) + chandler->PSendSysMessage("Successfully unbound %u bot(s)", name_or_count.get()); + else + chandler->PSendSysMessage("Successfully unbound %s", name_or_count.get().c_str()); + return true; + }; + + Player const* owner = handler->GetSession()->GetPlayer(); + + if (!owner->HaveBot()) + return return_syntax(handler); + + if (!names || names->empty()) + { + Unit const* target = handler->getSelectedCreature(); + Creature const* bot = target ? owner->GetBotMgr()->GetBot(target->GetGUID()) : nullptr; + if (bot && !bot->GetBotAI()->HasBotCommandState(BOT_COMMAND_UNBIND)) + { + owner->GetBotMgr()->UnbindBot(bot->GetGUID()); + return return_success(handler, { bot->GetName() }); + } + return return_syntax(handler); + } + + uint32 count = 0; + for (decltype(names)::value_type::value_type name : *names) + { + for (decltype(name)::size_type i = 0u; i < name.size(); ++i) + if (name[i] == '_') + name[i] = ' '; + + Creature const* bot = owner->GetBotMgr()->GetBotByName(name); + if (bot && !bot->GetBotAI()->HasBotCommandState(BOT_COMMAND_UNBIND)) + { + ++count; + owner->GetBotMgr()->UnbindBot(bot->GetGUID()); + } + } + + if (count == 0) + { + handler->PSendSysMessage("Unable to unbind any of %u bots!", uint32(names->size())); + handler->SetSentErrorMessage(true); + return false; + } + + return return_success(handler, { count }); + } + + static bool HandleNpcBotRemoveCommand(ChatHandler* handler) + { + Player* owner = handler->GetSession()->GetPlayer(); + Unit* u = owner->GetSelectedUnit(); + if (!u) + { + handler->SendSysMessage(".npcbot remove"); + handler->SendSysMessage("Frees selected npcbot from it's owner. Select player to remove all npcbots"); + handler->SetSentErrorMessage(true); + return false; + } + + Player* master = u->ToPlayer(); + if (master) + { + if (master->HaveBot()) + { + master->RemoveAllBots(BOT_REMOVE_DISMISS); + + if (!master->HaveBot()) + { + handler->SendSysMessage("Npcbots were successfully removed"); + handler->SetSentErrorMessage(true); + return true; + } + handler->SendSysMessage("Some npcbots were not removed!"); + handler->SetSentErrorMessage(true); + return false; + } + handler->SendSysMessage("Npcbots are not found!"); + handler->SetSentErrorMessage(true); + return false; + } + + Creature* cre = u->ToCreature(); + if (cre && cre->IsNPCBot() && !cre->IsFreeBot()) + { + master = cre->GetBotOwner(); + master->GetBotMgr()->RemoveBot(cre->GetGUID(), BOT_REMOVE_DISMISS); + if (master->GetBotMgr()->GetBot(cre->GetGUID()) == nullptr) + { + handler->SendSysMessage("NpcBot successfully removed"); + handler->SetSentErrorMessage(true); + return true; + } + handler->SendSysMessage("NpcBot was NOT removed for some stupid reason!"); + handler->SetSentErrorMessage(true); + return false; + } + + handler->SendSysMessage("You must select player or controlled npcbot"); + handler->SetSentErrorMessage(true); + return false; + } + + static bool HandleNpcBotReviveCommand(ChatHandler* handler) + { + Player* owner = handler->GetSession()->GetPlayer(); + Unit* u = owner->GetSelectedUnit(); + if (!u) + { + handler->SendSysMessage(".npcbot revive"); + handler->SendSysMessage("Revives selected npcbot. If player is selected, revives all selected player's npcbots"); + handler->SetSentErrorMessage(true); + return false; + } + + if (Player* master = u->ToPlayer()) + { + if (!master->HaveBot()) + { + handler->PSendSysMessage("%s has no npcbots!", master->GetName().c_str()); + handler->SetSentErrorMessage(true); + return false; + } + + master->GetBotMgr()->ReviveAllBots(); + handler->SendSysMessage("Npcbots revived"); + return true; + } + else if (Creature* bot = u->ToCreature()) + { + if (bot->GetBotAI()) + { + if (bot->IsAlive()) + { + handler->PSendSysMessage("%s is not dead", bot->GetName().c_str()); + handler->SetSentErrorMessage(true); + return false; + } + + BotMgr::ReviveBot(bot, (bot->GetBotOwner() == owner) ? owner->ToUnit() : bot->ToUnit()); + handler->PSendSysMessage("%s revived", bot->GetName().c_str()); + return true; + } + } + + handler->SendSysMessage("You must select player or npcbot"); + handler->SetSentErrorMessage(true); + return false; + } + + static bool HandleNpcBotAddCommand(ChatHandler* handler) + { + Player* owner = handler->GetSession()->GetPlayer(); + Unit* cre = owner->GetSelectedUnit(); + + if (!cre || cre->GetTypeId() != TYPEID_UNIT) + { + handler->SendSysMessage(".npcbot add"); + handler->SendSysMessage("Allows to hire selected uncontrolled bot"); + handler->SetSentErrorMessage(true); + return false; + } + + Creature* bot = cre->ToCreature(); + if (!bot || !bot->IsNPCBot() || bot->GetBotAI()->GetBotOwnerGuid() || bot->GetBotAI()->IsWanderer()) + { + handler->SendSysMessage("You must select uncontrolled non-wandering npcbot"); + handler->SetSentErrorMessage(true); + return false; + } + + ObjectGuid::LowType guidlow = owner->GetGUID().GetCounter(); + BotDataMgr::UpdateNpcBotData(bot->GetEntry(), NPCBOT_UPDATE_OWNER, &guidlow); + bot->GetBotAI()->ReinitOwner(); + + if (owner->GetBotMgr()->AddBot(bot) == BOT_ADD_SUCCESS) + { + handler->PSendSysMessage("%s is now your npcbot", bot->GetName().c_str()); + return true; + } + + handler->SendSysMessage("NpcBot is NOT added for some reason!"); + handler->SetSentErrorMessage(true); + return false; + } + + static bool HandleNpcBotReloadConfigCommand(ChatHandler* handler) + { + TC_LOG_INFO("misc", "Re-Loading config settings..."); + sWorld->LoadConfigSettings(true); + sMapMgr->InitializeVisibilityDistanceInfo(); + handler->SendGlobalGMSysMessage("World config settings reloaded."); + BotMgr::ReloadConfig(); + handler->SendGlobalGMSysMessage("NpcBot config settings reloaded."); + return true; + } +}; + +void AddSC_script_bot_commands() +{ + new script_bot_commands(); +} + +#ifdef _MSC_VER +# pragma warning(pop) +#endif diff --git a/src/server/game/AI/NpcBots/botcommon.h b/src/server/game/AI/NpcBots/botcommon.h new file mode 100644 index 000000000..4e3248156 --- /dev/null +++ b/src/server/game/AI/NpcBots/botcommon.h @@ -0,0 +1,591 @@ +#ifndef _BOTCOMMON_H +#define _BOTCOMMON_H + +#include "ObjectGuid.h" +#include "SharedDefines.h" +#include "SpellAuraDefines.h" + +#include +#include + +/* +NpcBot System by Trickerer (onlysuffering@gmail.com) +Original patch from: LordPsyan https://bitbucket.org/lordpsyan/trinitycore-patches/src/3b8b9072280e/Individual/11185-BOTS-NPCBots.patch +*/ + +constexpr std::size_t MAX_BOT_LOG_PARAMS = 5; +constexpr std::size_t MAX_BOT_LOG_PARAM_LENGTH = 50; + +struct Position; + +typedef std::vector > AoeSpotsVec; +typedef std::vector AoeSafeSpotsVec; + +enum BotCommonValues +{ +//MISC + BOT_GIVER_ENTRY = 70000, + BOT_ENTRY_BEGIN = 70001, + //BOT_ENTRY_END = 71000, + BOT_ENTRY_CREATE_BEGIN = 70800, // 70800+ reserved for bot creation + //BOT_PET_ENTRY_BEGIN = 70501, + //BOT_PET_ENTRY_END = 70550, + BOT_ENTRY_MIRROR_IMAGE_BM = 70552, + BOT_MAX_CHASE_RANGE = 120, //yds + //BOT_EVADE_TIME = 3000, //ms +//COMMON GAMEOBJECTS + GO_REFRESHMENT_TABLE_1 = 186812,//lvl 65 req70 + GO_REFRESHMENT_TABLE_2 = 193061,//lvl 80 req80 + GO_SOULWELL_1 = 181621,//lvl 60 req68 + GO_SOULWELL_2 = 193169,//lvl 69 req80 + GO_BOT_MONEY_BAG = 186736, +//COMMON CDs + POTION_CD = 60000,//default 60sec potion cd + REGEN_CD = 1000, //update hp/mana every X milliseconds +//COMMON TIMERS + ITEM_ENCHANTMENT_EXPIRE_TIMER = 3600000, //1 Hour + REVIVE_TIMER_DEFAULT = 180000, //3 Minutes + REVIVE_TIMER_MEDIUM = 90000, //1.5 Minutes + REVIVE_TIMER_SHORT = 60000, //1 Minute + INOUTDOORS_ENSURE_TIMER = 1500, + BOT_GROUP_UPDATE_TIMER = 2000, +//VEHICLE CREATURES + CREATURE_NEXUS_SKYTALON_1 = 32535, // [Q] Aces High + CREATURE_EOE_SKYTALON_N = 30161, // Eye of Eternity + CREATURE_EOE_SKYTALON_H = 31752, + CREATURE_OCULUS_DRAKE_RUBY = 27756, // Oculus + CREATURE_OCULUS_DRAKE_EMERALD = 27692, + CREATURE_OCULUS_DRAKE_AMBER = 27755, + //CREATURE_TOC_STEED_QUELDOREI = 33845, // Argent Tournament + //CREATURE_TOC_NIGHTSABER = 33319, + //CREATURE_TOC_STEED_STORMWIND = 33217, + //CREATURE_TOC_MECHANOSTRIDER = 33317, + //CREATURE_TOC_RAM = 33316, + //CREATURE_TOC_ELEKK = 33318, + //CREATURE_TOC_HAWKSTRIDER_SUNREAVER = 33844, + //CREATURE_TOC_RAPTOR = 33321, + //CREATURE_TOC_WARHORSE = 33324, + //CREATURE_TOC_WOLF = 33320, + //CREATURE_TOC_HAWKSTRIDER_SILVERMOON = 33323, + //CREATURE_TOC_KODO = 33322, + CREATURE_TOC5_WARHORSE = 35644, // Trial of Champion + CREATURE_TOC5_BATTLEWORG = 36558, + CREATURE_ULDUAR_DEMOLISHER = 33109, // Ulduar + CREATURE_ULDUAR_SIEGE_ENGINE = 33060, + CREATURE_ULDUAR_CHOPPER = 33062, + CREATURE_ULDUAR_CHOPPER1 = 34045, + CREATURE_ICC_BONE_SPIKE1 = 36619, // Icecrown Citadel + CREATURE_ICC_BONE_SPIKE2 = 38712, + CREATURE_ICC_BONE_SPIKE3 = 38711, + CREATURE_ICC_GUNSHIPCANNON_ALLIANCE = 36838, + CREATURE_ICC_GUNSHIPCANNON_HORDE = 36839, + CREATURE_ICC_MUTATED_ABOMINATION1 = 38285, + CREATURE_ICC_MUTATED_ABOMINATION2 = 38788, + CREATURE_ICC_MUTATED_ABOMINATION3 = 38789, + CREATURE_ICC_MUTATED_ABOMINATION4 = 38790, + CREATURE_ICC_MUTATED_ABOMINATION5 = 37672, + CREATURE_ICC_MUTATED_ABOMINATION6 = 38605, + CREATURE_ICC_MUTATED_ABOMINATION7 = 38786, + CREATURE_ICC_MUTATED_ABOMINATION8 = 38787, +//COMMON AOE TRIGGERS + CREATURE_FOCUS_FIRE_N = 18374, + CREATURE_FOCUS_FIRE_H = 20308, + CREATURE_ZA_FIRE_BOMB = 23920, + CREATURE_UK_SHADOW_AXE_N = 23997, + CREATURE_UK_SHADOW_AXE_H = 31835, + CREATURE_EOE_STATIC_FIELD = 30592, + CREATURE_ICC_OOZE_PUDDLE = 37690, + GAMEOBJECT_HOT_COAL = 178164, +//COMMON ENEMY CREATURES + CREATURE_BOSS_EREGOS_N = 27656, + CREATURE_BOSS_EREGOS_H = 31561, + CREATURE_ICC_SINDRAGOSA1 = 36853, + CREATURE_ICC_SINDRAGOSA2 = 38265, + CREATURE_ICC_SINDRAGOSA3 = 38266, + CREATURE_ICC_SINDRAGOSA4 = 38267, + CREATURE_ICC_ICE_TOMB1 = 36980, + CREATURE_ICC_ICE_TOMB2 = 38320, + CREATURE_ICC_ICE_TOMB3 = 38321, + CREATURE_ICC_ICE_TOMB4 = 38322, + CREATURE_ICC_VALKYR_LK1 = 36609, + CREATURE_ICC_VALKYR_LK2 = 39120, + CREATURE_ICC_VALKYR_LK3 = 39121, + CREATURE_ICC_VALKYR_LK4 = 39122, + CREATURE_ICC_ICE_SPHERE1 = 36633, + CREATURE_ICC_ICE_SPHERE2 = 39305, + CREATURE_ICC_ICE_SPHERE3 = 39306, + CREATURE_ICC_ICE_SPHERE4 = 39307, +//COMMON NPCS + SHAMAN_EARTH_ELEMENTAL = 15352, + SHAMAN_FIRE_ELEMENTAL = 15438, + //NPC_WORLD_TRIGGER = 22515, +//COMMON ITEM DISPLAY IDS + CHEST_HALISCAN = 50566, //Haliscan Jacket + LEGS_HALISCAN = 50567, //Haliscan Pants +//COMMON GAMEEVENTS + GAME_EVENT_WINTER_VEIL = 2, +//COMMON FACTIONS + FACTION_TEMPLATE_HATES_EVERYTHING_1 = 2150, //faction 966 - Monster spar buddy +//COMMON AI MISC VALUES + BOTAI_MISC_COMBO_POINTS = 1, + BOTAI_MISC_DAGGER_MAINHAND, + BOTAI_MISC_DAGGER_OFFHAND, + BOTAI_MISC_ENCHANT_IS_AUTO_MH, + BOTAI_MISC_ENCHANT_IS_AUTO_OH, + BOTAI_MISC_ENCHANT_CAN_EXPIRE_MH, + BOTAI_MISC_ENCHANT_CAN_EXPIRE_OH, + BOTAI_MISC_ENCHANT_CURRENT_MH, + BOTAI_MISC_ENCHANT_CURRENT_OH, + BOTAI_MISC_ENCHANT_AVAILABLE_1, + BOTAI_MISC_ENCHANT_AVAILABLE_2, + BOTAI_MISC_ENCHANT_AVAILABLE_3, + BOTAI_MISC_ENCHANT_AVAILABLE_4, + BOTAI_MISC_ENCHANT_AVAILABLE_5, + BOTAI_MISC_ENCHANT_AVAILABLE_6, + BOTAI_MISC_PET_TYPE, + BOTAI_MISC_PET_AVAILABLE_1, + BOTAI_MISC_PET_AVAILABLE_2, + BOTAI_MISC_PET_AVAILABLE_3, + BOTAI_MISC_PET_AVAILABLE_4, + BOTAI_MISC_PET_AVAILABLE_5, + BOTAI_MISC_PET_AVAILABLE_6, + BOTAI_MISC_PET_AVAILABLE_7, + BOTAI_MISC_PET_AVAILABLE_8, + BOTAI_MISC_PET_AVAILABLE_9, + BOTAI_MISC_PET_AVAILABLE_10, + BOTAI_MISC_PET_AVAILABLE_11, + BOTAI_MISC_WEAPON_SPEC, + BOTPETAI_MISC_DURATION, + BOTPETAI_MISC_MAXLEVEL, + BOTPETAI_MISC_FIXEDLEVEL, + BOTPETAI_MISC_CARRY, + BOTPETAI_MISC_CAPACITY, + BOTPETAI_MISC_MAX_ATTACKERS, + //SOUNDS + SOUND_FREEZE_IMPACT_WINDWALK = 29, + SOUND_AXE_2H_IMPACT_FLESH_CRIT = 158, + SOUND_ABSORB_GET_HIT = 3334, + SOUND_MISS_WHOOSH_2H = 7081, + +//UNUSED + + //MAX_LOOT_ITEMS = 18 // Client limitation 3.3.5 code confirmed +}; + +enum BotClasses : uint8 +{ + BOT_CLASS_NONE = CLASS_NONE, + BOT_CLASS_WARRIOR = CLASS_WARRIOR, + BOT_CLASS_PALADIN = CLASS_PALADIN, + BOT_CLASS_HUNTER = CLASS_HUNTER, + BOT_CLASS_ROGUE = CLASS_ROGUE, + BOT_CLASS_PRIEST = CLASS_PRIEST, + BOT_CLASS_DEATH_KNIGHT = CLASS_DEATH_KNIGHT, + BOT_CLASS_SHAMAN = CLASS_SHAMAN, + BOT_CLASS_MAGE = CLASS_MAGE, + BOT_CLASS_WARLOCK = CLASS_WARLOCK, + BOT_CLASS_DRUID = CLASS_DRUID, + + BOT_CLASS_BM, + BOT_CLASS_SPHYNX, + BOT_CLASS_ARCHMAGE, + BOT_CLASS_DREADLORD, + BOT_CLASS_SPELLBREAKER, + BOT_CLASS_DARK_RANGER, + BOT_CLASS_NECROMANCER, + BOT_CLASS_SEA_WITCH, + BOT_CLASS_CRYPT_LORD, + + BOT_CLASS_END, + + BOT_CLASS_EX_START = BOT_CLASS_BM +}; + +constexpr uint32 ALL_BOT_CLASSES_MASK = + ((1 << BOT_CLASS_WARRIOR)|(1 << BOT_CLASS_PALADIN)|(1 << BOT_CLASS_HUNTER)|(1 << BOT_CLASS_ROGUE)|(1 << BOT_CLASS_PRIEST)| + (1 << BOT_CLASS_DEATH_KNIGHT)|(1 << BOT_CLASS_SHAMAN)|(1 << BOT_CLASS_MAGE)|(1 << BOT_CLASS_WARLOCK)|(1 << BOT_CLASS_DRUID)| + (1 << BOT_CLASS_BM)|(1 << BOT_CLASS_SPHYNX)|(1 << BOT_CLASS_ARCHMAGE)|(1 << BOT_CLASS_DREADLORD)|(1 << BOT_CLASS_SPELLBREAKER)| + (1 << BOT_CLASS_DARK_RANGER)|(1 << BOT_CLASS_NECROMANCER)|(1 << BOT_CLASS_SEA_WITCH)|(1 << BOT_CLASS_CRYPT_LORD)); + +enum BotStances +{ + BOT_STANCE_NONE = 0, + WARRIOR_BATTLE_STANCE = BOT_CLASS_END, + WARRIOR_DEFENSIVE_STANCE, + WARRIOR_BERSERKER_STANCE, + DEATH_KNIGHT_BLOOD_PRESENCE, + DEATH_KNIGHT_FROST_PRESENCE, + DEATH_KNIGHT_UNHOLY_PRESENCE, + DRUID_BEAR_FORM, + DRUID_CAT_FORM, + DRUID_MOONKIN_FORM, + DRUID_TREE_FORM, + DRUID_TRAVEL_FORM, + DRUID_AQUATIC_FORM, + //DRUID_FLIGHT_FORM //NYI +}; + +enum BotRoles : uint32 +{ + BOT_ROLE_NONE = 0x00000, + BOT_ROLE_TANK = 0x00001, + BOT_ROLE_TANK_OFF = 0x00002, + BOT_ROLE_DPS = 0x00004, + BOT_ROLE_HEAL = 0x00008, + BOT_ROLE_RANGED = 0x00010, + + BOT_ROLE_PARTY = 0x00020, //hidden + + BOT_ROLE_GATHERING_MINING = 0x00040, + BOT_ROLE_GATHERING_HERBALISM = 0x00080, + BOT_ROLE_GATHERING_SKINNING = 0x00100, + BOT_ROLE_GATHERING_ENGINEERING = 0x00200, + + BOT_ROLE_AUTOLOOT = 0x00400, //not in mask + BOT_ROLE_AUTOLOOT_POOR = 0x00800, + BOT_ROLE_AUTOLOOT_COMMON = 0x01000, + BOT_ROLE_AUTOLOOT_UNCOMMON = 0x02000, + BOT_ROLE_AUTOLOOT_RARE = 0x04000, + BOT_ROLE_AUTOLOOT_EPIC = 0x08000, + BOT_ROLE_AUTOLOOT_LEGENDARY = 0x10000, + + BOT_MAX_ROLE = 0x20000, + + BOT_ROLE_MASK_MAIN = (BOT_ROLE_TANK | BOT_ROLE_TANK_OFF | BOT_ROLE_DPS | BOT_ROLE_HEAL | BOT_ROLE_RANGED), + //BOT_ROLE_MASK_MAIN_EX = (BOT_ROLE_TANK | BOT_ROLE_DPS | BOT_ROLE_HEAL | BOT_ROLE_RANGED | BOT_ROLE_PARTY), + BOT_ROLE_MASK_GATHERING = (BOT_ROLE_GATHERING_MINING | BOT_ROLE_GATHERING_HERBALISM | BOT_ROLE_GATHERING_SKINNING | BOT_ROLE_GATHERING_ENGINEERING), + BOT_ROLE_MASK_LOOTING = (BOT_ROLE_AUTOLOOT_POOR | BOT_ROLE_AUTOLOOT_COMMON | BOT_ROLE_AUTOLOOT_UNCOMMON | BOT_ROLE_AUTOLOOT_RARE | BOT_ROLE_AUTOLOOT_EPIC | BOT_ROLE_AUTOLOOT_LEGENDARY), + + //BOT_ROLE_TANK_MELEE = (BOT_ROLE_TANK | BOT_ROLE_DPS), + //BOT_ROLE_TANK_RANGED = (BOT_ROLE_TANK | BOT_ROLE_DPS | BOT_ROLE_RANGED), + //BOT_ROLE_TANK_RANGED_NODPS = (BOT_ROLE_TANK | BOT_ROLE_RANGED), +}; + +enum BotTalentSpecs +{ + BOT_SPEC_WARRIOR_ARMS = 1, + BOT_SPEC_WARRIOR_FURY = 2, + BOT_SPEC_WARRIOR_PROTECTION = 3, + BOT_SPEC_PALADIN_HOLY = 4, + BOT_SPEC_PALADIN_PROTECTION = 5, + BOT_SPEC_PALADIN_RETRIBUTION = 6, + BOT_SPEC_HUNTER_BEASTMASTERY = 7, + BOT_SPEC_HUNTER_MARKSMANSHIP = 8, + BOT_SPEC_HUNTER_SURVIVAL = 9, + BOT_SPEC_ROGUE_ASSASINATION = 10, + BOT_SPEC_ROGUE_COMBAT = 11, + BOT_SPEC_ROGUE_SUBTLETY = 12, + BOT_SPEC_PRIEST_DISCIPLINE = 13, + BOT_SPEC_PRIEST_HOLY = 14, + BOT_SPEC_PRIEST_SHADOW = 15, + BOT_SPEC_DK_BLOOD = 16, + BOT_SPEC_DK_FROST = 17, + BOT_SPEC_DK_UNHOLY = 18, + BOT_SPEC_SHAMAN_ELEMENTAL = 19, + BOT_SPEC_SHAMAN_ENHANCEMENT = 20, + BOT_SPEC_SHAMAN_RESTORATION = 21, + BOT_SPEC_MAGE_ARCANE = 22, + BOT_SPEC_MAGE_FIRE = 23, + BOT_SPEC_MAGE_FROST = 24, + BOT_SPEC_WARLOCK_AFFLICTION = 25, + BOT_SPEC_WARLOCK_DEMONOLOGY = 26, + BOT_SPEC_WARLOCK_DESTRUCTION = 27, + BOT_SPEC_DRUID_BALANCE = 28, + BOT_SPEC_DRUID_FERAL = 29, + BOT_SPEC_DRUID_RESTORATION = 30, + BOT_SPEC_DEFAULT = 31, + + BOT_SPEC_BEGIN = BOT_SPEC_WARRIOR_ARMS, + BOT_SPEC_END = BOT_SPEC_DEFAULT + +}; + +enum BotPetTypes +{ + //Warlock + BOT_PET_IMP = 70501, + BOT_PET_VOIDWALKER = 70502, + BOT_PET_SUCCUBUS = 70503, + BOT_PET_FELHUNTER = 70504, + BOT_PET_FELGUARD = 70505, + + BOT_PET_WARLOCK_START = BOT_PET_IMP, + BOT_PET_WARLOCK_END = BOT_PET_FELGUARD, + + //Hunter + //cunning + BOT_PET_SPIDER = 70506, + BOT_PET_SERPENT = 70507, + BOT_PET_BIRDOFPREY = 70508, + BOT_PET_BAT = 70509, + BOT_PET_WINDSERPENT = 70510, + BOT_PET_RAVAGER = 70511, + BOT_PET_DRAGONHAWK = 70512, + BOT_PET_NETHERRAY = 70513, + BOT_PET_SPOREBAT = 70514, + //ferocity + BOT_PET_CARRIONBIRD = 70515, + BOT_PET_RAPTOR = 70516, + BOT_PET_WOLF = 70517, + BOT_PET_TALLSTRIDER = 70518, + BOT_PET_CAT = 70519, + BOT_PET_HYENA = 70520, + BOT_PET_WASP = 70521, + BOT_PET_TEROMOTH = 70522, + //tenacity + BOT_PET_SCORPID = 70523, + BOT_PET_TURTLE = 70524, + BOT_PET_GORILLA = 70525, + BOT_PET_BEAR = 70526, + BOT_PET_BOAR = 70527, + BOT_PET_CRAB = 70528, + BOT_PET_CROCOLISK = 70529, + BOT_PET_WARPSTALKER = 70530, + //cunning (exotic) + BOT_PET_SILITHID = 70531, + BOT_PET_CHIMAERA = 70532, + //ferocity (exotic) + BOT_PET_SPIRITBEAST = 70533, + BOT_PET_COREHOUND = 70534, + BOT_PET_DEVILSAUR = 70535, + //tenacity (exotic) + BOT_PET_RHINO = 70536, + BOT_PET_WORM = 70537, + + BOT_PET_HUNTER_START = BOT_PET_SPIDER, + BOT_PET_HUNTER_END_GENERAL = BOT_PET_WARPSTALKER, + BOT_PET_HUNTER_END_EXOTIC = BOT_PET_WORM, + + BOT_PET_CUNNING_START = BOT_PET_SPIDER, + BOT_PET_CUNNING_END = BOT_PET_SPOREBAT, + BOT_PET_FEROCITY_START = BOT_PET_CARRIONBIRD, + BOT_PET_FEROCITY_END = BOT_PET_TEROMOTH, + BOT_PET_TENACITY_START = BOT_PET_SCORPID, + BOT_PET_TENACITY_END = BOT_PET_WARPSTALKER, + + BOT_PET_EXOTIC_START = BOT_PET_SILITHID, + BOT_PET_EXOTIC_END = BOT_PET_WORM, + + //DK + BOT_PET_GHOUL = 70538, + BOT_PET_GARGOYLE = 70539,//NYI + BOT_PET_DANCING_RUNE_WEAPON = 70540,//NYI + BOT_PET_AOD_GHOUL = 70541,//NYI + + //Priest + BOT_PET_SHADOWFIEND = 70542, + + //Shaman + BOT_PET_SPIRIT_WOLF = 70543, + + //Mage + BOT_PET_WATER_ELEMENTAL = 70544, + + //Druid + BOT_PET_FORCE_OF_NATURE = 70545, + + //Archmage + BOT_PET_AWATER_ELEMENTAL = 70556, + + //Dreadlord + BOT_PET_INFERNAL = 70562, + + //Dark Ranger + BOT_PET_DARK_MINION = 70573, + BOT_PET_DARK_MINION_ELITE = 70574, + + //Necromancer + BOT_PET_NECROSKELETON = 70580, + + //Sea Witch + BOT_PET_TORNADO = 70586, + + //Crypt Lord + BOT_PET_CARRION_BEETLE1 = 70592, + BOT_PET_CARRION_BEETLE2 = 70593, + BOT_PET_CARRION_BEETLE3 = 70594, + BOT_PET_LOCUST_SWARM = 70595, + + BOT_PET_INVALID = 99999 +}; + +enum BotPetOriginalEntries +{ + ORIGINAL_ENTRY_IMP = 416, + ORIGINAL_ENTRY_VOIDWALKER = 1860, + ORIGINAL_ENTRY_SUCCUBUS = 1863, + ORIGINAL_ENTRY_FELHUNTER = 417, + ORIGINAL_ENTRY_FELGUARD = 17252, + //ORIGINAL_ENTRY_GHOUL = 26125, + //ORIGINAL_ENTRY_SHADOWFIEND = 19668, + //ORIGINAL_ENTRY_SPIRIT_WOLF = 29264, + ORIGINAL_ENTRY_WATER_ELEMENTAL = 510, + //ORIGINAL_ENTRY_FORCE_OF_NATURE = 1964, + ORIGINAL_ENTRY_HUNTER_PET = 1 // from Pet.cpp InitStatsForLevel() +}; + +enum BotEquipSlot : uint8 +{ + BOT_SLOT_MAINHAND = 0, + BOT_SLOT_OFFHAND = 1, + BOT_SLOT_RANGED = 2, + BOT_SLOT_HEAD = 3, + BOT_SLOT_SHOULDERS = 4, + BOT_SLOT_CHEST = 5, + BOT_SLOT_WAIST = 6, + BOT_SLOT_LEGS = 7, + BOT_SLOT_FEET = 8, + BOT_SLOT_WRIST = 9, + BOT_SLOT_HANDS = 10, + BOT_SLOT_BACK = 11, + BOT_SLOT_BODY = 12, + BOT_SLOT_FINGER1 = 13, + BOT_SLOT_FINGER2 = 14, + BOT_SLOT_TRINKET1 = 15, + BOT_SLOT_TRINKET2 = 16, + BOT_SLOT_NECK = 17, + BOT_INVENTORY_SIZE +}; + +constexpr uint8 BOT_TRANSMOG_INVENTORY_SIZE = 13; // BOT_SLOT_BODY + 1 + +enum BotStatMods : uint8 +{ + //ItemProtoType.h + BOT_STAT_MOD_MANA = 0, + BOT_STAT_MOD_HEALTH = 1, + BOT_STAT_MOD_AGILITY = 3, + BOT_STAT_MOD_STRENGTH = 4, + BOT_STAT_MOD_INTELLECT = 5, + BOT_STAT_MOD_SPIRIT = 6, + BOT_STAT_MOD_STAMINA = 7, + BOT_STAT_MOD_DEFENSE_SKILL_RATING = 12, + BOT_STAT_MOD_DODGE_RATING = 13, + BOT_STAT_MOD_PARRY_RATING = 14, + BOT_STAT_MOD_BLOCK_RATING = 15, + BOT_STAT_MOD_HIT_MELEE_RATING = 16, + BOT_STAT_MOD_HIT_RANGED_RATING = 17, + BOT_STAT_MOD_HIT_SPELL_RATING = 18, + BOT_STAT_MOD_CRIT_MELEE_RATING = 19, + BOT_STAT_MOD_CRIT_RANGED_RATING = 20, + BOT_STAT_MOD_CRIT_SPELL_RATING = 21, + BOT_STAT_MOD_HIT_TAKEN_MELEE_RATING = 22, + BOT_STAT_MOD_HIT_TAKEN_RANGED_RATING = 23, + BOT_STAT_MOD_HIT_TAKEN_SPELL_RATING = 24, + BOT_STAT_MOD_CRIT_TAKEN_MELEE_RATING = 25, + BOT_STAT_MOD_CRIT_TAKEN_RANGED_RATING = 26, + BOT_STAT_MOD_CRIT_TAKEN_SPELL_RATING = 27, + BOT_STAT_MOD_HASTE_MELEE_RATING = 28, + BOT_STAT_MOD_HASTE_RANGED_RATING = 29, + BOT_STAT_MOD_HASTE_SPELL_RATING = 30, + BOT_STAT_MOD_HIT_RATING = 31, + BOT_STAT_MOD_CRIT_RATING = 32, + BOT_STAT_MOD_HIT_TAKEN_RATING = 33, + BOT_STAT_MOD_CRIT_TAKEN_RATING = 34, + BOT_STAT_MOD_RESILIENCE_RATING = 35, + BOT_STAT_MOD_HASTE_RATING = 36, + BOT_STAT_MOD_EXPERTISE_RATING = 37, + BOT_STAT_MOD_ATTACK_POWER = 38, + BOT_STAT_MOD_RANGED_ATTACK_POWER = 39, + BOT_STAT_MOD_FERAL_ATTACK_POWER = 40, + BOT_STAT_MOD_SPELL_HEALING_DONE = 41, // deprecated + BOT_STAT_MOD_SPELL_DAMAGE_DONE = 42, // deprecated + BOT_STAT_MOD_MANA_REGENERATION = 43, + BOT_STAT_MOD_ARMOR_PENETRATION_RATING = 44, + BOT_STAT_MOD_SPELL_POWER = 45, + BOT_STAT_MOD_HEALTH_REGEN = 46, + BOT_STAT_MOD_SPELL_PENETRATION = 47, + BOT_STAT_MOD_BLOCK_VALUE = 48, + //END ItemProtoType.h + + BOT_STAT_MOD_DAMAGE_MIN = BOT_STAT_MOD_BLOCK_VALUE + 1, + BOT_STAT_MOD_DAMAGE_MAX, + BOT_STAT_MOD_ARMOR, + BOT_STAT_MOD_RESIST_HOLY, + BOT_STAT_MOD_RESIST_FIRE, + BOT_STAT_MOD_RESIST_NATURE, + BOT_STAT_MOD_RESIST_FROST, + BOT_STAT_MOD_RESIST_SHADOW, + BOT_STAT_MOD_RESIST_ARCANE, + BOT_STAT_MOD_EX, + MAX_BOT_ITEM_MOD, + + BOT_STAT_MOD_RESISTANCE_START = BOT_STAT_MOD_ARMOR +}; + +enum BotAIResetType +{ + BOTAI_RESET_INIT = 0x01, + BOTAI_RESET_DISMISS = 0x02, + BOTAI_RESET_UNBIND = 0x04, + BOTAI_RESET_LOGOUT = 0x08, + BOTAI_RESET_FORCERECALL = 0x10, + + BOTAI_RESET_MASK_ABANDON_MASTER = (BOTAI_RESET_INIT | BOTAI_RESET_DISMISS) +}; + +enum BotMovementType +{ + BOT_MOVE_POINT = 1, + //BOT_MOVE_FOLLOW + BOT_MOVE_CHASE +}; + +enum BotCommandStates : uint32 +{ + BOT_COMMAND_STAY = 0x00000001, + BOT_COMMAND_FOLLOW = 0x00000002, + BOT_COMMAND_ATTACK = 0x00000004, + BOT_COMMAND_COMBATRESET = 0x00000008, + BOT_COMMAND_FULLSTOP = 0x00000010, + BOT_COMMAND_ISSUED_ORDER = 0x00000020, + BOT_COMMAND_WALK = 0x00000040, + BOT_COMMAND_NOGOSSIP = 0x00000080, + BOT_COMMAND_UNBIND = 0x00000100, + BOT_COMMAND_NO_CAST = 0x00000200, + BOT_COMMAND_NO_CAST_LONG = 0x00000400, + BOT_COMMAND_INACTION = 0x00000800, + + BOT_COMMAND_MASK_UNCHASE = BOT_COMMAND_STAY | BOT_COMMAND_FOLLOW | BOT_COMMAND_FULLSTOP | BOT_COMMAND_INACTION, + BOT_COMMAND_MASK_UNMOVING = BOT_COMMAND_STAY | BOT_COMMAND_FULLSTOP | BOT_COMMAND_ISSUED_ORDER, + BOT_COMMAND_MASK_NOCAST_ANY = BOT_COMMAND_NO_CAST | BOT_COMMAND_NO_CAST_LONG +}; + +enum BotAwaitStates +{ + BOT_AWAIT_NONE = 0x00, + BOT_AWAIT_SEND = 0x01 +}; + +constexpr size_t MAX_SEND_POINTS = 5u; + +#define FROM_ARRAY(arr) arr, arr + sizeof(arr) / sizeof(arr[0]) + +//Only non-persistent types are allowed +enum BotOrderTypes +{ + BOT_ORDER_NONE = 0, + BOT_ORDER_SPELLCAST = 1, + BOT_ORDER_PULL = 2, + + BOT_ORDER_END +}; +constexpr bool DEBUG_BOT_ORDERS = false; +constexpr size_t MAX_BOT_ORDERS_QUEUE_SIZE = 3u; + +enum BotVehicleStrats +{ + BOT_VEH_STRAT_NONE, + BOT_VEH_STRAT_WYRMREST_SKYTALON, + BOT_VEH_STRAT_RUBY_DRAKE, + BOT_VEH_STRAT_EMERALD_DRAKE, + BOT_VEH_STRAT_AMBER_DRAKE, + BOT_VEH_STRAT_TOC5_MOUNT, + BOT_VEH_STRAT_ULDUAR_DEMOLISHER, + BOT_VEH_STRAT_ULDUAR_SIEGEENGINE, + BOT_VEH_STRAT_ULDUAR_CHOPPER, + + BOT_VEH_STRAT_GENERIC +}; + +#endif diff --git a/src/server/game/AI/NpcBots/botdatamgr.cpp b/src/server/game/AI/NpcBots/botdatamgr.cpp new file mode 100644 index 000000000..340a49afd --- /dev/null +++ b/src/server/game/AI/NpcBots/botdatamgr.cpp @@ -0,0 +1,3110 @@ +#include "BattlegroundMgr.h" +#include "BattlegroundQueue.h" +#include "bot_ai.h" +#include "botdatamgr.h" +#include "botlog.h" +#include "botmgr.h" +#include "botspell.h" +#include "botwanderful.h" +#include "bpet_ai.h" +#include "Containers.h" +#include "Creature.h" +#include "DatabaseEnv.h" +#include "GameTime.h" +#include "GroupMgr.h" +#include "Item.h" +#include "Log.h" +#include "Map.h" +#include "MapManager.h" +#include "ObjectMgr.h" +#include "ScriptMgr.h" +#include "SpellInfo.h" +#include "SpellMgr.h" +#include "StringConvert.h" +#include "World.h" +#include "WorldDatabase.h" +/* +Npc Bot Data Manager by Trickerer (onlysuffering@gmail.com) +NpcBots DB Data management +%Complete: ??? +*/ + +#ifdef _MSC_VER +# pragma warning(push, 4) +#endif + +typedef std::unordered_map NpcBotDataMap; +typedef std::unordered_map NpcBotAppearanceDataMap; +typedef std::unordered_map NpcBotExtrasMap; +typedef std::unordered_map NpcBotTransmogDataMap; +NpcBotDataMap _botsData; +NpcBotAppearanceDataMap _botsAppearanceData; +NpcBotExtrasMap _botsExtras; +NpcBotTransmogDataMap _botsTransmogData; +NpcBotRegistry _existingBots; + +std::map _wpMinSpawnLevelPerMapId; +std::map _wpMaxSpawnLevelPerMapId; +std::map> _spareBotIdsPerClassMap; +CreatureTemplateContainer _botsWanderCreatureTemplates; +std::unordered_map _botsWanderCreatureEquipmentTemplates; +std::list> _botsWanderCreaturesToSpawn; +std::set _botsWanderCreaturesToDespawn; + +ItemPerBotClassMap _botsWanderCreaturesSortedGear; + +typedef std::unordered_map BotGearStorageMap; +BotGearStorageMap _botStoredGearMap; + +static bool allBotsLoaded = false; + +static uint32 next_wandering_bot_spawn_delay = 0; + +static EventProcessor botSpawnEvents; +static std::unordered_map botBGJoinEvents; + +bool BotBankItemCompare::operator()(Item const* item1, Item const* item2) const +{ + ItemTemplate const* proto1 = item1->GetTemplate(); + ItemTemplate const* proto2 = item2->GetTemplate(); + return proto1->Name1 < proto2->Name1; +} + +class BotBattlegroundEnterEvent : public BasicEvent +{ + const ObjectGuid _playerGUID; + const ObjectGuid _botGUID; + const BattlegroundQueueTypeId _bgQueueTypeId; + const BattlegroundTypeId _bgTypeId; + const uint64 _removeTime; + +public: + BotBattlegroundEnterEvent(ObjectGuid playerGUID, ObjectGuid botGUID, BattlegroundQueueTypeId bgQueueTypeId, BattlegroundTypeId bgTypeId, uint64 removeTime) + : _playerGUID(playerGUID), _botGUID(botGUID), _bgQueueTypeId(bgQueueTypeId), _bgTypeId(bgTypeId), _removeTime(removeTime) {} + + void AbortMe() + { + TC_LOG_ERROR("npcbots", "BotBattlegroundEnterEvent: Aborting bot {} bg {}!", _botGUID.GetEntry(), uint32(_bgQueueTypeId)); + sBattlegroundMgr->GetBattlegroundQueue(_bgQueueTypeId).RemovePlayer(_botGUID, true); + BotDataMgr::DespawnWandererBot(_botGUID.GetEntry()); + } + + void AbortAll() + { + TC_LOG_ERROR("npcbots", "BotBattlegroundEnterEvent: Aborting ALL bots by {} bg {}!", _playerGUID.GetCounter(), uint32(_bgQueueTypeId)); + AbortMe(); + botBGJoinEvents.at(_playerGUID).KillAllEvents(false); + } + + bool Execute(uint64 e_time, uint32 /*p_time*/) override + { + if (e_time >= _removeTime) + { + AbortMe(); + return true; + } + else if (Creature const* bot = BotDataMgr::FindBot(_botGUID.GetEntry())) + { + // Battleground is created at this point, try to find it + BattlegroundQueue& queue = sBattlegroundMgr->GetBattlegroundQueue(_bgQueueTypeId); + BattlegroundQueue::QueuedPlayersMap::const_iterator qpm_citr = queue.m_QueuedPlayers.find(_botGUID); + GroupQueueInfo const* my_gqi = qpm_citr != queue.m_QueuedPlayers.cend() ? qpm_citr->second.GroupInfo : nullptr; + Battleground* bg = my_gqi ? sBattlegroundMgr->GetBattleground(my_gqi->IsInvitedToBGInstanceGUID, _bgTypeId) : nullptr; + + if (!bg || bg->GetPlayersCountByTeam(ALLIANCE) + bg->GetPlayersCountByTeam(HORDE) >= bg->GetMaxPlayersPerTeam() * 2) + { + AbortAll(); + return true; + } + + if (!queue.IsBotInvited(_botGUID, bg->GetInstanceID())) + { + AbortMe(); + return true; + } + + if (bg->GetPlayersCountByTeam(ALLIANCE) + bg->GetPlayersCountByTeam(HORDE) > 0) + { + Map* bgMap = ASSERT_NOTNULL(sMapMgr->FindMap(bg->GetMapId(), bg->GetInstanceID())); + + queue.RemovePlayer(bot->GetGUID(), false); + + //BG is set second time in Battleground::AddBot() but it's the same value so this is alright + bot->GetBotAI()->SetBG(bg); + + TeamId teamId = BotDataMgr::GetTeamIdForFaction(bot->GetFaction()); + BotMgr::TeleportBot(const_cast(bot), bgMap, bg->GetTeamStartPosition(teamId), true, false); + } + else if (std::any_of(queue.m_QueuedPlayers.cbegin(), queue.m_QueuedPlayers.cend(), [=](BattlegroundQueue::QueuedPlayersMap::value_type const& qpm_pair) { + return qpm_pair.first.IsPlayer() && qpm_pair.second.GroupInfo->IsInvitedToBGInstanceGUID == my_gqi->IsInvitedToBGInstanceGUID; + })) + botBGJoinEvents.at(_playerGUID).AddEventAtOffset(new BotBattlegroundEnterEvent(_playerGUID, _botGUID, _bgQueueTypeId, _bgTypeId, _removeTime), 2s); + else + AbortAll(); + } + + return true; + } + + void Abort(uint64 /*e_time*/) override { AbortMe(); } +}; + +static void SpawnWandererBot(uint32 bot_id, WanderNode const* spawnLoc, NpcBotRegistry* registry) +{ + CreatureTemplate const& bot_template = _botsWanderCreatureTemplates.at(bot_id); + NpcBotData const* bot_data = BotDataMgr::SelectNpcBotData(bot_id); + NpcBotExtras const* bot_extras = BotDataMgr::SelectNpcBotExtras(bot_id); + Position spawnPos = spawnLoc->GetPosition(); + + ASSERT(bot_data); + ASSERT(bot_extras); + + Map* map = sMapMgr->CreateBaseMap(spawnLoc->GetMapId()); + map->LoadGrid(spawnLoc->m_positionX, spawnLoc->m_positionY); + + TC_LOG_DEBUG("npcbots", "Spawning wandering bot: {} ({}) class {} race {} fac {}, location: mapId {} {} ({})", + bot_template.Name, bot_id, uint32(bot_extras->bclass), uint32(bot_extras->race), bot_data->faction, + spawnLoc->GetMapId(), spawnLoc->ToString(), spawnLoc->GetName()); + + Creature* bot = new Creature(); + if (!bot->LoadBotCreatureFromDB(0, map, true, true, bot_id, &spawnPos)) + { + delete bot; + TC_LOG_FATAL("server.loading", "Cannot load npcbot from DB!"); + ASSERT(false); + } + + if (registry) + registry->insert(bot); +} + +void BotDataMgr::DespawnWandererBot(uint32 entry) +{ + Creature const* bot = FindBot(entry); + if (bot && bot->IsWandererBot()) + { + if (bot->GetBotAI()) + bot->GetBotAI()->canUpdate = false; + _botsWanderCreaturesToDespawn.insert(entry); + } + else + TC_LOG_ERROR("npcbots", "DespawnWandererBot(): trying to despawn non-existing wanderer bot {} '{}'!", entry, bot ? bot->GetName() : "unknown"); +} + +struct WanderingBotsGenerator +{ +private: + using NodeVec = std::vector; + + const std::map wbot_faction_for_ex_class = { + {BOT_CLASS_BM, 14u/*2u*/}, + {BOT_CLASS_SPHYNX, 14u}, + {BOT_CLASS_ARCHMAGE, 14u/*1u*/}, + {BOT_CLASS_DREADLORD, 14u}, + {BOT_CLASS_SPELLBREAKER, 14u/*1610u*/}, + {BOT_CLASS_DARK_RANGER, 14u}, + {BOT_CLASS_NECROMANCER, 14u}, + {BOT_CLASS_SEA_WITCH, 14u}, + {BOT_CLASS_CRYPT_LORD, 14u} + }; + + uint32 next_bot_id; + uint32 enabledBotsCount; + + WanderingBotsGenerator() + { + next_bot_id = BOT_ENTRY_CREATE_BEGIN - 1; + QueryResult result = CharacterDatabase.PQuery("SELECT value FROM worldstates WHERE entry = {}", uint32(BOT_GIVER_ENTRY)); + if (!result) + { + TC_LOG_WARN("server.loading", "Next bot id for autogeneration is not found! Resetting! (client cache may interfere with names)"); + for (uint32 bot_cid : BotDataMgr::GetExistingNPCBotIds()) + if (bot_cid > next_bot_id) + next_bot_id = bot_cid; + CharacterDatabase.DirectPExecute("INSERT INTO worldstates (entry, value, comment) VALUES ({}, {}, '{}')", + uint32(BOT_GIVER_ENTRY), next_bot_id, "NPCBOTS MOD - last autogenerated bot entry"); + } + else + next_bot_id = result->Fetch()[0].GetUInt32(); + + ASSERT(next_bot_id > BOT_ENTRY_BEGIN); + + for (uint8 c = BOT_CLASS_WARRIOR; c < BOT_CLASS_END; ++c) + if (BotMgr::IsWanderingClassEnabled(c) && _spareBotIdsPerClassMap.find(c) == _spareBotIdsPerClassMap.cend()) + _spareBotIdsPerClassMap.insert({ c, {} }); + + for (decltype(_botsExtras)::value_type const& vt : _botsExtras) + { + uint8 c = vt.second->bclass; + if (c != BOT_CLASS_NONE && BotMgr::IsWanderingClassEnabled(c)) + { + ++enabledBotsCount; + if (_botsData.find(vt.first) == _botsData.end()) + { + ASSERT(_spareBotIdsPerClassMap.find(c) != _spareBotIdsPerClassMap.cend()); + _spareBotIdsPerClassMap.at(c).insert(vt.first); + } + } + } + + for (uint8 c = BOT_CLASS_WARRIOR; c < BOT_CLASS_END; ++c) + if (_spareBotIdsPerClassMap.find(c) != _spareBotIdsPerClassMap.cend() && _spareBotIdsPerClassMap.at(c).empty()) + _spareBotIdsPerClassMap.erase(c); + } + + uint32 GetDefaultFactionForRaceClass(uint8 bot_class, uint8 bot_race) const + { + ChrRacesEntry const* rentry = sChrRacesStore.LookupEntry(bot_race); + return (bot_class >= BOT_CLASS_EX_START) ? wbot_faction_for_ex_class.find(bot_class)->second : rentry ? rentry->FactionID : 14u; + } + + bool GenerateWanderingBotToSpawn(std::pair const& spareBotPair, uint8 desired_bracket, + NodeVec const& spawns_a, NodeVec const& spawns_h, NodeVec const& spawns_n, + bool immediate, PvPDifficultyEntry const* bracketEntry, NpcBotRegistry* registry) + { + CreatureTemplateContainer const& all_templates = sObjectMgr->GetCreatureTemplates(); + + while (all_templates.find(++next_bot_id) != all_templates.cend()) {} + + const uint8 bot_class = spareBotPair.first; + const uint32 orig_entry = spareBotPair.second; + CreatureTemplate const* orig_template = ASSERT_NOTNULL(sObjectMgr->GetCreatureTemplate(orig_entry)); + NpcBotExtras const* orig_extras = ASSERT_NOTNULL(BotDataMgr::SelectNpcBotExtras(orig_entry)); + uint32 bot_faction = GetDefaultFactionForRaceClass(bot_class, orig_extras->race); + + NodeVec const* bot_spawn_nodes; + TeamId bot_team = BotDataMgr::GetTeamIdForFaction(bot_faction); + switch (bot_team) + { + case TEAM_ALLIANCE: + bot_spawn_nodes = &spawns_a; + break; + case TEAM_HORDE: + bot_spawn_nodes = &spawns_h; + break; + default: + bot_spawn_nodes = &spawns_n; + break; + } + NodeVec level_nodes; + level_nodes.reserve(bot_spawn_nodes->size()); + desired_bracket = std::max(desired_bracket, BotDataMgr::GetMinLevelForBotClass(bot_class) / 10); + for (WanderNode const* node : *bot_spawn_nodes) + { + if (desired_bracket * 10 + 9 >= node->GetLevels().first && node->GetLevels().second >= desired_bracket * 10) + level_nodes.push_back(node); + } + + ASSERT(!level_nodes.empty()); + WanderNode const* spawnLoc = Trinity::Containers::SelectRandomContainerElement(level_nodes); + + CreatureTemplate& bot_template = _botsWanderCreatureTemplates[next_bot_id]; + //copy all fields + bot_template = *orig_template; + bot_template.Entry = next_bot_id; + bot_template.Title = ""; + bot_template.speed_run = BotMgr::GetBotWandererSpeedMod(); + bot_template.KillCredit[0] = orig_entry; + //bot_template.type_flags |= CREATURE_TYPE_FLAG_FORCE_GOSSIP; + + uint32 max_level = DEFAULT_MAX_LEVEL; + if (bracketEntry && BotMgr::IsBotLevelCappedByConfigBG()) + { + max_level = std::min(sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL), max_level); + max_level = std::min(GetMaxLevelForExpansion(sWorld->getIntConfig(CONFIG_EXPANSION)), max_level); + } + + if (bracketEntry) + { + //force level range for bgs + bot_template.minlevel = std::min(bracketEntry->MinLevel, max_level); + bot_template.maxlevel = std::min(bracketEntry->MaxLevel, max_level); + if (sWorld->getBoolConfig(CONFIG_BG_XP_FOR_KILL)) + bot_template.flags_extra &= ~(CREATURE_FLAG_EXTRA_NO_XP); + } + else + { + bot_template.minlevel = std::min(std::max(desired_bracket * 10, spawnLoc->GetLevels().first), max_level); + bot_template.maxlevel = std::min(std::min(desired_bracket * 10 + 9, spawnLoc->GetLevels().second), max_level); + bot_template.flags_extra &= ~(CREATURE_FLAG_EXTRA_NO_XP); + } + + bot_template.InitializeQueryData(); + + uint8 bot_spec = bot_ai::SelectSpecForClass(bot_class); + NpcBotData* bot_data = new NpcBotData(bot_ai::DefaultRolesForClass(bot_class, bot_spec), bot_faction, bot_spec); + _botsData[next_bot_id] = bot_data; + NpcBotExtras* bot_extras = new NpcBotExtras(); + bot_extras->bclass = bot_class; + bot_extras->race = orig_extras->race; + _botsExtras[next_bot_id] = bot_extras; + if (NpcBotAppearanceData const* orig_apdata = BotDataMgr::SelectNpcBotAppearance(orig_entry)) + { + NpcBotAppearanceData* bot_apdata = new NpcBotAppearanceData(); + bot_apdata->face = orig_apdata->face; + bot_apdata->features = orig_apdata->features; + bot_apdata->gender = orig_apdata->gender; + bot_apdata->hair = orig_apdata->hair; + bot_apdata->haircolor = orig_apdata->haircolor; + bot_apdata->skin = orig_apdata->skin; + _botsAppearanceData[next_bot_id] = bot_apdata; + } + int8 beqId = 1; + _botsWanderCreatureEquipmentTemplates[next_bot_id] = sObjectMgr->GetEquipmentInfo(orig_entry, beqId); + + //We do not create CreatureData for generated bots + + CellCoord c = Trinity::ComputeCellCoord(spawnLoc->m_positionX, spawnLoc->m_positionY); + GridCoord g = Trinity::ComputeGridCoord(spawnLoc->m_positionX, spawnLoc->m_positionY); + ASSERT(c.IsCoordValid(), "Invalid Cell coord!"); + ASSERT(g.IsCoordValid(), "Invalid Grid coord!"); + Map* map = sMapMgr->CreateBaseMap(spawnLoc->GetMapId()); + ASSERT(map->GetEntry()->IsContinent() || map->GetEntry()->IsBattlegroundOrArena(), "%s", map->GetDebugInfo().c_str()); + + if (immediate) + SpawnWandererBot(next_bot_id, spawnLoc, registry); + else + _botsWanderCreaturesToSpawn.push_back({ next_bot_id, spawnLoc }); + + _spareBotIdsPerClassMap.at(bot_class).erase(orig_entry); + if (_spareBotIdsPerClassMap.at(bot_class).empty()) + _spareBotIdsPerClassMap.erase(bot_class); + + return true; + } + +public: + uint32 GetEnabledBotsCount() const { return enabledBotsCount; } + + uint32 GetSpareBotsCount(TeamId teamId = TEAM_NEUTRAL) const + { + uint32 count = 0; + for (auto const& kv : _spareBotIdsPerClassMap) + { + if (teamId == TEAM_NEUTRAL) + count += kv.second.size(); + else + { + if (kv.first >= BOT_CLASS_EX_START) + { + auto cit = wbot_faction_for_ex_class.find(kv.first); + if (cit != wbot_faction_for_ex_class.cend() && cit->second == FACTION_MONSTER) + continue; + } + + for (uint32 entry : kv.second) + { + NpcBotExtras const* extras = ASSERT_NOTNULL(BotDataMgr::SelectNpcBotExtras(entry)); + uint32 bot_faction = GetDefaultFactionForRaceClass(kv.first, extras->race); + TeamId bot_team = BotDataMgr::GetTeamIdForFaction(bot_faction); + if (teamId == bot_team) + ++count; + } + } + } + return count; + } + + bool GenerateWanderingBotsToSpawn(uint32 count, int32 map_id, int32 team, bool immediate, PvPDifficultyEntry const* bracketEntry, NpcBotRegistry* registry, uint32& spawned) + { + using NodeVec = std::vector; + + if (_spareBotIdsPerClassMap.empty()) + return false; + + NodeVec spawns_a, spawns_h, spawns_n; + for (NodeVec* vec : { &spawns_a, &spawns_h, &spawns_n }) + vec->reserve(WanderNode::GetWPMapsCount() * 20u); + + WanderNode::DoForAllWPs([map_id = map_id, &spawns_a, &spawns_h, &spawns_n](WanderNode const* wp) { + MapEntry const* mapEntry = sMapStore.LookupEntry(wp->GetMapId()); + if ((map_id == -1) ? mapEntry->IsWorldMap() : (int32(mapEntry->ID) == map_id)) + { + if (wp->HasFlag(BotWPFlags::BOTWP_FLAG_SPAWN)) + { + if (wp->HasFlag(BotWPFlags::BOTWP_FLAG_ALLIANCE_ONLY)) + spawns_a.push_back(wp); + else if (wp->HasFlag(BotWPFlags::BOTWP_FLAG_HORDE_ONLY)) + spawns_h.push_back(wp); + else + { + spawns_a.push_back(wp); + spawns_h.push_back(wp); + spawns_n.push_back(wp); + } + } + } + }); + + bool found_maxlevel_node_a = false; + bool found_maxlevel_node_h = false; + bool found_maxlevel_node_n = false; + const uint8 maxof_minclasslvl_nor = BotDataMgr::GetMinLevelForBotClass(BOT_CLASS_DEATH_KNIGHT); // 55 + const uint8 maxof_minclasslvl_ex = BotDataMgr::GetMinLevelForBotClass(BOT_CLASS_DREADLORD); // 60 + for (WanderNode const* wp : spawns_a) + { + if (wp->GetLevels().second >= maxof_minclasslvl_nor) + { + found_maxlevel_node_a = true; + break; + } + } + for (WanderNode const* wp : spawns_h) + { + if (wp->GetLevels().second >= maxof_minclasslvl_nor) + { + found_maxlevel_node_h = true; + break; + } + } + for (WanderNode const* wp : spawns_n) + { + if (wp->GetLevels().second >= maxof_minclasslvl_ex) + { + found_maxlevel_node_n = true; + break; + } + } + + std::vector> teamSpareBotIdsPerClass; + PctBrackets bracketPcts{}; + PctBrackets bots_per_bracket{}; + + teamSpareBotIdsPerClass.reserve(count); + + if (team == -1) + { + if (!found_maxlevel_node_a || !found_maxlevel_node_h || !found_maxlevel_node_n) + return false; + + //make a full copy + for (auto const& kv : _spareBotIdsPerClassMap) + for (uint32 spareBotId : kv.second) + teamSpareBotIdsPerClass.push_back({kv.first, spareBotId}); + bracketPcts = BotMgr::GetBotWandererLevelBrackets(); + } + else + { + bracketPcts[bracketEntry->MinLevel / 10] = 100u; + switch (team) + { + case ALLIANCE: + if (!found_maxlevel_node_a) + return false; + break; + case HORDE: + if (!found_maxlevel_node_h) + return false; + break; + case TEAM_OTHER: + default: + if (!found_maxlevel_node_n) + return false; + break; + } + + for (auto const& kv : _spareBotIdsPerClassMap) + { + for (uint32 spareBotId : kv.second) + { + NpcBotExtras const* orig_extras = ASSERT_NOTNULL(BotDataMgr::SelectNpcBotExtras(spareBotId)); + uint32 bot_faction = GetDefaultFactionForRaceClass(kv.first, orig_extras->race); + uint32 botTeam = BotDataMgr::GetTeamForFaction(bot_faction); + + if (int32(botTeam) != team) + continue; + + if (bracketEntry && BotDataMgr::GetMinLevelForBotClass(kv.first) > bracketEntry->MaxLevel) + continue; + + teamSpareBotIdsPerClass.push_back({kv.first, spareBotId}); + } + } + } + + if (teamSpareBotIdsPerClass.empty()) + return false; + + uint32 total_bots_in_brackets = 0; + for (size_t k = 0; k < BracketsCount; ++k) + { + if (!bracketPcts[k]) + continue; + bots_per_bracket[k] = CalculatePct(count, bracketPcts[k]); + total_bots_in_brackets += bots_per_bracket[k]; + } + for (int32 j = BracketsCount - 1; j >= 0; --j) + { + if (bots_per_bracket[j]) + { + bots_per_bracket[j] += count - total_bots_in_brackets; + break; + } + } + + std::vector brackets_shuffled; + brackets_shuffled.reserve(count); + for (uint8 bracket = 0; bracket < BracketsCount; ++bracket) + { + while (bots_per_bracket[bracket]) + { + brackets_shuffled.push_back(bracket); + --bots_per_bracket[bracket]; + } + } + + Trinity::Containers::RandomShuffle(teamSpareBotIdsPerClass); + Trinity::Containers::RandomShuffle(brackets_shuffled); + + for (size_t i = 0; i < brackets_shuffled.size() && !teamSpareBotIdsPerClass.empty();) // i is a counter, NOT used as index or value + { + uint8 bracket = brackets_shuffled[i]; + + int8 tries = 100; + do { + --tries; + if (GenerateWanderingBotToSpawn(teamSpareBotIdsPerClass.back(), bracket, spawns_a, spawns_h, spawns_n, immediate, bracketEntry, registry)) + { + ++i; + ++spawned; + teamSpareBotIdsPerClass.pop_back(); + break; + } + } while (tries >= 0); + + if (tries < 0) + return false; + } + + CharacterDatabase.PExecute("UPDATE worldstates SET value = {} WHERE entry = {}", next_bot_id, uint32(BOT_GIVER_ENTRY)); + + return true; + } + + static WanderingBotsGenerator* instance() + { + static WanderingBotsGenerator _instance; + return &_instance; + } +}; +#define sBotGen WanderingBotsGenerator::instance() + +void BotDataMgr::Update(uint32 diff) +{ + botSpawnEvents.Update(diff); + for (auto& kv : botBGJoinEvents) + kv.second.Update(diff); + + //lock is not needed here + for (Creature const* bot : _existingBots) + { + if (bot->IsFreeBot() && !bot->IsWandererBot() && !bot->IsInWorld() && bot->FindMap() && !!SelectNpcBotData(bot->GetEntry())) + { + bot->GetBotAI()->CommonTimers(diff); + bot->GetBotAI()->UpdateAI(diff); + } + } + + if (!_botsWanderCreaturesToDespawn.empty()) + { + TC_LOG_DEBUG("npcbots", "Bots to despawn: {}", uint32(_botsWanderCreaturesToDespawn.size())); + + while (!_botsWanderCreaturesToDespawn.empty()) + { + uint32 bot_despawn_id = *_botsWanderCreaturesToDespawn.begin(); + + Creature* bot = const_cast(FindBot(bot_despawn_id)); + ASSERT(bot); + + if (!bot->IsInWorld()) + break; + + _botsWanderCreaturesToDespawn.erase(bot_despawn_id); + + uint32 origEntry = _botsWanderCreatureTemplates.at(bot_despawn_id).KillCredit[0]; + std::string botName = bot->GetName(); + + _spareBotIdsPerClassMap[bot->GetBotClass()].insert(origEntry); + + BotMgr::CleanupsBeforeBotDelete(bot); + bot->GetBotAI()->canUpdate = false; + bot->GetMap()->AddObjectToRemoveList(bot); + + auto bditr = _botsData.find(bot_despawn_id); + auto beitr = _botsExtras.find(bot_despawn_id); + auto baditr = _botsAppearanceData.find(bot_despawn_id); + auto bwcetitr = _botsWanderCreatureEquipmentTemplates.find(bot_despawn_id); + auto bwctitr = _botsWanderCreatureTemplates.find(bot_despawn_id); + + ASSERT(bditr != _botsData.end()); + ASSERT(beitr != _botsExtras.end()); + //ASSERT(baditr != _botsAppearanceData.end()); may not exist + ASSERT(bwcetitr != _botsWanderCreatureEquipmentTemplates.end()); + ASSERT(bwctitr != _botsWanderCreatureTemplates.end()); + + delete bditr->second; + _botsData.erase(bditr); + delete beitr->second; + _botsExtras.erase(beitr); + if (baditr != _botsAppearanceData.end()) + { + delete baditr->second; + _botsAppearanceData.erase(baditr); + } + _botsWanderCreatureEquipmentTemplates.erase(bwcetitr); + _botsWanderCreatureTemplates.erase(bwctitr); + + TC_LOG_DEBUG("npcbots", "Despawned wanderer bot {} '{}' (orig {})", bot_despawn_id, botName, origEntry); + } + } + + if (!_botsWanderCreaturesToSpawn.empty()) + { + static const uint32 WANDERING_BOT_SPAWN_DELAY = 500; + + next_wandering_bot_spawn_delay += diff; + + while (next_wandering_bot_spawn_delay >= WANDERING_BOT_SPAWN_DELAY && !_botsWanderCreaturesToSpawn.empty()) + { + next_wandering_bot_spawn_delay -= WANDERING_BOT_SPAWN_DELAY; + + auto const& p = _botsWanderCreaturesToSpawn.front(); + + uint32 bot_id = p.first; + WanderNode const* spawnLoc = p.second; + + _botsWanderCreaturesToSpawn.pop_front(); + + SpawnWandererBot(bot_id, spawnLoc, nullptr); + } + + return; + } +} + +std::shared_mutex* BotDataMgr::GetLock() +{ + static std::shared_mutex _lock; + return &_lock; +} + +bool BotDataMgr::AllBotsLoaded() +{ + return allBotsLoaded; +} + +void BotDataMgr::LoadNpcBots(bool spawn) +{ + if (allBotsLoaded) + return; + + TC_LOG_INFO("server.loading", "Starting NpcBot system..."); + + GenerateBotCustomSpells(); + + uint32 botoldMSTime = getMSTime(); + + Field* field; + uint8 index; + + // 1 2 3 4 5 6 + QueryResult result = WorldDatabase.Query("SELECT entry, gender, skin, face, hair, haircolor, features FROM creature_template_npcbot_appearance"); + if (result) + { + do + { + field = result->Fetch(); + index = 0; + uint32 entry = field[ index].GetUInt32(); + + if (!sObjectMgr->GetCreatureTemplate(entry)) + { + TC_LOG_ERROR("server.loading", "Bot entry {} has appearance data but doesn't exist in `creature_template` table! Skipped.", entry); + continue; + } + + NpcBotAppearanceData* appearanceData = new NpcBotAppearanceData(); + appearanceData->gender = field[++index].GetUInt8(); + appearanceData->skin = field[++index].GetUInt8(); + appearanceData->face = field[++index].GetUInt8(); + appearanceData->hair = field[++index].GetUInt8(); + appearanceData->haircolor = field[++index].GetUInt8(); + appearanceData->features = field[++index].GetUInt8(); + + _botsAppearanceData[entry] = appearanceData; + + } while (result->NextRow()); + + TC_LOG_INFO("server.loading", ">> Bot appearance data loaded"); + } + else + TC_LOG_INFO("server.loading", ">> Bots appearance data is not loaded. Table `creature_template_npcbot_appearance` is empty!"); + + // 1 2 + result = WorldDatabase.Query("SELECT entry, class, race FROM creature_template_npcbot_extras"); + if (result) + { + do + { + field = result->Fetch(); + index = 0; + uint32 entry = field[ index].GetUInt32(); + + if (!sObjectMgr->GetCreatureTemplate(entry)) + { + TC_LOG_ERROR("server.loading", "Bot entry {} has extras data but doesn't exist in `creature_template` table! Skipped.", entry); + continue; + } + + NpcBotExtras* extras = new NpcBotExtras(); + extras->bclass = field[++index].GetUInt8(); + extras->race = field[++index].GetUInt8(); + + _botsExtras[entry] = extras; + + } while (result->NextRow()); + + TC_LOG_INFO("server.loading", ">> Bot race data loaded"); + } + else + TC_LOG_INFO("server.loading", ">> Bots race data is not loaded. Table `creature_template_npcbot_extras` is empty!"); + + // 1 2 3 + result = CharacterDatabase.Query("SELECT entry, slot, item_id, fake_id FROM characters_npcbot_transmog"); + if (result) + { + do + { + field = result->Fetch(); + index = 0; + uint32 entry = field[ index].GetUInt32(); + + if (!sObjectMgr->GetCreatureTemplate(entry)) + { + TC_LOG_ERROR("server.loading", "Bot entry {} has transmog data but doesn't exist in `creature_template` table! Skipped.", entry); + continue; + } + + if (!_botsTransmogData.contains(entry)) + _botsTransmogData[entry] = new NpcBotTransmogData(); + + //load data + uint8 slot = field[++index].GetUInt8(); + uint32 item_id = field[++index].GetUInt32(); + int32 fake_id = field[++index].GetInt32(); + + _botsTransmogData[entry]->transmogs[slot] = { item_id, fake_id }; + + } while (result->NextRow()); + + TC_LOG_INFO("server.loading", ">> Bot transmog data loaded"); + } + else + TC_LOG_INFO("server.loading", ">> Bots transmog data is not loaded. Table `characters_npcbot_transmog` is empty!"); + + // 0 1 2 3 4 5 + result = CharacterDatabase.Query("SELECT entry, owner, roles, spec, faction, UNIX_TIMESTAMP(hire_time)," + // 6 7 8 9 10 11 12 13 14 + "equipMhEx, equipOhEx, equipRhEx, equipHead, equipShoulders, equipChest, equipWaist, equipLegs, equipFeet," + // 15 16 17 18 19 20 21 22 23 24 + "equipWrist, equipHands, equipBack, equipBody, equipFinger1, equipFinger2, equipTrinket1, equipTrinket2, equipNeck, spells_disabled FROM characters_npcbot"); + + if (result) + { + uint32 botcounter = 0; + uint32 datacounter = 0; + std::set botgrids; + QueryResult infores; + CreatureTemplate const* proto; + NpcBotData* botData; + std::list entryList; + + do + { + field = result->Fetch(); + index = 0; + uint32 entry = field[ index].GetUInt32(); + + if (!sObjectMgr->GetCreatureTemplate(entry)) + { + TC_LOG_ERROR("server.loading", "Bot entry {} doesn't exist in `creature_template` table! Skipped.", entry); + continue; + } + + //load data + botData = new NpcBotData(0, 0); + botData->owner = field[++index].GetUInt32(); + botData->roles = field[++index].GetUInt32(); + botData->spec = field[++index].GetUInt8(); + botData->faction = field[++index].GetUInt32(); + botData->hire_time = field[++index].GetUInt64(); + + for (uint8 i = BOT_SLOT_MAINHAND; i != BOT_INVENTORY_SIZE; ++i) + botData->equips[i] = field[++index].GetUInt32(); + + if (char const* disabled_spells_str = field[++index].GetCString()) + { + std::vector tok = Trinity::Tokenize(disabled_spells_str, ' ', false); + for (std::vector::size_type i = 0; i != tok.size(); ++i) + botData->disabled_spells.insert(*(Trinity::StringTo(tok[i]))); + } + + entryList.push_back(entry); + _botsData[entry] = botData; + ++datacounter; + + } while (result->NextRow()); + + TC_LOG_INFO("server.loading", ">> Loaded {} bot data entries", datacounter); + + if (spawn) + { + for (std::list::const_iterator itr = entryList.cbegin(); itr != entryList.cend(); ++itr) + { + uint32 entry = *itr; + proto = sObjectMgr->GetCreatureTemplate(entry); + // 1 2 3 4 5 6 + infores = WorldDatabase.PQuery("SELECT guid, map, position_x, position_y, position_z, orientation FROM creature WHERE id = {}", entry); + if (!infores) + { + TC_LOG_ERROR("server.loading", "Cannot spawn npcbot {} (id: {}), not found in `creature` table!", proto->Name, entry); + continue; + } + + field = infores->Fetch(); + uint32 tableGuid = field[0].GetUInt32(); + uint32 mapId = uint32(field[1].GetUInt16()); + float pos_x = field[2].GetFloat(); + float pos_y = field[3].GetFloat(); + float pos_z = field[4].GetFloat(); + float ori = field[5].GetFloat(); + + CellCoord c = Trinity::ComputeCellCoord(pos_x, pos_y); + GridCoord g = Trinity::ComputeGridCoord(pos_x, pos_y); + ASSERT(c.IsCoordValid(), "Invalid Cell coord!"); + ASSERT(g.IsCoordValid(), "Invalid Grid coord!"); + Map* map = sMapMgr->CreateBaseMap(mapId); + Position spawnPos(pos_x, pos_y, pos_z, ori); + Creature* bot = new Creature(); + if (!bot->LoadBotCreatureFromDB(tableGuid, map, false, false, entry, &spawnPos)) + { + delete bot; + TC_LOG_FATAL("server.loading", "Cannot load npcbot {} from DB!", entry); + ABORT(); + } + + if (!bot->AIM_Initialize()) + { + delete bot; + TC_LOG_FATAL("server.loading", "Cannot initialize npcbot {} AI!", entry); + ABORT(); + } + + if (!bot->IsAlive()) + { + TC_LOG_WARN("server.loading", "bot {} is dead, respawning!", entry); + bot->setDeathState(JUST_RESPAWNED); + } + + TC_LOG_DEBUG("server.loading", ">> Spawned npcbot {} (id: {}, map: {}, grid: {}, cell: {})", proto->Name, entry, mapId, g.GetId(), c.GetId()); + botgrids.insert(g.GetId()); + ++botcounter; + } + + TC_LOG_INFO("server.loading", ">> Spawned {} npcbot(s) within {} grid(s) in {} ms", botcounter, uint32(botgrids.size()), GetMSTimeDiffToNow(botoldMSTime)); + } + } + else + TC_LOG_INFO("server.loading", ">> Loaded 0 npcbots. Table `characters_npcbot` is empty!"); + + allBotsLoaded = true; +} + +void BotDataMgr::LoadNpcBotGroupData() +{ + TC_LOG_INFO("server.loading", "Loading NPCBot group members..."); + + uint32 oldMSTime = getMSTime(); + + CharacterDatabase.DirectExecute("DELETE FROM characters_npcbot_group_member WHERE guid NOT IN (SELECT guid FROM `groups`)"); + CharacterDatabase.DirectExecute("DELETE FROM characters_npcbot_group_member WHERE entry NOT IN (SELECT entry FROM characters_npcbot)"); + + // 0 1 2 3 4 + QueryResult result = CharacterDatabase.Query("SELECT guid, entry, memberFlags, subgroup, roles FROM characters_npcbot_group_member ORDER BY guid"); + if (!result) + { + TC_LOG_INFO("server.loading", ">> Loaded 0 NPCBot group members. DB table `characters_npcbot_group_member` is empty!"); + return; + } + + uint32 count = 0; + do + { + Field* fields = result->Fetch(); + + uint32 creature_id = fields[1].GetUInt32(); + uint8 subgroup = fields[3].GetUInt8(); + if (!SelectNpcBotExtras(creature_id)) + { + TC_LOG_WARN("server.loading", "Table `characters_npcbot_group_member` contains non-NPCBot creature {} which will not be loaded!", creature_id); + continue; + } + + if (Group* group = sGroupMgr->GetGroupByDbStoreId(fields[0].GetUInt32())) + { + group->LoadCreatureMemberFromDB(creature_id, fields[2].GetUInt8(), subgroup, fields[4].GetUInt8()); + const_cast(ASSERT_NOTNULL(BotDataMgr::FindBot(creature_id)))->SetBotGroup(group, subgroup); + } + else + TC_LOG_ERROR("misc", "BotDataMgr::LoadNpcBotGroupData: Consistency failed, can't find group (storage id: {})", fields[0].GetUInt32()); + + ++count; + + } while (result->NextRow()); + + TC_LOG_INFO("server.loading", ">> Loaded {} NPCBot group members in {} ms", count, GetMSTimeDiffToNow(oldMSTime)); +} + +void BotDataMgr::LoadNpcBotGearStorage() +{ + TC_LOG_INFO("server.loading", "Loading NPCBot items storage..."); + + uint32 oldMSTime = getMSTime(); + + QueryResult result = CharacterDatabase.Query( + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + "SELECT ii.creatorGuid, ii.giftCreatorGuid, ii.count, ii.duration, ii.charges, ii.flags, ii.enchantments, ii.randomPropertyId, ii.durability, ii.playedTime, ii.text, ii.guid, ii.itemEntry, ii.owner_guid, gs.guid, gs.item_guid" + " FROM characters_npcbot_gear_storage gs JOIN item_instance ii ON gs.item_guid = ii.guid ORDER BY gs.guid, gs.item_guid"); + if (!result) + { + TC_LOG_INFO("server.loading", ">> Loaded 0 NPCBot stored gear items. DB table `characters_npcbot_gear_storage` is empty!"); + return; + } + + uint32 count = 0; + std::set player_guids; + do + { + Field* fields = result->Fetch(); + + uint32 item_id = fields[12].GetUInt32(); + uint32 player_guidlow = fields[14].GetUInt32(); + uint32 item_guidlow = fields[15].GetUInt32(); + + Item* item = new Item(); + ObjectGuid player_guid = ObjectGuid::Create(player_guidlow); + ASSERT(item->LoadFromDB(item_guidlow, player_guid, fields, item_id), "LoadNpcBotGearStorage(): unable to load item %u id %u! Owner: %s", item_guidlow, item_id, player_guid.ToString().c_str()); + + _botStoredGearMap[player_guid].insert(item); + player_guids.insert(player_guidlow); + ++count; + + } while (result->NextRow()); + + TC_LOG_INFO("server.loading", ">> Loaded {} NPCBot stored items for {} bot owners in {} ms", count, uint32(player_guids.size()), GetMSTimeDiffToNow(oldMSTime)); +} + +void BotDataMgr::DeleteOldLogs() +{ + uint32 month_cutoff = static_cast(GameTime::GetGameTime() - static_cast(BOT_LOG_KEEP_DAYS) * DAY); + CharacterDatabase.PExecute("DELETE FROM `characters_npcbot_logs` WHERE timestamp IS NOT NULL AND timestamp < FROM_UNIXTIME({})", month_cutoff); + TC_LOG_INFO("server.loading", "Deleting NPCBot log entries older than {} days...", BOT_LOG_KEEP_DAYS); +} + +void BotDataMgr::LoadWanderMap(bool reload) +{ + using SpawnMapEx = std::map; + using SpawnVector = std::vector; + + const std::array ALL_CONTINENT_MAPS = { 0u, 1u, 530, 571 }; + + if (WanderNode::GetAllWPsCount() > 0u) + { + if (!reload) + return; + + WanderNode::RemoveAllWPs(); + } + + _wpMinSpawnLevelPerMapId.clear(); + _wpMaxSpawnLevelPerMapId.clear(); + + uint32 botoldMSTime = getMSTime(); + + TC_LOG_INFO("server.loading", "Setting up wander map..."); + + // 0 1 2 3 4 5 6 7 8 9 10 11 12 + QueryResult wres = WorldDatabase.Query("SELECT id,mapid,x,y,z,o,zoneId,areaId,minlevel,maxlevel,flags,name,links FROM creature_template_npcbot_wander_nodes ORDER BY mapid,id"); + if (!wres) + { + TC_LOG_FATAL("server.loading", "Failed to load wander points: table `creature_template_npcbot_wander_nodes` is empty!"); + ASSERT(false); + } + + const uint32 maxof_minclasslvl_nr = GetMinLevelForBotClass(BOT_CLASS_DEATH_KNIGHT); // 55 + const uint32 maxof_minclasslvl_ex = GetMinLevelForBotClass(BOT_CLASS_DREADLORD); // 60 + SpawnVector all_spawn_nodes; + SpawnMapEx spawn_node_exists_a; + SpawnMapEx spawn_node_exists_h; + SpawnMapEx spawn_node_exists_n; + std::unordered_map>>> links_to_create; + + all_spawn_nodes.reserve(wres->GetRowCount() >> 8); + for (SpawnMapEx* smap : { &spawn_node_exists_a, &spawn_node_exists_h, &spawn_node_exists_n }) + for (uint32 mapId : ALL_CONTINENT_MAPS) + if (BotMgr::IsBotGenerationEnabledWorldMapId(mapId)) + smap->emplace(mapId, false); + + uint32 disabled_nodes = 0; + do + { + Field* fields = wres->Fetch(); + uint32 index = 0; + + uint32 id = fields[ index].GetUInt32(); + uint32 mapId = fields[++index].GetUInt16(); + float x = fields[++index].GetFloat(); + float y = fields[++index].GetFloat(); + float z = fields[++index].GetFloat(); + float o = fields[++index].GetFloat(); + uint32 zoneId = fields[++index].GetUInt32(); + uint32 areaId = fields[++index].GetUInt32(); + uint8 minLevel = fields[++index].GetUInt8(); + uint8 maxLevel = fields[++index].GetUInt8(); + uint32 flags = fields[++index].GetUInt32(); + std::string name = fields[++index].GetString(); + std::string_view lstr = fields[++index].GetStringView(); + + WanderNode::nextWPId = std::max(WanderNode::nextWPId, id); + + MapEntry const* mapEntry = sMapStore.LookupEntry(mapId); + if (!mapEntry) + { + TC_LOG_ERROR("server.loading", "WP {} has invalid map id {}!", id, mapId); + continue; + } + + if (minLevel == 1u && maxLevel == DEFAULT_MAX_LEVEL) + TC_LOG_WARN("server.loading", "WP {} has no levels set.", id); + + if (!minLevel || !maxLevel || minLevel > DEFAULT_MAX_LEVEL || maxLevel > DEFAULT_MAX_LEVEL || minLevel > maxLevel) + { + TC_LOG_WARN("server.loading", "WP {} has invalid levels min {} max {}! Setting to default...", + id, uint32(minLevel), uint32(maxLevel)); + minLevel = 1; + maxLevel = DEFAULT_MAX_LEVEL; + } + + if (flags >= AsUnderlyingType(BotWPFlags::BOTWP_FLAG_END)) + { + TC_LOG_WARN("server.loading", "WP {} has invalid flags {}! Removing all invalid flags...", id, flags); + flags &= (AsUnderlyingType(BotWPFlags::BOTWP_FLAG_END) - 1); + } + + const uint32 nonbg_flags = AsUnderlyingType(BotWPFlags::BOTWP_FLAG_BG_FLAG_PICKUP_TARGET) | AsUnderlyingType(BotWPFlags::BOTWP_FLAG_BG_FLAG_DELIVER_TARGET); + if ((flags & nonbg_flags) && !mapEntry->IsBattleground()) + { + TC_LOG_WARN("server.loading", "WP {} has BG-only flags {} for non-BG map {}! Removing...", id, (flags & nonbg_flags), mapEntry->ID); + flags &= ~nonbg_flags; + } + + const uint32 conflicting_flags_1 = AsUnderlyingType(BotWPFlags::BOTWP_FLAG_ALLIANCE_ONLY) | AsUnderlyingType(BotWPFlags::BOTWP_FLAG_HORDE_ONLY); + if ((flags & conflicting_flags_1) == conflicting_flags_1) + { + TC_LOG_WARN("server.loading", "WP {} has conflicting flags {}+{}! Removing both...", + id, AsUnderlyingType(BotWPFlags::BOTWP_FLAG_ALLIANCE_ONLY), AsUnderlyingType(BotWPFlags::BOTWP_FLAG_HORDE_ONLY)); + flags &= ~conflicting_flags_1; + } + + if (mapEntry->IsContinent() && !BotMgr::IsBotGenerationEnabledWorldMapId(mapId)) + { + ++disabled_nodes; + continue; + } + + WanderNode* wp = new WanderNode(id, mapId, x, y, z, o, zoneId, areaId, name); + wp->SetLevels(minLevel, maxLevel); + wp->SetFlags(BotWPFlags(flags)); + + if (wp->HasFlag(BotWPFlags::BOTWP_FLAG_SPAWN) && !lstr.empty()) + all_spawn_nodes.push_back(wp); + + if (lstr.empty()) + { + TC_LOG_ERROR("server.loading", "WP {} has no links!", id); + continue; + } + std::vector tok = Trinity::Tokenize(lstr, ' ', false); + for (std::vector::size_type i = 0; i != tok.size(); ++i) + { + std::vector link_str = Trinity::Tokenize(tok[i], ':', false); + ASSERT(link_str.size() == 2u, "Invalid links_str format: '%s'", std::string(tok[i].data(), tok[i].length()).c_str()); + ASSERT(link_str[0].find(" ") == std::string_view::npos); + ASSERT(link_str[1].find(" ") == std::string_view::npos); + ASSERT(Trinity::StringTo(link_str[0]) != std::nullopt, "Invalid links_str format: '%s'", std::string(tok[i].data(), tok[i].length()).c_str()); + ASSERT(Trinity::StringTo(link_str[1]) != std::nullopt, "Invalid links_str format: '%s'", std::string(tok[i].data(), tok[i].length()).c_str()); + + std::pair tok_pair = { std::string(link_str[0].data(), link_str[0].length()), std::string(link_str[1].data(), link_str[1].length()) }; + if (links_to_create.find(id) == links_to_create.cend()) + links_to_create[id] = { wp, {std::move(tok_pair)} }; + else + links_to_create.at(id).second.push_back(std::move(tok_pair)); + } + + } while (wres->NextRow()); + + for (WanderNode const* wp : all_spawn_nodes) + { + uint32 mapId = wp->GetMapId(); + auto [minLevel, maxLevel] = wp->GetLevels(); + + spawn_node_exists_a[mapId] |= (maxLevel >= maxof_minclasslvl_nr && !wp->HasFlag(BotWPFlags::BOTWP_FLAG_HORDE_ONLY)); + spawn_node_exists_h[mapId] |= (maxLevel >= maxof_minclasslvl_nr && !wp->HasFlag(BotWPFlags::BOTWP_FLAG_ALLIANCE_ONLY)); + spawn_node_exists_n[mapId] |= (maxLevel >= maxof_minclasslvl_ex && !wp->HasFlag(BotWPFlags::BOTWP_FLAG_ALLIANCE_OR_HORDE_ONLY)); + + decltype(_wpMinSpawnLevelPerMapId)::const_iterator mincit = _wpMinSpawnLevelPerMapId.find(mapId); + _wpMinSpawnLevelPerMapId[mapId] = std::min((mincit != _wpMinSpawnLevelPerMapId.cend()) ? mincit->second : uint8(DEFAULT_MAX_LEVEL), minLevel); + decltype(_wpMaxSpawnLevelPerMapId)::const_iterator maxcit = _wpMaxSpawnLevelPerMapId.find(mapId); + _wpMaxSpawnLevelPerMapId[mapId] = std::max((maxcit != _wpMaxSpawnLevelPerMapId.cend()) ? maxcit->second : 1u, maxLevel); + } + + bool spawn_node_minclasslvl_exists_all = true; + for (auto& kv : spawn_node_exists_a) + { + if (!kv.second) + { + TC_LOG_FATAL("server.loading", "No valid Alliance spawn node for at least level {} on map {}! Spawning wandering bots is impossible! Aborting.", + maxof_minclasslvl_nr, kv.first); + spawn_node_minclasslvl_exists_all = false; + } + } + for (auto& kv : spawn_node_exists_h) + { + if (!kv.second) + { + TC_LOG_FATAL("server.loading", "No valid Horde spawn node for at least level {} on map {}! Spawning wandering bots is impossible! Aborting.", + maxof_minclasslvl_nr, kv.first); + spawn_node_minclasslvl_exists_all = false; + } + } + for (auto& kv : spawn_node_exists_n) + { + if (!kv.second) + { + if (sMapStore.LookupEntry(kv.first)->IsBattlegroundOrArena()) + TC_LOG_INFO("server.loading", "No valid Neutral spawn node for at least level {} on non-continent map {}.", maxof_minclasslvl_ex, kv.first); + else + { + TC_LOG_FATAL("server.loading", "No valid Neutral spawn node for at least level {} on map {}! Spawning wandering bots is impossible! Aborting.", + maxof_minclasslvl_ex, kv.first); + spawn_node_minclasslvl_exists_all = false; + } + } + } + if (!spawn_node_minclasslvl_exists_all) + ABORT(); + + const uint8 TEAMS_COUNT = TEAM_NEUTRAL + 1; + char const* const team_strs[TEAMS_COUNT] = { "Alliance", "Horde", "Neutral" }; + std::array spawn_node_levels[TEAMS_COUNT]{ { false } }; + uint8 min_spawn_level = DEFAULT_MAX_LEVEL; + uint8 max_spawn_level = 0; + for (WanderNode const* wp : all_spawn_nodes) + { + if (sMapStore.LookupEntry(wp->GetMapId())->IsContinent() && BotMgr::IsBotGenerationEnabledWorldMapId(wp->GetMapId())) + { + auto [minLevel, maxLevel] = wp->GetLevels(); + min_spawn_level = std::min(min_spawn_level, minLevel); + max_spawn_level = std::max(max_spawn_level, maxLevel); + } + } + for (WanderNode const* wp : all_spawn_nodes) + { + if (sMapStore.LookupEntry(wp->GetMapId())->IsContinent()) + { + auto [minLevel, maxLevel] = wp->GetLevels(); + minLevel = std::max(minLevel, 1); + maxLevel = std::min(maxLevel, max_spawn_level); + for (uint8 k = 0; k < TEAMS_COUNT; ++k) + { + if ((k == 0 && !wp->HasFlag(BotWPFlags::BOTWP_FLAG_HORDE_ONLY)) || + (k == 1 && !wp->HasFlag(BotWPFlags::BOTWP_FLAG_ALLIANCE_ONLY)) || + (k == 2 && !wp->HasFlag(BotWPFlags::BOTWP_FLAG_ALLIANCE_OR_HORDE_ONLY))) + { + for (uint8 i = minLevel; i <= maxLevel; ++i) + spawn_node_levels[k][i - 1] = true; + } + } + } + } + for (uint8 k = 0; k < TEAMS_COUNT; ++k) + { + auto const& vec = spawn_node_levels[k]; + for (uint32 i = min_spawn_level; i <= max_spawn_level; ++i) + { + if (vec[i - 1] == false) + TC_LOG_ERROR("server.loading", "No {} spawn node found for level {}! Wandering bots may cause a crash!", team_strs[k], i); + } + } + + float mindist = 50000.f; + float maxdist = 0.f; + for (auto const& vt : links_to_create) + { + for (auto const& p : vt.second.second) + { + uint32 lid = *Trinity::StringTo(p.first); + if (lid == vt.first) + { + TC_LOG_ERROR("server.loading", "WP {} has link {} which links to itself! Skipped.", vt.first, lid); + continue; + } + + WanderNode* lwp = WanderNode::FindInAllWPs(lid); + if (!lwp) + { + TC_LOG_ERROR("server.loading", "WP {} has link {} which does not exist!", vt.first, lid); + continue; + } + if (lwp->GetMapId() != vt.second.first->GetMapId()) + { + TC_LOG_ERROR("server.loading", "WP {} map {} has link {} ON A DIFFERENT MAP {}!", vt.first, vt.second.first->GetMapId(), lid, lwp->GetMapId()); + continue; + } + + bool is_continent = sMapStore.LookupEntry(vt.second.first->GetMapId())->IsContinent(); + float lwpdist2d = vt.second.first->GetExactDist2d(lwp); + if (lwpdist2d > MAX_WANDER_NODE_DISTANCE) + TC_LOG_WARN("server.loading", "Warning! Link distance between WP {} and {} is too great ({})", vt.first, lid, lwpdist2d); + if (lwpdist2d < MIN_WANDER_NODE_DISTANCE && is_continent) + TC_LOG_WARN("server.loading", "Warning! Link distance between WP {} and {} is low ({})", vt.first, lid, lwpdist2d); + + vt.second.first->Link(lwp, true); + + if (is_continent) + { + float dist2d = vt.second.first->GetExactDist2d(lwp); + if (dist2d < mindist) + mindist = dist2d; + if (dist2d > maxdist) + maxdist = dist2d; + } + } + } + + std::set tops; + WanderNode::DoForAllWPs([&](WanderNode const* wp) { + if (!tops.contains(wp) && wp->GetLinks().size() == 1u) + { + TC_LOG_DEBUG("server.loading", "Node {} ('{}') has single connection!", wp->GetWPId(), wp->GetName()); + WanderNode const* tn = wp->GetLinks().front(); + WanderNode const* prev = nullptr; + std::vector sc_chain; + sc_chain.push_back(wp); + tops.emplace(wp); + while (tn != wp) + { + if (tn->GetLinks().size() != 2u || !tn->HasLink(prev ? prev : wp)) + { + sc_chain.push_back(tn); + break; + } + prev = sc_chain.back(); + sc_chain.push_back(tn); + tn = *std::find_if_not(std::cbegin(tn->GetLinks()), std::cend(tn->GetLinks()), [=](WanderNode const* lwp) { return lwp == prev; }); + } + if (sc_chain.back()->GetLinks().size() == 1u && prev && sc_chain.back()->GetLinks().front() == prev) + { + TC_LOG_DEBUG("server.loading", "Node {} ('{}') has single connection!", tn->GetWPId(), tn->GetName()); + tops.emplace(sc_chain.back()); + std::ostringstream ss; + ss << "Node " << (sc_chain.size() == 2u ? "pair " : "chain "); + for (uint32 i = 0u; i < sc_chain.size(); ++i) + { + ss << sc_chain[i]->GetWPId(); + if (i < sc_chain.size() - 1u) + ss << '-'; + } + ss << " is isolated!"; + TC_LOG_INFO("server.loading", "{}", ss.str()); + } + } + }); + + TC_LOG_INFO("server.loading", ">> Loaded {} bot wander nodes ({} disabled) on {} maps (total {} tops) in {} ms", + uint32(WanderNode::GetAllWPsCount()), disabled_nodes, uint32(WanderNode::GetWPMapsCount()), uint32(tops.size()), GetMSTimeDiffToNow(botoldMSTime)); +} + +void BotDataMgr::GenerateWanderingBots() +{ + const uint32 wandering_bots_desired = BotMgr::GetDesiredWanderingBotsCount(); + + if (wandering_bots_desired == 0) + return; + + TC_LOG_INFO("server.loading", "Spawning wandering bots..."); + + uint32 oldMSTime = getMSTime(); + + uint32 maxbots = sBotGen->GetSpareBotsCount(); + uint32 enabledbots = sBotGen->GetEnabledBotsCount(); + + if (maxbots < wandering_bots_desired) + { + TC_LOG_FATAL("server.loading", "Only {} out of {} bots of enabled classes aren't spawned. Desired amount of wandering bots ({}) cannot be created. Aborting!", + maxbots, enabledbots, wandering_bots_desired); + ASSERT(false); + } + + uint32 spawned_count = 0; + if (!sBotGen->GenerateWanderingBotsToSpawn(wandering_bots_desired, -1, -1, false, nullptr, nullptr, spawned_count)) + { + TC_LOG_FATAL("server.loading", "Failed to spawn all {} bots ({} succeeded)!", wandering_bots_desired, spawned_count); + ASSERT(false); + } + + TC_LOG_INFO("server.loading", ">> Set up spawning of {} wandering bots in {} ms", spawned_count, GetMSTimeDiffToNow(oldMSTime)); +} + +bool BotDataMgr::GenerateBattlegroundBots(Player const* groupLeader, [[maybe_unused]] Group const* group, BattlegroundQueue* queue, PvPDifficultyEntry const* bracketEntry, GroupQueueInfo const* gqinfo) +{ + if (!BotMgr::IsBotGenerationEnabledBGs()) + return true; + + BattlegroundTypeId bgTypeId = gqinfo->BgTypeId; + uint8 atype = gqinfo->ArenaType; + uint32 ammr = gqinfo->ArenaMatchmakerRating; + BattlegroundBracketId bracketId = bracketEntry->GetBracketId(); + BattlegroundQueueTypeId bgqTypeId = sBattlegroundMgr->BGQueueTypeId(bgTypeId, atype); + + uint32 tarteamplayers = BotMgr::GetBGTargetTeamPlayersCount(bgTypeId); + + if (tarteamplayers == 0) + { + TC_LOG_INFO("npcbots", "[Disabled] BG {} wandering bots generation is disabled (not implemented?)", uint32(bgTypeId)); + return true; + } + + //find running BG + auto const& all_bgs = sBattlegroundMgr->GetBgDataStore(); + for (auto const& kv : all_bgs) + { + if (kv.first == bgTypeId) + { + for (auto const& real_bg_pair : kv.second.m_Battlegrounds) + { + Battleground const* real_bg = real_bg_pair.second.get(); + if (real_bg->GetInstanceID() != 0 && real_bg->GetBracketId() == bracketId && real_bg->GetStatus() < STATUS_WAIT_LEAVE && real_bg->HasFreeSlots()) + { + if (real_bg->GetFreeSlotsForTeam(groupLeader->GetTeam()) < gqinfo->Players.size()) + { + TC_LOG_INFO("npcbots", "[Already running 1] Found running non-full BG {} instance {}. Not generating bots: queuing group or player (leader {}) CANNOT join existing BG, prevent borrowing bots", + uint32(bgTypeId), real_bg->GetInstanceID(), groupLeader->GetGUID().GetCounter()); + } + else + { + TC_LOG_INFO("npcbots", "[Already running 2] Found running non-full BG {} instance {}. Not generating bots: queuing group or player (leader {}) CAN join existing BG", + uint32(bgTypeId), real_bg->GetInstanceID(), groupLeader->GetGUID().GetCounter()); + } + return true; + } + } + } + } + + Battleground const* bg_template = sBattlegroundMgr->GetBattlegroundTemplate(bgTypeId); + + if (!bg_template) + return false; + + uint32 minteamplayers = bg_template->GetMinPlayersPerTeam(); + uint32 maxteamplayers = bg_template->GetMaxPlayersPerTeam(); + + RoundToInterval(tarteamplayers, minteamplayers, maxteamplayers); + + uint32 queued_players_a = 0; + uint32 queued_players_h = 0; + for (uint8 i = 0; i < BG_QUEUE_GROUP_TYPES_COUNT; ++i) + { + for (GroupQueueInfo const* qgr : queue->m_QueuedGroups[bracketId][i]) + { + if (qgr->Team == ALLIANCE) + queued_players_a += qgr->Players.size(); + else + queued_players_h += qgr->Players.size(); + } + } + + uint32 needed_bots_count_a = (queued_players_a < tarteamplayers) ? (tarteamplayers - queued_players_a) : 0; + uint32 needed_bots_count_h = (queued_players_h < tarteamplayers) ? (tarteamplayers - queued_players_h) : 0; + + ASSERT(needed_bots_count_a <= maxteamplayers); + ASSERT(needed_bots_count_h <= maxteamplayers); + + if (needed_bots_count_a + needed_bots_count_h == 0) + { + TC_LOG_INFO("npcbots", "[No bots required] Failed to generate bots for BG {} inited by player {} ({})", + uint32(bgTypeId), groupLeader->GetName(), groupLeader->GetGUID().GetCounter()); + return true; + } + + uint32 spare_bots_a = sBotGen->GetSpareBotsCount(TEAM_ALLIANCE); + uint32 spare_bots_h = sBotGen->GetSpareBotsCount(TEAM_HORDE); + + if (queued_players_a + spare_bots_a < minteamplayers) + { + TC_LOG_INFO("npcbots", "[Not enough A bots] Failed to generate bots for BG {} inited by player {} ({})", + uint32(bgTypeId), groupLeader->GetName(), groupLeader->GetGUID().GetCounter()); + return false; + } + if (queued_players_h + spare_bots_h < minteamplayers) + { + TC_LOG_INFO("npcbots", "[Not enough H bots] Failed to generate bots for BG {} inited by player {} ({})", + uint32(bgTypeId), groupLeader->GetName(), groupLeader->GetGUID().GetCounter()); + return false; + } + + needed_bots_count_a = std::min(needed_bots_count_a, spare_bots_a); + needed_bots_count_h = std::min(needed_bots_count_h, spare_bots_h); + + uint32 spawned_a = 0; + uint32 spawned_h = 0; + NpcBotRegistry spawned_bots_a; + NpcBotRegistry spawned_bots_h; + + if (needed_bots_count_a) + { + if (!sBotGen->GenerateWanderingBotsToSpawn(needed_bots_count_a, bg_template->GetMapId(), ALLIANCE, true, bracketEntry, &spawned_bots_a, spawned_a)) + { + TC_LOG_WARN("npcbots", "Failed to spawn {} ALLIANCE bots for BG {} '{}' queued A {} H {} req A {} H {} spare {}", + needed_bots_count_a, uint32(bg_template->GetTypeID()), bg_template->GetName(), + queued_players_a, queued_players_h, needed_bots_count_a, needed_bots_count_h, spare_bots_a); + for (NpcBotRegistry const* registry1 : { &spawned_bots_a, &spawned_bots_h }) + for (Creature const* bot : *registry1) + DespawnWandererBot(bot->GetEntry()); + return false; + } + } + if (needed_bots_count_h) + { + if (!sBotGen->GenerateWanderingBotsToSpawn(needed_bots_count_h, bg_template->GetMapId(), HORDE, true, bracketEntry, &spawned_bots_h, spawned_h)) + { + TC_LOG_WARN("npcbots", "Failed to spawn {} HORDE bots for BG {} '{}' queued A {} H {} req A {} H {} spare {}", + needed_bots_count_h, uint32(bg_template->GetTypeID()), bg_template->GetName(), + queued_players_a, queued_players_h, needed_bots_count_a, needed_bots_count_h, spare_bots_h); + for (NpcBotRegistry const* registry2 : { &spawned_bots_a, &spawned_bots_h }) + for (Creature const* bot : *registry2) + DespawnWandererBot(bot->GetEntry()); + return false; + } + } + + ASSERT(uint32(spawned_bots_a.size()) == needed_bots_count_a); + ASSERT(uint32(spawned_bots_h.size()) == needed_bots_count_h); + + botBGJoinEvents[groupLeader->GetGUID()].AddEventAtOffset([ammr = ammr, atype = atype, bgqTypeId = bgqTypeId, bgTypeId = bgTypeId, bracketId = bracketId]() { + sBattlegroundMgr->ScheduleQueueUpdate(ammr, atype, bgqTypeId, bgTypeId, bracketId); + }, Seconds(2)); + + uint8 maxlevel = BotMgr::IsBotLevelCappedByConfigBGFirstPlayer() ? groupLeader->GetLevel() : 0; + for (NpcBotRegistry const* registry3 : { &spawned_bots_a, &spawned_bots_h }) + { + uint32 seconds_delay = 5; + for (Creature const* bot : *registry3) + { + bot->GetBotAI()->SetBotCommandState(BOT_COMMAND_STAY); + bot->GetBotAI()->canUpdate = false; + + const_cast(bot)->SetPvP(true); + if (maxlevel && bot->GetLevel() > maxlevel) + const_cast(bot)->SetLevel(maxlevel); + queue->AddBotAsGroup(bot->GetGUID(), GetTeamIdForFaction(bot->GetFaction()) == TEAM_HORDE ? HORDE : ALLIANCE, + bgTypeId, bracketEntry, atype, false, gqinfo->ArenaTeamRating, ammr); + + seconds_delay = std::min(uint32(MINUTE * 2), seconds_delay + std::max(1u, uint32((MINUTE / 2) / std::max(needed_bots_count_a, needed_bots_count_h)))); + + BotBattlegroundEnterEvent* bbe = new BotBattlegroundEnterEvent(groupLeader->GetGUID(), bot->GetGUID(), bgqTypeId, bgTypeId, + botBGJoinEvents[groupLeader->GetGUID()].CalculateTime(Milliseconds(uint32(INVITE_ACCEPT_WAIT_TIME) + uint32(BG_START_DELAY_2M))).count()); + botBGJoinEvents[groupLeader->GetGUID()].AddEventAtOffset(bbe, Seconds(seconds_delay)); + } + } + + return true; +} + +ItemPerBotClassMap const& BotDataMgr::GetWanderingBotsSortedGearMap() +{ + return _botsWanderCreaturesSortedGear; +} + +void BotDataMgr::CreateWanderingBotsSortedGear() +{ + TC_LOG_INFO("server.loading", "Sorting wandering bot's gear..."); + + uint32 oldMSTime = getMSTime(); + + std::set disabled_item_ids; + QueryResult dires = WorldDatabase.Query("SELECT id FROM creature_template_npcbot_disabled_items"); + if (dires) + { + do + { + uint32 id = dires->Fetch()->GetUInt32(); + disabled_item_ids.insert(id); + + } while (dires->NextRow()); + + TC_LOG_INFO("server.loading", ">> Loaded {} disabled wandering bots gear items", uint32(disabled_item_ids.size())); + } + else + TC_LOG_INFO("server.loading", ">> Loaded 0 disabled wandering bots gear items. Table `creature_template_npcbot_disabled_items` is empty!"); + + const std::map InvTypeToBotSlot = { + {INVTYPE_HEAD, BOT_SLOT_HEAD}, + {INVTYPE_SHOULDERS, BOT_SLOT_SHOULDERS}, + {INVTYPE_CHEST, BOT_SLOT_CHEST}, + {INVTYPE_ROBE, BOT_SLOT_CHEST}, + {INVTYPE_WAIST, BOT_SLOT_WAIST}, + {INVTYPE_LEGS, BOT_SLOT_LEGS}, + {INVTYPE_FEET, BOT_SLOT_FEET}, + {INVTYPE_WRISTS, BOT_SLOT_WRIST}, + {INVTYPE_HANDS, BOT_SLOT_HANDS} + }; + + auto push_gear_to_classes = [](ItemTemplate const& itt, uint8 slot, uint8 lstep, std::initializer_list const& cs) { + for (BotClasses c : cs) + { + if (c == BOT_CLASS_SPHYNX && + (itt.InventoryType == INVTYPE_FINGER || itt.InventoryType == INVTYPE_TRINKET || itt.InventoryType == INVTYPE_CLOAK || itt.InventoryType == INVTYPE_NECK || itt.InventoryType == INVTYPE_SHIELD)) + continue; + if (!itt.AllowableClass || itt.AllowableClass >= ((1u << MAX_CLASSES) - 1) || !!(itt.AllowableClass & (1 << (c - 1)))) + _botsWanderCreaturesSortedGear[c][slot][lstep].push_back(itt.ItemId); + } + }; + + const std::initializer_list IntUsers = { BOT_CLASS_PALADIN, BOT_CLASS_PRIEST, BOT_CLASS_SHAMAN, BOT_CLASS_MAGE, BOT_CLASS_WARLOCK, BOT_CLASS_DRUID, BOT_CLASS_SPHYNX, BOT_CLASS_ARCHMAGE, BOT_CLASS_DREADLORD, BOT_CLASS_NECROMANCER, BOT_CLASS_SEA_WITCH, BOT_CLASS_CRYPT_LORD }; + const std::initializer_list StrUsers = { BOT_CLASS_WARRIOR, BOT_CLASS_DEATH_KNIGHT, BOT_CLASS_SPELLBREAKER, BOT_CLASS_CRYPT_LORD }; + const std::initializer_list AgiUsers = { BOT_CLASS_HUNTER, BOT_CLASS_SHAMAN, BOT_CLASS_ROGUE, BOT_CLASS_DRUID, BOT_CLASS_BM, BOT_CLASS_DARK_RANGER }; + + ItemTemplateContainer const& all_item_templates = sObjectMgr->GetItemTemplateStore(); + for (auto const& kv : all_item_templates) + { + ItemTemplate const& proto = kv.second; + + if (!proto.ItemLevel || proto.RequiredSpell) + continue; + + bool skip = false; + switch (proto.Quality) + { + case ITEM_QUALITY_NORMAL: + if (std::any_of(proto.Spells.cbegin(), proto.Spells.cend(), [](_Spell const& spell) { return !!spell.SpellId; })) + skip = true; + if (proto.RequiredLevel > 14) + skip = true; + break; + case ITEM_QUALITY_UNCOMMON: + if (proto.RequiredLevel > 75) + skip = true; + [[fallthrough]]; + case ITEM_QUALITY_RARE: + if (proto.RequiredLevel > 75 && proto.RequiredLevel < 80) + skip = true; + [[fallthrough]]; + case ITEM_QUALITY_EPIC: + if (!(proto.RequiredLevel >= 2 && proto.RequiredLevel <= DEFAULT_MAX_LEVEL)) + skip = true; + if (!proto.RandomProperty && !proto.RandomSuffix && !proto.StatsCount && + !(proto.Class == ITEM_CLASS_WEAPON && proto.SubClass == ITEM_SUBCLASS_WEAPON_WAND) && + !(proto.Class == ITEM_CLASS_WEAPON && proto.SubClass == ITEM_SUBCLASS_WEAPON_BOW && proto.RequiredLevel < 45)) + skip = true; + break; + default: + skip = true; + break; + } + if (skip) + continue; + + if (disabled_item_ids.contains(proto.ItemId)) + { + //TC_LOG_INFO("server.loading", "Item {} is disabled...", proto.ItemId); + continue; + } + + if (proto.StatsCount > 0 && std::any_of(proto.ItemStat.cbegin(), proto.ItemStat.cend(), [](_ItemStat const& stat) { + return (stat.ItemStatType == ITEM_MOD_DEFENSE_SKILL_RATING || stat.ItemStatType == ITEM_MOD_DODGE_RATING || + stat.ItemStatType == ITEM_MOD_PARRY_RATING || stat.ItemStatType == ITEM_MOD_BLOCK_VALUE) && + stat.ItemStatValue > 0; + })) + continue; + + uint8 reqLstep = (((proto.RequiredLevel == 1) ? 0 : proto.RequiredLevel) + ITEM_SORTING_LEVEL_STEP - 1) / ITEM_SORTING_LEVEL_STEP; + bool is_caster_item = proto.StatsCount > 0 && std::any_of(proto.ItemStat.cbegin(), proto.ItemStat.cend(), [](_ItemStat const& stat) { + return (stat.ItemStatType == ITEM_MOD_INTELLECT || stat.ItemStatType == ITEM_MOD_SPELL_POWER || + stat.ItemStatType == ITEM_MOD_SPELL_PENETRATION || stat.ItemStatType == ITEM_MOD_MANA_REGENERATION) && + stat.ItemStatValue > 0; + }); + bool is_strength_item = proto.StatsCount > 0 && std::any_of(proto.ItemStat.cbegin(), proto.ItemStat.cend(), [](_ItemStat const& stat) { + return stat.ItemStatType == ITEM_MOD_STRENGTH && stat.ItemStatValue > 0; + }); + bool is_agility_item = proto.StatsCount > 0 && std::any_of(proto.ItemStat.cbegin(), proto.ItemStat.cend(), [](_ItemStat const& stat) { + return stat.ItemStatType == ITEM_MOD_AGILITY && stat.ItemStatValue > 0; + }); + + switch (proto.Class) + { + case ITEM_CLASS_ARMOR: + switch (proto.InventoryType) + { + case INVTYPE_NECK: + if (proto.Quality < ITEM_QUALITY_UNCOMMON) + break; + if (is_caster_item || (reqLstep < LEVEL_STEPS - 1 && proto.StatsCount == 0)) + push_gear_to_classes(proto, BOT_SLOT_NECK, reqLstep, IntUsers); + if (is_strength_item || (reqLstep < LEVEL_STEPS - 1 && proto.StatsCount == 0)) + push_gear_to_classes(proto, BOT_SLOT_NECK, reqLstep, StrUsers); + if (is_agility_item || (reqLstep < LEVEL_STEPS - 1 && proto.StatsCount == 0)) + push_gear_to_classes(proto, BOT_SLOT_NECK, reqLstep, AgiUsers); + break; + case INVTYPE_FINGER: + if (proto.Quality < ITEM_QUALITY_UNCOMMON) + break; + if (is_caster_item || (reqLstep < LEVEL_STEPS - 1 && proto.StatsCount == 0)) + { + push_gear_to_classes(proto, BOT_SLOT_FINGER1, reqLstep, IntUsers); + push_gear_to_classes(proto, BOT_SLOT_FINGER2, reqLstep, IntUsers); + } + if (is_strength_item || (reqLstep < LEVEL_STEPS - 1 && proto.StatsCount == 0)) + { + push_gear_to_classes(proto, BOT_SLOT_FINGER1, reqLstep, StrUsers); + push_gear_to_classes(proto, BOT_SLOT_FINGER2, reqLstep, StrUsers); + } + if (is_agility_item || (reqLstep < LEVEL_STEPS - 1 && proto.StatsCount == 0)) + { + push_gear_to_classes(proto, BOT_SLOT_FINGER1, reqLstep, AgiUsers); + push_gear_to_classes(proto, BOT_SLOT_FINGER2, reqLstep, AgiUsers); + } + break; + case INVTYPE_TRINKET: + if (proto.Quality < ITEM_QUALITY_UNCOMMON) + break; + if (!is_strength_item && !is_agility_item) + { + push_gear_to_classes(proto, BOT_SLOT_TRINKET1, reqLstep, IntUsers); + push_gear_to_classes(proto, BOT_SLOT_TRINKET2, reqLstep, IntUsers); + } + if (!is_caster_item) + { + push_gear_to_classes(proto, BOT_SLOT_TRINKET1, reqLstep, StrUsers); + push_gear_to_classes(proto, BOT_SLOT_TRINKET2, reqLstep, StrUsers); + push_gear_to_classes(proto, BOT_SLOT_TRINKET1, reqLstep, AgiUsers); + push_gear_to_classes(proto, BOT_SLOT_TRINKET2, reqLstep, AgiUsers); + } + break; + case INVTYPE_CLOAK: + if (is_caster_item || (reqLstep < LEVEL_STEPS - 1 && proto.StatsCount == 0)) + push_gear_to_classes(proto, BOT_SLOT_BACK, reqLstep, IntUsers); + if (is_strength_item || (reqLstep < LEVEL_STEPS - 1 && proto.StatsCount == 0)) + push_gear_to_classes(proto, BOT_SLOT_BACK, reqLstep, StrUsers); + if (is_agility_item || (reqLstep < LEVEL_STEPS - 1 && proto.StatsCount == 0)) + push_gear_to_classes(proto, BOT_SLOT_BACK, reqLstep, AgiUsers); + break; + case INVTYPE_HOLDABLE: + if (proto.Quality < ITEM_QUALITY_UNCOMMON) + break; + push_gear_to_classes(proto, BOT_SLOT_OFFHAND, reqLstep, { BOT_CLASS_PRIEST, BOT_CLASS_MAGE, BOT_CLASS_WARLOCK, BOT_CLASS_DRUID }); + break; + case INVTYPE_SHIELD: + if (proto.Armor == 0) + break; + if (!is_caster_item) + push_gear_to_classes(proto, BOT_SLOT_OFFHAND, reqLstep, { BOT_CLASS_WARRIOR }); + if (is_caster_item || proto.RequiredLevel < 60 || (proto.RequiredLevel < 69 && (proto.RandomProperty || proto.RandomSuffix))) + push_gear_to_classes(proto, BOT_SLOT_OFFHAND, reqLstep, { BOT_CLASS_PALADIN, BOT_CLASS_SHAMAN, BOT_CLASS_SPELLBREAKER }); + break; + case INVTYPE_HEAD: + case INVTYPE_SHOULDERS: + case INVTYPE_CHEST: + case INVTYPE_ROBE: + case INVTYPE_WAIST: + case INVTYPE_LEGS: + case INVTYPE_FEET: + case INVTYPE_WRISTS: + case INVTYPE_HANDS: + { + if (proto.Armor == 0) + break; + decltype(InvTypeToBotSlot)::const_iterator ci = InvTypeToBotSlot.find(proto.InventoryType); + ASSERT(ci != InvTypeToBotSlot.cend()); + uint8 slot = ci->second; + switch (proto.SubClass) + { + case ItemSubclassArmor::ITEM_SUBCLASS_ARMOR_CLOTH: + if (slot == BOT_SLOT_CHEST && proto.InventoryType != INVTYPE_ROBE) + break; + push_gear_to_classes(proto, slot, reqLstep, { BOT_CLASS_PRIEST, BOT_CLASS_MAGE, BOT_CLASS_WARLOCK, BOT_CLASS_ARCHMAGE, BOT_CLASS_NECROMANCER, BOT_CLASS_SEA_WITCH }); + break; + case ItemSubclassArmor::ITEM_SUBCLASS_ARMOR_LEATHER: + if (!is_caster_item) + push_gear_to_classes(proto, slot, reqLstep, { BOT_CLASS_ROGUE, BOT_CLASS_DARK_RANGER }); + push_gear_to_classes(proto, slot, reqLstep, { BOT_CLASS_DRUID }); + if (proto.RequiredLevel < 40) + push_gear_to_classes(proto, slot, reqLstep, { BOT_CLASS_HUNTER, BOT_CLASS_SHAMAN }); + break; + case ItemSubclassArmor::ITEM_SUBCLASS_ARMOR_MAIL: + if (proto.RequiredLevel < 40) + { + if (!is_caster_item) + push_gear_to_classes(proto, slot, reqLstep, { BOT_CLASS_WARRIOR }); + push_gear_to_classes(proto, slot, reqLstep, { BOT_CLASS_PALADIN }); + } + else + push_gear_to_classes(proto, slot, reqLstep, { BOT_CLASS_HUNTER, BOT_CLASS_SHAMAN }); + if (!is_caster_item) + push_gear_to_classes(proto, slot, reqLstep, { BOT_CLASS_BM, BOT_CLASS_SPELLBREAKER }); + push_gear_to_classes(proto, slot, reqLstep, { BOT_CLASS_SPHYNX, BOT_CLASS_CRYPT_LORD }); + break; + case ItemSubclassArmor::ITEM_SUBCLASS_ARMOR_PLATE: + if (!is_caster_item) + push_gear_to_classes(proto, slot, reqLstep, { BOT_CLASS_WARRIOR, BOT_CLASS_DEATH_KNIGHT, BOT_CLASS_BM, BOT_CLASS_SPELLBREAKER }); + if (is_caster_item || proto.RequiredLevel < 60 || (proto.RequiredLevel < 78 && (proto.RandomProperty || proto.RandomSuffix))) + push_gear_to_classes(proto, slot, reqLstep, { BOT_CLASS_PALADIN, BOT_CLASS_SPHYNX, BOT_CLASS_DREADLORD, BOT_CLASS_CRYPT_LORD }); + break; + default: + break; + } + break; + } + default: + break; + } + break; + case ITEM_CLASS_WEAPON: + if (proto.Damage[0].DamageMin < 1.0f || proto.Damage[0].DamageMax < 2.0f || proto.Delay < 1000) + break; + if (proto.RequiredLevel > 75 && proto.Quality < ITEM_QUALITY_EPIC) + break; + switch (proto.SubClass) + { + case ItemSubclassWeapon::ITEM_SUBCLASS_WEAPON_WAND: + if (proto.InventoryType != INVTYPE_RANGED && proto.InventoryType != INVTYPE_RANGEDRIGHT) + break; + push_gear_to_classes(proto, BOT_SLOT_MAINHAND, reqLstep, { BOT_CLASS_SPHYNX }); + push_gear_to_classes(proto, BOT_SLOT_OFFHAND, reqLstep, { BOT_CLASS_SPHYNX }); + push_gear_to_classes(proto, BOT_SLOT_RANGED, reqLstep, { BOT_CLASS_PRIEST, BOT_CLASS_MAGE, BOT_CLASS_WARLOCK }); + break; + case ItemSubclassWeapon::ITEM_SUBCLASS_WEAPON_GUN: + case ItemSubclassWeapon::ITEM_SUBCLASS_WEAPON_CROSSBOW: + if (proto.InventoryType != INVTYPE_RANGED && proto.InventoryType != INVTYPE_RANGEDRIGHT) + break; + push_gear_to_classes(proto, BOT_SLOT_RANGED, reqLstep, { BOT_CLASS_WARRIOR, BOT_CLASS_ROGUE, BOT_CLASS_HUNTER }); + break; + case ItemSubclassWeapon::ITEM_SUBCLASS_WEAPON_BOW: + if (proto.InventoryType != INVTYPE_RANGED && proto.InventoryType != INVTYPE_RANGEDRIGHT) + break; + push_gear_to_classes(proto, BOT_SLOT_RANGED, reqLstep, { BOT_CLASS_WARRIOR, BOT_CLASS_ROGUE, BOT_CLASS_HUNTER, BOT_CLASS_DARK_RANGER, BOT_CLASS_SEA_WITCH }); + break; + case ItemSubclassWeapon::ITEM_SUBCLASS_WEAPON_THROWN: + if (proto.InventoryType != INVTYPE_THROWN) + break; + push_gear_to_classes(proto, BOT_SLOT_RANGED, reqLstep, { BOT_CLASS_WARRIOR, BOT_CLASS_ROGUE }); + break; + case ItemSubclassWeapon::ITEM_SUBCLASS_WEAPON_STAFF: + if (proto.InventoryType != INVTYPE_2HWEAPON) + break; + if (is_caster_item || proto.RequiredLevel < 50) + push_gear_to_classes(proto, BOT_SLOT_MAINHAND, reqLstep, { BOT_CLASS_PRIEST, BOT_CLASS_MAGE, BOT_CLASS_WARLOCK, BOT_CLASS_DRUID, BOT_CLASS_SHAMAN, BOT_CLASS_ARCHMAGE, BOT_CLASS_NECROMANCER, BOT_CLASS_DREADLORD, BOT_CLASS_CRYPT_LORD }); + break; + case ItemSubclassWeapon::ITEM_SUBCLASS_WEAPON_AXE2: + if (proto.InventoryType != INVTYPE_2HWEAPON) + break; + if (!is_caster_item) + { + push_gear_to_classes(proto, BOT_SLOT_MAINHAND, reqLstep, { BOT_CLASS_WARRIOR, BOT_CLASS_PALADIN, BOT_CLASS_DEATH_KNIGHT, BOT_CLASS_BM }); + if (proto.RequiredLevel >= 60 - ITEM_SORTING_LEVEL_STEP) + push_gear_to_classes(proto, BOT_SLOT_OFFHAND, reqLstep, { BOT_CLASS_WARRIOR }); + } + push_gear_to_classes(proto, BOT_SLOT_MAINHAND, reqLstep, { BOT_CLASS_HUNTER, BOT_CLASS_SHAMAN, BOT_CLASS_DREADLORD, BOT_CLASS_CRYPT_LORD }); + break; + case ItemSubclassWeapon::ITEM_SUBCLASS_WEAPON_SWORD2: + if (proto.InventoryType != INVTYPE_2HWEAPON) + break; + if (!is_caster_item) + { + push_gear_to_classes(proto, BOT_SLOT_MAINHAND, reqLstep, { BOT_CLASS_WARRIOR, BOT_CLASS_PALADIN, BOT_CLASS_DEATH_KNIGHT, BOT_CLASS_BM }); + if (proto.RequiredLevel >= 60 - ITEM_SORTING_LEVEL_STEP) + push_gear_to_classes(proto, BOT_SLOT_OFFHAND, reqLstep, { BOT_CLASS_WARRIOR }); + } + push_gear_to_classes(proto, BOT_SLOT_MAINHAND, reqLstep, { BOT_CLASS_HUNTER, BOT_CLASS_DREADLORD, BOT_CLASS_CRYPT_LORD }); + break; + case ItemSubclassWeapon::ITEM_SUBCLASS_WEAPON_POLEARM: + if (proto.InventoryType != INVTYPE_2HWEAPON) + break; + if (!is_caster_item) + push_gear_to_classes(proto, BOT_SLOT_MAINHAND, reqLstep, { BOT_CLASS_WARRIOR, BOT_CLASS_PALADIN, BOT_CLASS_DEATH_KNIGHT, BOT_CLASS_BM }); + push_gear_to_classes(proto, BOT_SLOT_MAINHAND, reqLstep, { BOT_CLASS_DRUID, BOT_CLASS_HUNTER, BOT_CLASS_DREADLORD, BOT_CLASS_CRYPT_LORD }); + break; + case ItemSubclassWeapon::ITEM_SUBCLASS_WEAPON_MACE2: + if (proto.InventoryType != INVTYPE_2HWEAPON) + break; + if (!is_caster_item) + { + push_gear_to_classes(proto, BOT_SLOT_MAINHAND, reqLstep, { BOT_CLASS_WARRIOR, BOT_CLASS_PALADIN, BOT_CLASS_HUNTER, BOT_CLASS_DEATH_KNIGHT }); + if (proto.RequiredLevel >= 60 - ITEM_SORTING_LEVEL_STEP) + push_gear_to_classes(proto, BOT_SLOT_OFFHAND, reqLstep, { BOT_CLASS_WARRIOR }); + } + push_gear_to_classes(proto, BOT_SLOT_MAINHAND, reqLstep, { BOT_CLASS_DRUID, BOT_CLASS_DREADLORD, BOT_CLASS_CRYPT_LORD }); + break; + case ItemSubclassWeapon::ITEM_SUBCLASS_WEAPON_AXE: + if (proto.InventoryType == INVTYPE_WEAPON || proto.InventoryType == INVTYPE_WEAPONMAINHAND) + { + if (!is_caster_item) + { + push_gear_to_classes(proto, BOT_SLOT_MAINHAND, reqLstep, { BOT_CLASS_WARRIOR }); + push_gear_to_classes(proto, BOT_SLOT_MAINHAND, reqLstep, { BOT_CLASS_ROGUE, BOT_CLASS_SPELLBREAKER }); + } + push_gear_to_classes(proto, BOT_SLOT_MAINHAND, reqLstep, { BOT_CLASS_PALADIN, BOT_CLASS_SHAMAN }); + } + if (proto.InventoryType == INVTYPE_WEAPON || proto.InventoryType == INVTYPE_WEAPONOFFHAND) + { + if (!is_caster_item) + { + if (proto.RequiredLevel < 60 - ITEM_SORTING_LEVEL_STEP) + push_gear_to_classes(proto, BOT_SLOT_OFFHAND, reqLstep, { BOT_CLASS_WARRIOR }); + push_gear_to_classes(proto, BOT_SLOT_OFFHAND, reqLstep, { BOT_CLASS_ROGUE }); + } + push_gear_to_classes(proto, BOT_SLOT_OFFHAND, reqLstep, { BOT_CLASS_SHAMAN }); + } + break; + case ItemSubclassWeapon::ITEM_SUBCLASS_WEAPON_MACE: + if (proto.InventoryType == INVTYPE_WEAPON || proto.InventoryType == INVTYPE_WEAPONMAINHAND) + { + if (!is_caster_item) + { + push_gear_to_classes(proto, BOT_SLOT_MAINHAND, reqLstep, { BOT_CLASS_WARRIOR }); + push_gear_to_classes(proto, BOT_SLOT_MAINHAND, reqLstep, { BOT_CLASS_ROGUE, BOT_CLASS_SPELLBREAKER }); + } + push_gear_to_classes(proto, BOT_SLOT_MAINHAND, reqLstep, { BOT_CLASS_PALADIN, BOT_CLASS_SHAMAN }); + if (is_caster_item || proto.RequiredLevel < 55 || (proto.RequiredLevel < 78 && (proto.RandomProperty || proto.RandomSuffix))) + push_gear_to_classes(proto, BOT_SLOT_MAINHAND, reqLstep, { BOT_CLASS_DRUID, BOT_CLASS_PRIEST }); + } + if (proto.InventoryType == INVTYPE_WEAPON || proto.InventoryType == INVTYPE_WEAPONOFFHAND) + { + if (!is_caster_item) + { + if (proto.RequiredLevel < 60 - ITEM_SORTING_LEVEL_STEP) + push_gear_to_classes(proto, BOT_SLOT_OFFHAND, reqLstep, { BOT_CLASS_WARRIOR }); + push_gear_to_classes(proto, BOT_SLOT_OFFHAND, reqLstep, { BOT_CLASS_ROGUE }); + } + push_gear_to_classes(proto, BOT_SLOT_OFFHAND, reqLstep, { BOT_CLASS_SHAMAN }); + } + break; + case ItemSubclassWeapon::ITEM_SUBCLASS_WEAPON_SWORD: + if (proto.InventoryType == INVTYPE_WEAPON || proto.InventoryType == INVTYPE_WEAPONMAINHAND) + { + if (!is_caster_item) + { + push_gear_to_classes(proto, BOT_SLOT_MAINHAND, reqLstep, { BOT_CLASS_WARRIOR }); + push_gear_to_classes(proto, BOT_SLOT_MAINHAND, reqLstep, { BOT_CLASS_ROGUE, BOT_CLASS_SPELLBREAKER, BOT_CLASS_DARK_RANGER }); + } + push_gear_to_classes(proto, BOT_SLOT_MAINHAND, reqLstep, { BOT_CLASS_PALADIN }); + if (is_caster_item || proto.RequiredLevel < 55 || (proto.RequiredLevel < 78 && (proto.RandomProperty || proto.RandomSuffix))) + push_gear_to_classes(proto, BOT_SLOT_MAINHAND, reqLstep, { BOT_CLASS_MAGE, BOT_CLASS_WARLOCK }); + } + if (proto.InventoryType == INVTYPE_WEAPON || proto.InventoryType == INVTYPE_WEAPONOFFHAND) + { + if (!is_caster_item) + { + if (proto.RequiredLevel < 60 - ITEM_SORTING_LEVEL_STEP) + push_gear_to_classes(proto, BOT_SLOT_OFFHAND, reqLstep, { BOT_CLASS_WARRIOR }); + push_gear_to_classes(proto, BOT_SLOT_OFFHAND, reqLstep, { BOT_CLASS_ROGUE, BOT_CLASS_DARK_RANGER }); + } + } + break; + case ItemSubclassWeapon::ITEM_SUBCLASS_WEAPON_FIST_WEAPON: + if (proto.InventoryType == INVTYPE_WEAPON || proto.InventoryType == INVTYPE_WEAPONMAINHAND) + { + if (!is_caster_item) + { + push_gear_to_classes(proto, BOT_SLOT_MAINHAND, reqLstep, { BOT_CLASS_WARRIOR }); + push_gear_to_classes(proto, BOT_SLOT_MAINHAND, reqLstep, { BOT_CLASS_SHAMAN, BOT_CLASS_ROGUE, BOT_CLASS_SPELLBREAKER }); + } + } + if (proto.InventoryType == INVTYPE_WEAPON || proto.InventoryType == INVTYPE_WEAPONOFFHAND) + { + if (!is_caster_item) + { + if (proto.RequiredLevel < 60 - ITEM_SORTING_LEVEL_STEP) + push_gear_to_classes(proto, BOT_SLOT_OFFHAND, reqLstep, { BOT_CLASS_WARRIOR }); + push_gear_to_classes(proto, BOT_SLOT_OFFHAND, reqLstep, { BOT_CLASS_SHAMAN, BOT_CLASS_ROGUE }); + } + } + break; + case ItemSubclassWeapon::ITEM_SUBCLASS_WEAPON_DAGGER: + if (proto.InventoryType == INVTYPE_WEAPON || proto.InventoryType == INVTYPE_WEAPONMAINHAND) + { + if (!is_caster_item) + { + if (proto.RequiredLevel < 60 - ITEM_SORTING_LEVEL_STEP) + push_gear_to_classes(proto, BOT_SLOT_MAINHAND, reqLstep, { BOT_CLASS_WARRIOR }); + push_gear_to_classes(proto, BOT_SLOT_MAINHAND, reqLstep, { BOT_CLASS_ROGUE, BOT_CLASS_SPELLBREAKER, BOT_CLASS_DARK_RANGER }); + } + push_gear_to_classes(proto, BOT_SLOT_MAINHAND, reqLstep, { BOT_CLASS_SHAMAN }); + if (is_caster_item || proto.RequiredLevel < 55 || (proto.RequiredLevel < 78 && (proto.RandomProperty || proto.RandomSuffix))) + push_gear_to_classes(proto, BOT_SLOT_MAINHAND, reqLstep, { BOT_CLASS_PRIEST, BOT_CLASS_MAGE, BOT_CLASS_WARLOCK, BOT_CLASS_DRUID, BOT_CLASS_SEA_WITCH }); + } + if (proto.InventoryType == INVTYPE_WEAPON || proto.InventoryType == INVTYPE_WEAPONOFFHAND) + { + if (!is_caster_item) + { + if (proto.RequiredLevel < 60 - ITEM_SORTING_LEVEL_STEP) + push_gear_to_classes(proto, BOT_SLOT_OFFHAND, reqLstep, { BOT_CLASS_WARRIOR }); + push_gear_to_classes(proto, BOT_SLOT_OFFHAND, reqLstep, { BOT_CLASS_ROGUE, BOT_CLASS_DARK_RANGER }); + } + push_gear_to_classes(proto, BOT_SLOT_OFFHAND, reqLstep, { BOT_CLASS_SHAMAN, BOT_CLASS_SEA_WITCH }); + } + break; + default: + break; + } + break; + default: + break; + } + } + + for (uint32 c = BOT_CLASS_WARRIOR; c < BOT_CLASS_END; ++c) + { + if (c == 10) + continue; + ItemPerSlot const& ips_arr = _botsWanderCreaturesSortedGear.at(c); + for (uint32 s = BOT_SLOT_MAINHAND; s < BOT_INVENTORY_SIZE; ++s) + { + if (s == BOT_SLOT_FINGER2 || s == BOT_SLOT_TRINKET1 || s == BOT_SLOT_TRINKET2 || s == BOT_SLOT_BODY) + continue; + if ((s == BOT_SLOT_FINGER1 || s == BOT_SLOT_NECK || s == BOT_SLOT_BACK) && c == BOT_CLASS_SPHYNX) + continue; + if (s == BOT_SLOT_RANGED && !(c == BOT_CLASS_HUNTER || c == BOT_CLASS_ROGUE || c == BOT_CLASS_WARRIOR || c == BOT_CLASS_PRIEST || + c == BOT_CLASS_MAGE || c == BOT_CLASS_WARLOCK || c == BOT_CLASS_DARK_RANGER || c == BOT_CLASS_SEA_WITCH)) + continue; + ItemLeveledArr const& il_arr = ips_arr[s]; + for (uint32 lstep = 0; lstep < LEVEL_STEPS; ++lstep) + { + if ((s == BOT_SLOT_SHOULDERS || s == BOT_SLOT_FINGER1 || s == BOT_SLOT_NECK) && lstep < 4) + continue; + if ((s == BOT_SLOT_HEAD || s == BOT_SLOT_TRINKET1) && lstep < 6) + continue; + if (s == BOT_SLOT_OFFHAND && + (lstep < 3 || c == BOT_CLASS_PALADIN || c == BOT_CLASS_HUNTER || c == BOT_CLASS_DEATH_KNIGHT || c == BOT_CLASS_BM || c == BOT_CLASS_ARCHMAGE || + c == BOT_CLASS_SPHYNX || c == BOT_CLASS_DREADLORD || c == BOT_CLASS_NECROMANCER || c == BOT_CLASS_CRYPT_LORD)) + continue; + if ((c == BOT_CLASS_DREADLORD || c == BOT_CLASS_DEATH_KNIGHT) && lstep < 8) + continue; + if (il_arr[lstep].empty()) + { + uint32 minlvl = std::max(lstep * ITEM_SORTING_LEVEL_STEP, 1); + uint32 maxlvl = (lstep + 1) * ITEM_SORTING_LEVEL_STEP - 1; + TC_LOG_DEBUG("npcbots", "No items for class {} slot {} at levels {}-{}!", c, s, minlvl, maxlvl); + } + } + } + } + + TC_LOG_INFO("server.loading", ">> Sorted wandering bots gear in {} ms", GetMSTimeDiffToNow(oldMSTime)); +} + +Item* BotDataMgr::GenerateWanderingBotItem(uint8 slot, uint8 botclass, uint8 level, std::function&& check) +{ + ASSERT(slot < BOT_INVENTORY_SIZE); + ASSERT(botclass < BOT_CLASS_END); + ASSERT(level <= DEFAULT_MAX_LEVEL + 4); + + uint8 lvl = level; + ItemIdVector const* itemIdVec = &_botsWanderCreaturesSortedGear[botclass][slot][lvl / ITEM_SORTING_LEVEL_STEP]; + + while (itemIdVec->empty() && lvl > ITEM_SORTING_LEVEL_STEP) + { + lvl -= ITEM_SORTING_LEVEL_STEP; + itemIdVec = &_botsWanderCreaturesSortedGear[botclass][slot][lvl / ITEM_SORTING_LEVEL_STEP]; + } + + if (!itemIdVec->empty()) + { + ItemIdVector validVec; + validVec.reserve(itemIdVec->size()); + for (uint32 iid : *itemIdVec) + { + ItemTemplate const* proto = sObjectMgr->GetItemTemplate(iid); + if (check(proto)) + validVec.push_back(iid); + } + + if (!validVec.empty()) + { + uint32 itemId = Trinity::Containers::SelectRandomContainerElement(validVec); + if (Item* newItem = Item::CreateItem(itemId, 1, nullptr)) + { + if (uint32 randomPropertyId = GenerateItemRandomPropertyId(itemId)) + newItem->SetItemRandomProperties(randomPropertyId); + + return newItem; + } + } + } + + return nullptr; +} + +bool BotDataMgr::GenerateWanderingBotItemEnchants(Item* item, uint8 slot, uint8 spec) +{ + bool result = false; + + switch (slot) + { + case BOT_SLOT_BODY: + case BOT_SLOT_TRINKET1: + case BOT_SLOT_TRINKET2: + return result; + default: + break; + } + + ItemTemplate const* proto = item->GetTemplate(); + + if (proto->RequiredLevel < 60) + return result; + + static const auto is_enchantable = [](ItemTemplate const* p, SpellInfo const* s) { + SpellEffectInfo const& e = s->GetEffect(EFFECT_0); + return e.Effect == SPELL_EFFECT_ENCHANT_ITEM && s->EquippedItemClass == int32(p->Class) && s->BaseLevel <= p->RequiredLevel && e.MiscValue > 0 && + (s->EquippedItemClass == ITEM_CLASS_WEAPON ? !!(s->EquippedItemSubClassMask & (1 << p->SubClass)) : !!(s->EquippedItemInventoryTypeMask & (1 << p->InventoryType))) && + sSpellItemEnchantmentStore.LookupEntry(uint32(e.MiscValue)); + }; + + constexpr std::array weapon_enchants_dk{ 53323, 53331, 53341, 53342, 53343, 53344, 53346, 53347, 62158, 70164 }; //2h only + constexpr std::array weapon_enchants_caster{ 27968, 27975, 28003, 34010, 44510, 44629, 59619, 59625, 60714, 62948, 62959 }; + constexpr std::array weapon_enchants_melee{ 27971, 27977, 27984, 28004, 42620, 42974, 44524, 44576, 44630, 44633, 46578, 55836, 59619, 59621, 60621, 60691, 60707, 62257 }; + constexpr std::array armor_enchants_caster{ 34003, 34008, 44383, 44488, 44492, 44528, 44555, 44582, 44592, 44612, 44616, 44623, 44635, 47898, 47900, 47901, 57690, 57691, 59636, 59784, 59970, 60609, 60653, 60692, 60767, 61120, 61271, 62256, 60583, 50911, 55016, 55634, 55642, 56034 }; + constexpr std::array armor_enchants_melee{ 34007, 34008, 34009, 44383, 44484, 44488, 44492, 44500, 44513, 44528, 44529, 44575, 44589, 44598, 44612, 44616, 44623, 47898, 47900, 47901, 59777, 59954, 60606, 60609, 60616, 60623, 60663, 60668, 60692, 60763, 61271, 62256, 50903, 50911, 55016, 55777, 57690, 61117, 62201, 59636 }; + + //enchants + SpellInfo const* sInfo; + std::vector valid_enchant_ids; + valid_enchant_ids.reserve(1ULL << 6); + switch (spec) + { + case BOT_SPEC_PALADIN_HOLY: + case BOT_SPEC_PRIEST_DISCIPLINE: + case BOT_SPEC_PRIEST_HOLY: + case BOT_SPEC_PRIEST_SHADOW: + case BOT_SPEC_SHAMAN_ELEMENTAL: + case BOT_SPEC_SHAMAN_RESTORATION: + case BOT_SPEC_MAGE_ARCANE: + case BOT_SPEC_MAGE_FIRE: + case BOT_SPEC_MAGE_FROST: + case BOT_SPEC_WARLOCK_AFFLICTION: + case BOT_SPEC_WARLOCK_DEMONOLOGY: + case BOT_SPEC_WARLOCK_DESTRUCTION: + case BOT_SPEC_DRUID_BALANCE: + case BOT_SPEC_DRUID_RESTORATION: + switch (proto->Class) + { + case ITEM_CLASS_WEAPON: + for (uint32 spellId : weapon_enchants_caster) + { + sInfo = sSpellMgr->AssertSpellInfo(spellId); + if (is_enchantable(proto, sInfo)) + valid_enchant_ids.push_back(uint32(sInfo->GetEffect(EFFECT_0).MiscValue)); + } + break; + case ITEM_CLASS_ARMOR: + for (uint32 spellId : armor_enchants_caster) + { + sInfo = sSpellMgr->AssertSpellInfo(spellId); + if (is_enchantable(proto, sInfo)) + valid_enchant_ids.push_back(uint32(sInfo->GetEffect(EFFECT_0).MiscValue)); + } + break; + default: + break; + } + break; + case BOT_SPEC_DK_BLOOD: + case BOT_SPEC_DK_FROST: + case BOT_SPEC_DK_UNHOLY: + switch (proto->Class) + { + case ITEM_CLASS_WEAPON: + for (uint32 spellId : weapon_enchants_dk) + { + sInfo = sSpellMgr->AssertSpellInfo(spellId); + if (is_enchantable(proto, sInfo)) + valid_enchant_ids.push_back(uint32(sInfo->GetEffect(EFFECT_0).MiscValue)); + } + break; + default: + break; + } + [[fallthrough]]; + case BOT_SPEC_WARRIOR_ARMS: + case BOT_SPEC_WARRIOR_FURY: + case BOT_SPEC_WARRIOR_PROTECTION: + case BOT_SPEC_PALADIN_PROTECTION: + case BOT_SPEC_PALADIN_RETRIBUTION: + case BOT_SPEC_HUNTER_BEASTMASTERY: + case BOT_SPEC_HUNTER_MARKSMANSHIP: + case BOT_SPEC_HUNTER_SURVIVAL: + case BOT_SPEC_ROGUE_ASSASINATION: + case BOT_SPEC_ROGUE_COMBAT: + case BOT_SPEC_ROGUE_SUBTLETY: + case BOT_SPEC_SHAMAN_ENHANCEMENT: + case BOT_SPEC_DRUID_FERAL: + switch (proto->Class) + { + case ITEM_CLASS_WEAPON: + for (uint32 spellId : weapon_enchants_melee) + { + sInfo = sSpellMgr->AssertSpellInfo(spellId); + if (is_enchantable(proto, sInfo)) + valid_enchant_ids.push_back(uint32(sInfo->GetEffect(EFFECT_0).MiscValue)); + } + break; + case ITEM_CLASS_ARMOR: + for (uint32 spellId : armor_enchants_melee) + { + sInfo = sSpellMgr->AssertSpellInfo(spellId); + if (is_enchantable(proto, sInfo)) + valid_enchant_ids.push_back(uint32(sInfo->GetEffect(EFFECT_0).MiscValue)); + } + break; + default: + break; + } + break; + default: + break; + } + + uint32 enchant_id; + enchant_id = valid_enchant_ids.empty() ? 0 : valid_enchant_ids.size() == 1u ? valid_enchant_ids.front() : Trinity::Containers::SelectRandomContainerElement(valid_enchant_ids); + if (enchant_id) + { + item->SetUInt32Value(ITEM_FIELD_ENCHANTMENT_1_1 + PERM_ENCHANTMENT_SLOT*MAX_ENCHANTMENT_OFFSET + ENCHANTMENT_ID_OFFSET, enchant_id); + item->SetUInt32Value(ITEM_FIELD_ENCHANTMENT_1_1 + PERM_ENCHANTMENT_SLOT*MAX_ENCHANTMENT_OFFSET + ENCHANTMENT_DURATION_OFFSET, 0); + item->SetUInt32Value(ITEM_FIELD_ENCHANTMENT_1_1 + PERM_ENCHANTMENT_SLOT*MAX_ENCHANTMENT_OFFSET + ENCHANTMENT_CHARGES_OFFSET, 0); + result = true; + } + + //gems + constexpr std::array gems_caster{ 40132, 40135, 40123, 40127, 40128 }; + constexpr std::array gems_melee{ 40136, 40140, 40124, 40125, 40127, 40128 }; + + for (uint8 i = 0; i < MAX_ITEM_PROTO_SOCKETS; ++i) + { + valid_enchant_ids.clear(); + switch (spec) + { + case BOT_SPEC_PALADIN_HOLY: + case BOT_SPEC_PRIEST_DISCIPLINE: + case BOT_SPEC_PRIEST_HOLY: + case BOT_SPEC_PRIEST_SHADOW: + case BOT_SPEC_SHAMAN_ELEMENTAL: + case BOT_SPEC_SHAMAN_RESTORATION: + case BOT_SPEC_MAGE_ARCANE: + case BOT_SPEC_MAGE_FIRE: + case BOT_SPEC_MAGE_FROST: + case BOT_SPEC_WARLOCK_AFFLICTION: + case BOT_SPEC_WARLOCK_DEMONOLOGY: + case BOT_SPEC_WARLOCK_DESTRUCTION: + case BOT_SPEC_DRUID_BALANCE: + case BOT_SPEC_DRUID_RESTORATION: + for (uint32 gId : gems_caster) + { + GemPropertiesEntry const* gprops = sGemPropertiesStore.LookupEntry(sObjectMgr->GetItemTemplate(gId)->GemProperties); + if (gprops->Type & proto->Socket[i].Color) + valid_enchant_ids.push_back(gprops->EnchantID); + } + break; + case BOT_SPEC_DK_BLOOD: + case BOT_SPEC_DK_FROST: + case BOT_SPEC_DK_UNHOLY: + case BOT_SPEC_WARRIOR_ARMS: + case BOT_SPEC_WARRIOR_FURY: + case BOT_SPEC_WARRIOR_PROTECTION: + case BOT_SPEC_PALADIN_PROTECTION: + case BOT_SPEC_PALADIN_RETRIBUTION: + case BOT_SPEC_HUNTER_BEASTMASTERY: + case BOT_SPEC_HUNTER_MARKSMANSHIP: + case BOT_SPEC_HUNTER_SURVIVAL: + case BOT_SPEC_ROGUE_ASSASINATION: + case BOT_SPEC_ROGUE_COMBAT: + case BOT_SPEC_ROGUE_SUBTLETY: + case BOT_SPEC_SHAMAN_ENHANCEMENT: + case BOT_SPEC_DRUID_FERAL: + for (uint32 gId : gems_melee) + { + GemPropertiesEntry const* gprops = sGemPropertiesStore.LookupEntry(sObjectMgr->GetItemTemplate(gId)->GemProperties); + if (gprops->Type & proto->Socket[i].Color) + valid_enchant_ids.push_back(gprops->EnchantID); + } + break; + default: + break; + } + + enchant_id = valid_enchant_ids.empty() ? 0 : valid_enchant_ids.size() == 1u ? valid_enchant_ids.front() : Trinity::Containers::SelectRandomContainerElement(valid_enchant_ids); + if (enchant_id) + { + item->SetUInt32Value(ITEM_FIELD_ENCHANTMENT_1_1 + (uint8(SOCK_ENCHANTMENT_SLOT) + i)*MAX_ENCHANTMENT_OFFSET + ENCHANTMENT_ID_OFFSET, enchant_id); + item->SetUInt32Value(ITEM_FIELD_ENCHANTMENT_1_1 + (uint8(SOCK_ENCHANTMENT_SLOT) + i)*MAX_ENCHANTMENT_OFFSET + ENCHANTMENT_DURATION_OFFSET, 0); + item->SetUInt32Value(ITEM_FIELD_ENCHANTMENT_1_1 + (uint8(SOCK_ENCHANTMENT_SLOT) + i)*MAX_ENCHANTMENT_OFFSET + ENCHANTMENT_CHARGES_OFFSET, 0); + result = true; + } + } + + return result; +} + +CreatureTemplate const* BotDataMgr::GetBotExtraCreatureTemplate(uint32 entry) +{ + CreatureTemplateContainer::const_iterator cit = _botsWanderCreatureTemplates.find(entry); + return cit == _botsWanderCreatureTemplates.cend() ? nullptr : &cit->second; +} + +EquipmentInfo const* BotDataMgr::GetBotEquipmentInfo(uint32 entry) +{ + decltype(_botsWanderCreatureEquipmentTemplates)::const_iterator cit = _botsWanderCreatureEquipmentTemplates.find(entry); + if (cit == _botsWanderCreatureEquipmentTemplates.cend()) + { + static int8 eqId = 1; + return sObjectMgr->GetEquipmentInfo(entry, eqId); + } + else + return cit->second; +} + +void BotDataMgr::AddNpcBotData(uint32 entry, uint32 roles, uint8 spec, uint32 faction) +{ + //botData must be allocated explicitly + NpcBotDataMap::iterator itr = _botsData.find(entry); + if (itr == _botsData.end()) + { + NpcBotData* botData = new NpcBotData(roles, faction, spec); + _botsData[entry] = botData; + + CharacterDatabasePreparedStatement* bstmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_NPCBOT); + //"INSERT INTO characters_npcbot (entry, roles, spec, faction) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC); + bstmt->setUInt32(0, entry); + bstmt->setUInt32(1, roles); + bstmt->setUInt8(2, spec); + bstmt->setUInt32(3, faction); + CharacterDatabase.Execute(bstmt); + + return; + } + + TC_LOG_ERROR("sql.sql", "BotMgr::AddNpcBotData(): trying to add new data but entry already exists! entry = {}", entry); +} +NpcBotData const* BotDataMgr::SelectNpcBotData(uint32 entry) +{ + NpcBotDataMap::const_iterator itr = _botsData.find(entry); + return itr != _botsData.cend() ? itr->second : nullptr; +} +void BotDataMgr::UpdateNpcBotData(uint32 entry, NpcBotDataUpdateType updateType, void* data) +{ + NpcBotDataMap::iterator itr = _botsData.find(entry); + if (itr == _botsData.end()) + return; + + CharacterDatabasePreparedStatement* bstmt; + switch (updateType) + { + case NPCBOT_UPDATE_OWNER: + { + if (itr->second->owner == *(uint32*)(data)) + break; + itr->second->owner = *(uint32*)(data); + itr->second->hire_time = itr->second->owner ? uint64(time(0)) : 1ULL; + bstmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_NPCBOT_OWNER); + //"UPDATE characters_npcbot SET owner = ?, hire_time = FROM_UNIXTIME(?) WHERE entry = ?", CONNECTION_ASYNC + bstmt->setUInt32(0, itr->second->owner); + bstmt->setUInt64(1, itr->second->hire_time); + bstmt->setUInt32(2, entry); + CharacterDatabase.Execute(bstmt); + //break; //no break: erase transmogs + } + [[fallthrough]]; + case NPCBOT_UPDATE_TRANSMOG_ERASE: + bstmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_NPCBOT_TRANSMOG); + //"DELETE FROM characters_npcbot_transmog WHERE entry = ?", CONNECTION_ASYNC + bstmt->setUInt32(0, entry); + CharacterDatabase.Execute(bstmt); + break; + case NPCBOT_UPDATE_ROLES: + itr->second->roles = *(uint32*)(data); + bstmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_NPCBOT_ROLES); + //"UPDATE character_npcbot SET roles = ? WHERE entry = ?", CONNECTION_ASYNC + bstmt->setUInt32(0, itr->second->roles); + bstmt->setUInt32(1, entry); + CharacterDatabase.Execute(bstmt); + break; + case NPCBOT_UPDATE_SPEC: + itr->second->spec = *(uint8*)(data); + bstmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_NPCBOT_SPEC); + //"UPDATE characters_npcbot SET spec = ? WHERE entry = ?", CONNECTION_ASYNCH + bstmt->setUInt8(0, itr->second->spec); + bstmt->setUInt32(1, entry); + CharacterDatabase.Execute(bstmt); + break; + case NPCBOT_UPDATE_FACTION: + itr->second->faction = *(uint32*)(data); + bstmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_NPCBOT_FACTION); + //"UPDATE characters_npcbot SET faction = ? WHERE entry = ?", CONNECTION_ASYNCH + bstmt->setUInt32(0, itr->second->faction); + bstmt->setUInt32(1, entry); + CharacterDatabase.Execute(bstmt); + break; + case NPCBOT_UPDATE_DISABLED_SPELLS: + { + NpcBotData::DisabledSpellsContainer const* spells = (NpcBotData::DisabledSpellsContainer const*)(data); + std::ostringstream ss; + for (NpcBotData::DisabledSpellsContainer::const_iterator citr = spells->begin(); citr != spells->end(); ++citr) + ss << (*citr) << ' '; + + bstmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_NPCBOT_DISABLED_SPELLS); + //"UPDATE characters_npcbot SET spells_disabled = ? WHERE entry = ?", CONNECTION_ASYNCH + bstmt->setString(0, ss.str()); + bstmt->setUInt32(1, entry); + CharacterDatabase.Execute(bstmt); + break; + } + case NPCBOT_UPDATE_EQUIPS: + { + Item** items = (Item**)(data); + + EquipmentInfo const* einfo = BotDataMgr::GetBotEquipmentInfo(entry); + + CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); + + bstmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_NPCBOT_EQUIP); + //"UPDATE character_npcbot SET equipMhEx = ?, equipOhEx = ?, equipRhEx = ?, equipHead = ?, equipShoulders = ?, equipChest = ?, equipWaist = ?, equipLegs = ?, + //equipFeet = ?, equipWrist = ?, equipHands = ?, equipBack = ?, equipBody = ?, equipFinger1 = ?, equipFinger2 = ?, equipTrinket1 = ?, equipTrinket2 = ?, equipNeck = ? WHERE entry = ?", CONNECTION_ASYNC + CharacterDatabasePreparedStatement* stmt; + uint8 k; + for (k = BOT_SLOT_MAINHAND; k != BOT_INVENTORY_SIZE; ++k) + { + itr->second->equips[k] = items[k] ? items[k]->GetGUID().GetCounter() : 0; + if (Item const* botitem = items[k]) + { + bool standard = false; + for (uint8 i = 0; i != MAX_EQUIPMENT_ITEMS; ++i) + { + if (einfo->ItemEntry[i] == botitem->GetEntry()) + { + itr->second->equips[k] = 0; + bstmt->setUInt32(k, 0); + standard = true; + break; + } + } + if (standard) + continue; + + uint8 index = 0; + stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_ITEM_INSTANCE); + //REPLACE INTO item_instance (itemEntry, owner_guid, creatorGuid, giftCreatorGuid, count, duration, charges, flags, enchantments, randomPropertyId, durability, playedTime, text, guid) + //VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC : 0-13 + stmt->setUInt32( index, botitem->GetEntry()); + stmt->setUInt32(++index, botitem->GetOwnerGUID().GetCounter()); + stmt->setUInt32(++index, botitem->GetGuidValue(ITEM_FIELD_CREATOR).GetCounter()); + stmt->setUInt32(++index, botitem->GetGuidValue(ITEM_FIELD_GIFTCREATOR).GetCounter()); + stmt->setUInt32(++index, botitem->GetCount()); + stmt->setUInt32(++index, botitem->GetUInt32Value(ITEM_FIELD_DURATION)); + + std::ostringstream ssSpells; + for (uint8 i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i) + ssSpells << botitem->GetSpellCharges(i) << ' '; + stmt->setString(++index, ssSpells.str()); + + stmt->setUInt32(++index, botitem->GetUInt32Value(ITEM_FIELD_FLAGS)); + + std::ostringstream ssEnchants; + for (uint8 i = 0; i < MAX_ENCHANTMENT_SLOT; ++i) + { + ssEnchants << botitem->GetEnchantmentId(EnchantmentSlot(i)) << ' '; + ssEnchants << botitem->GetEnchantmentDuration(EnchantmentSlot(i)) << ' '; + ssEnchants << botitem->GetEnchantmentCharges(EnchantmentSlot(i)) << ' '; + } + stmt->setString(++index, ssEnchants.str()); + + stmt->setInt16 (++index, botitem->GetItemRandomPropertyId()); + stmt->setUInt16(++index, botitem->GetUInt32Value(ITEM_FIELD_DURABILITY)); + stmt->setUInt32(++index, botitem->GetUInt32Value(ITEM_FIELD_CREATE_PLAYED_TIME)); + stmt->setString(++index, botitem->GetText()); + stmt->setUInt32(++index, botitem->GetGUID().GetCounter()); + + trans->Append(stmt); + + Item::DeleteFromInventoryDB(trans, botitem->GetGUID().GetCounter()); //prevent duplicates + + bstmt->setUInt32(k, botitem->GetGUID().GetCounter()); + } + else + bstmt->setUInt32(k, uint32(0)); + } + + bstmt->setUInt32(k, entry); + trans->Append(bstmt); + CharacterDatabase.CommitTransaction(trans); + break; + } + case NPCBOT_UPDATE_ERASE: + { + NpcBotDataMap::iterator bitr = _botsData.find(entry); + ASSERT(bitr != _botsData.end()); + delete bitr->second; + _botsData.erase(bitr); + bstmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_NPCBOT); + //"DELETE FROM characters_npcbot WHERE entry = ?", CONNECTION_ASYNC + bstmt->setUInt32(0, entry); + CharacterDatabase.Execute(bstmt); + break; + } + default: + TC_LOG_ERROR("sql.sql", "BotDataMgr:UpdateNpcBotData: unhandled updateType {}", uint32(updateType)); + break; + } +} +void BotDataMgr::UpdateNpcBotDataAll(uint32 playerGuid, NpcBotDataUpdateType updateType, void* data) +{ + CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); + CharacterDatabasePreparedStatement* bstmt; + uint32 newowner = *(uint32*)(data); + switch (updateType) + { + case NPCBOT_UPDATE_OWNER: + ASSERT(newowner == 0); + bstmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_NPCBOT_EQUIP_RESET_ALL); + //"UPDATE characters_npcbot SET equipMhEx = 0, equipOhEx = 0, equipRhEx = 0, equipHead = 0, equipShoulders = 0, equipChest = 0, equipWaist = 0, equipLegs = 0, equipFeet = 0, " + //"equipWrist = 0, equipHands = 0, equipBack = 0, equipBody = 0, equipFinger1 = 0, equipFinger2 = 0, equipTrinket1 = 0, equipTrinket2 = 0, equipNeck = 0 WHERE owner = ?", CONNECTION_ASYNC + bstmt->setUInt32(0, playerGuid); + trans->Append(bstmt); + bstmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_NPCBOT_TRANSMOG_ALL); + //"DELETE FROM characters_npcbot_transmog WHERE entry IN (SELECT entry FROM characters_npcbot WHERE owner = ?)", CONNECTION_ASYNC + bstmt->setUInt32(0, playerGuid); + trans->Append(bstmt); + bstmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_NPCBOT_OWNER_ALL); + //"UPDATE characters_npcbot SET owner = ?, hire_time = FROM_UNIXTIME(?) WHERE owner = ?", CONNECTION_ASYNC + bstmt->setUInt32(0, newowner); + bstmt->setUInt64(1, uint64(1ULL)); + bstmt->setUInt32(2, playerGuid); + trans->Append(bstmt); + break; + default: + TC_LOG_ERROR("sql.sql", "BotDataMgr:UpdateNpcBotDataAll: unhandled updateType {}", uint32(updateType)); + break; + } + + if (trans->GetSize() > 0) + CharacterDatabase.CommitTransaction(trans); +} + +void BotDataMgr::SaveNpcBotStats(NpcBotStats const* stats) +{ + CharacterDatabasePreparedStatement* bstmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_NPCBOT_STATS); + //"REPLACE INTO characters_npcbot_stats + //(entry, maxhealth, maxpower, strength, agility, stamina, intellect, spirit, armor, defense, + //resHoly, resFire, resNature, resFrost, resShadow, resArcane, blockPct, dodgePct, parryPct, critPct, + //attackPower, spellPower, spellPen, hastePct, hitBonusPct, expertise, armorPenPct) VALUES + //(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC + + uint32 index = 0; + bstmt->setUInt32( index, stats->entry); + bstmt->setUInt32(++index, stats->maxhealth); + bstmt->setUInt32(++index, stats->maxpower); + bstmt->setUInt32(++index, stats->strength); + bstmt->setUInt32(++index, stats->agility); + bstmt->setUInt32(++index, stats->stamina); + bstmt->setUInt32(++index, stats->intellect); + bstmt->setUInt32(++index, stats->spirit); + bstmt->setUInt32(++index, stats->armor); + bstmt->setUInt32(++index, stats->defense); + bstmt->setUInt32(++index, stats->resHoly); + bstmt->setUInt32(++index, stats->resFire); + bstmt->setUInt32(++index, stats->resNature); + bstmt->setUInt32(++index, stats->resFrost); + bstmt->setUInt32(++index, stats->resShadow); + bstmt->setUInt32(++index, stats->resArcane); + bstmt->setFloat (++index, stats->blockPct); + bstmt->setFloat (++index, stats->dodgePct); + bstmt->setFloat (++index, stats->parryPct); + bstmt->setFloat (++index, stats->critPct); + bstmt->setUInt32(++index, stats->attackPower); + bstmt->setUInt32(++index, stats->spellPower); + bstmt->setUInt32(++index, stats->spellPen); + bstmt->setFloat (++index, stats->hastePct); + bstmt->setFloat (++index, stats->hitBonusPct); + bstmt->setUInt32(++index, stats->expertise); + bstmt->setFloat (++index, stats->armorPenPct); + + CharacterDatabase.Execute(bstmt); +} + +NpcBotAppearanceData const* BotDataMgr::SelectNpcBotAppearance(uint32 entry) +{ + NpcBotAppearanceDataMap::const_iterator itr = _botsAppearanceData.find(entry); + return itr != _botsAppearanceData.cend() ? itr->second : nullptr; +} + +NpcBotExtras const* BotDataMgr::SelectNpcBotExtras(uint32 entry) +{ + NpcBotExtrasMap::const_iterator itr = _botsExtras.find(entry); + return itr != _botsExtras.cend() ? itr->second : nullptr; +} + +NpcBotTransmogData const* BotDataMgr::SelectNpcBotTransmogs(uint32 entry) +{ + NpcBotTransmogDataMap::const_iterator itr = _botsTransmogData.find(entry); + return itr != _botsTransmogData.cend() ? itr->second : nullptr; +} +void BotDataMgr::UpdateNpcBotTransmogData(uint32 entry, uint8 slot, uint32 item_id, int32 fake_id, bool update_db) +{ + ASSERT(slot < BOT_TRANSMOG_INVENTORY_SIZE); + + NpcBotTransmogDataMap::const_iterator itr = _botsTransmogData.find(entry); + if (itr == _botsTransmogData.cend()) + _botsTransmogData[entry] = new NpcBotTransmogData(); + + _botsTransmogData[entry]->transmogs[slot] = { item_id, fake_id }; + + if (update_db) + { + CharacterDatabasePreparedStatement* bstmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_NPCBOT_TRANSMOG); + //"REPLACE INTO characters_npcbot_transmog (entry, slot, item_id, fake_id) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC + bstmt->setUInt32(0, entry); + bstmt->setUInt8(1, slot); + bstmt->setUInt32(2, item_id); + bstmt->setInt32(3, fake_id); + CharacterDatabase.Execute(bstmt); + } +} + +void BotDataMgr::ResetNpcBotTransmogData(uint32 entry, bool update_db) +{ + NpcBotTransmogDataMap::const_iterator itr = _botsTransmogData.find(entry); + if (itr == _botsTransmogData.cend()) + return; + + if (update_db) + { + CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); + for (uint8 i = 0; i != BOT_TRANSMOG_INVENTORY_SIZE; ++i) + { + if (_botsTransmogData[entry]->transmogs[i].first == 0 && _botsTransmogData[entry]->transmogs[i].second == -1) + continue; + + CharacterDatabasePreparedStatement* bstmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_NPCBOT_TRANSMOG); + //"REPLACE INTO characters_npcbot_transmog (entry, slot, item_id, fake_id) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC + bstmt->setUInt32(0, entry); + bstmt->setUInt8(1, i); + bstmt->setUInt32(2, 0); + bstmt->setInt32(3, -1); + trans->Append(bstmt); + } + + if (trans->GetSize() > 0) + CharacterDatabase.CommitTransaction(trans); + } + + for (uint8 i = 0; i != BOT_TRANSMOG_INVENTORY_SIZE; ++i) + _botsTransmogData[entry]->transmogs[i] = { 0, -1 }; +} + +void BotDataMgr::RegisterBot(Creature const* bot) +{ + if (_existingBots.find(bot) != _existingBots.end()) + { + TC_LOG_ERROR("entities.unit", "BotDataMgr::RegisterBot: bot {} ({}) already registered!", + bot->GetEntry(), bot->GetName()); + return; + } + + std::unique_lock lock(*GetLock()); + + _existingBots.insert(bot); + //TC_LOG_ERROR("entities.unit", "BotDataMgr::RegisterBot: registered bot {} ({})", bot->GetEntry(), bot->GetName()); +} +void BotDataMgr::UnregisterBot(Creature const* bot) +{ + if (_existingBots.find(bot) == _existingBots.end()) + { + TC_LOG_ERROR("entities.unit", "BotDataMgr::UnregisterBot: bot {} ({}) not found!", + bot->GetEntry(), bot->GetName()); + return; + } + + std::unique_lock lock(*GetLock()); + + _existingBots.erase(bot); + //TC_LOG_ERROR("entities.unit", "BotDataMgr::UnregisterBot: unregistered bot {} ({})", bot->GetEntry(), bot->GetName()); +} +Creature const* BotDataMgr::FindBot(uint32 entry) +{ + std::shared_lock lock(*GetLock()); + + for (NpcBotRegistry::const_iterator ci = _existingBots.cbegin(); ci != _existingBots.cend(); ++ci) + { + if ((*ci)->GetEntry() == entry) + return *ci; + } + return nullptr; +} +Creature const* BotDataMgr::FindBot(std::string_view name, LocaleConstant loc, std::vector const* not_ids) +{ + std::wstring wname; + if (Utf8toWStr(name, wname)) + { + wstrToLower(wname); + std::shared_lock lock(*GetLock()); + for (NpcBotRegistry::const_iterator ci = _existingBots.cbegin(); ci != _existingBots.cend(); ++ci) + { + if (not_ids && std::find(not_ids->cbegin(), not_ids->cend(), (*ci)->GetEntry()) != not_ids->cend()) + continue; + + std::string basename = (*ci)->GetName(); + if (CreatureLocale const* creatureInfo = sObjectMgr->GetCreatureLocale((*ci)->GetEntry())) + { + if (creatureInfo->Name.size() > loc && !creatureInfo->Name[loc].empty()) + basename = creatureInfo->Name[loc]; + } + + std::wstring wbname; + if (!Utf8toWStr(basename, wbname)) + continue; + + wstrToLower(wbname); + if (wbname == wname) + return *ci; + } + } + + return nullptr; +} + +NpcBotRegistry const& BotDataMgr::GetExistingNPCBots() +{ + return _existingBots; +} + +void BotDataMgr::GetNPCBotGuidsByOwner(std::vector &guids_vec, ObjectGuid owner_guid) +{ + ASSERT(AllBotsLoaded()); + + std::shared_lock lock(*GetLock()); + + for (NpcBotRegistry::const_iterator ci = _existingBots.cbegin(); ci != _existingBots.cend(); ++ci) + { + if (_botsData[(*ci)->GetEntry()]->owner == owner_guid.GetCounter()) + guids_vec.push_back((*ci)->GetGUID()); + } +} + +ObjectGuid BotDataMgr::GetNPCBotGuid(uint32 entry) +{ + ASSERT(AllBotsLoaded()); + + std::shared_lock lock(*GetLock()); + + for (NpcBotRegistry::const_iterator ci = _existingBots.cbegin(); ci != _existingBots.cend(); ++ci) + { + if ((*ci)->GetEntry() == entry) + return (*ci)->GetGUID(); + } + + return ObjectGuid::Empty; +} + +std::vector BotDataMgr::GetExistingNPCBotIds() +{ + ASSERT(AllBotsLoaded()); + + std::vector existing_ids; + existing_ids.reserve(_botsData.size()); + for (decltype(_botsData)::value_type const& bot_data_pair : _botsData) + existing_ids.push_back(bot_data_pair.first); + + return existing_ids; +} + +uint8 BotDataMgr::GetOwnedBotsCount(ObjectGuid owner_guid, uint32 class_mask) +{ + uint8 count = 0; + for (decltype(_botsData)::value_type const& bdata : _botsData) + if (bdata.second->owner == owner_guid.GetCounter() && (!class_mask || !!(class_mask & (1u << (_botsExtras[bdata.first]->bclass - 1))))) + ++count; + + return count; +} + +uint8 BotDataMgr::GetAccountBotsCount(uint32 account_id) +{ + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_NPCBOT_ACC_BOT_COUNT); + stmt->setUInt32(0, account_id); + + PreparedQueryResult result = CharacterDatabase.Query(stmt); + if (result) + return (*result)[0].GetUInt32(); + + return 0; +} + +uint8 BotDataMgr::GetLevelBonusForBotRank(uint32 rank) +{ + switch (rank) + { + case CREATURE_ELITE_RARE: + return 1; + case CREATURE_ELITE_ELITE: + return 2; + case CREATURE_ELITE_RAREELITE: + return 3; + default: + return 0; + } +} + +uint8 BotDataMgr::GetMinLevelForMapId(uint32 mapId) +{ + decltype(_wpMinSpawnLevelPerMapId)::const_iterator cit = _wpMinSpawnLevelPerMapId.find(mapId); + if (cit != _wpMinSpawnLevelPerMapId.cend()) + return cit->second; + + switch (mapId) + { + case 0: + case 1: + return 1; + case 530: + return 61; + case 571: + return 71; + default: + return 1; + } +} +uint8 BotDataMgr::GetMaxLevelForMapId(uint32 mapId) +{ + decltype(_wpMaxSpawnLevelPerMapId)::const_iterator cit = _wpMaxSpawnLevelPerMapId.find(mapId); + if (cit != _wpMaxSpawnLevelPerMapId.cend()) + return cit->second; + + switch (mapId) + { + case 0: + case 1: + return 60; + case 530: + return 70; + case 571: + return 80; + default: + return 80; + } +} + +uint8 BotDataMgr::GetMinLevelForBotClass(uint8 m_class) +{ + switch (m_class) + { + case BOT_CLASS_DEATH_KNIGHT: + return 55; + case BOT_CLASS_ARCHMAGE: + case BOT_CLASS_SPELLBREAKER: + case BOT_CLASS_NECROMANCER: + return 20; + case BOT_CLASS_DARK_RANGER: + return 40; + case BOT_CLASS_SPHYNX: + case BOT_CLASS_DREADLORD: + return 60; + default: + return 1; + } +} + +int32 BotDataMgr::GetBotBaseReputation(Creature const* bot, FactionEntry const* factionEntry) +{ + if (!factionEntry) + return 0; + + if (bot->IsNPCBotPet()) + bot = bot->GetBotPetAI()->GetPetsOwner(); + + uint32 raceMask = bot->GetFaction() == 14 ? 0 : bot->GetRaceMask(); + uint32 classMask = bot->GetClassMask(); + + int32 minRep = 42999; + for (uint8 i = 0; i < 4; ++i) + { + if (raceMask == 0) + minRep = std::min(minRep, factionEntry->ReputationBase[i]); + if ((factionEntry->ReputationRaceMask[i] & raceMask || (factionEntry->ReputationRaceMask[i] == 0 && factionEntry->ReputationClassMask[i] != 0)) && + (factionEntry->ReputationClassMask[i] & classMask || factionEntry->ReputationClassMask[i] == 0)) + { + return factionEntry->ReputationBase[i]; + } + } + + return std::min(minRep, 0); +} + +TeamId BotDataMgr::GetTeamIdForFaction(uint32 factionTemplateId) +{ + if (FactionTemplateEntry const* fte = sFactionTemplateStore.LookupEntry(factionTemplateId)) + { + if (fte->FactionGroup & FACTION_MASK_ALLIANCE) + return TEAM_ALLIANCE; + else if (fte->FactionGroup & FACTION_MASK_HORDE) + return TEAM_HORDE; + } + + return TEAM_NEUTRAL; +} + +uint32 BotDataMgr::GetTeamForFaction(uint32 factionTemplateId) +{ + switch (GetTeamIdForFaction(factionTemplateId)) + { + case TEAM_ALLIANCE: + return ALLIANCE; + case TEAM_HORDE: + return HORDE; + default: + return TEAM_OTHER; + } +} + +bool BotDataMgr::IsWanderNodeAvailableForBotFaction(WanderNode const* wp, uint32 factionTemplateId, bool teleport) +{ + if (!teleport) + { + if (wp->HasFlag(BotWPFlags::BOTWP_FLAG_MOVEMENT_IGNORES_FACTION)) + return true; + } + else + { + MapEntry const* mapEntry = sMapStore.LookupEntry(wp->GetMapId()); + if (!mapEntry->IsContinent()) + return false; + } + + switch (GetTeamIdForFaction(factionTemplateId)) + { + case TEAM_ALLIANCE: + return !wp->HasFlag(BotWPFlags::BOTWP_FLAG_HORDE_ONLY); + case TEAM_HORDE: + return !wp->HasFlag(BotWPFlags::BOTWP_FLAG_ALLIANCE_ONLY); + case TEAM_NEUTRAL: + return !wp->HasFlag(BotWPFlags::BOTWP_FLAG_ALLIANCE_OR_HORDE_ONLY); + default: + return true; + } +} + +WanderNode const* BotDataMgr::GetNextWanderNode(WanderNode const* curNode, WanderNode const* lastNode, Position const* fromPos, Creature const* bot, uint8 lvl, bool random) +{ + using NodeList = std::list; + + static auto node_viable = [](WanderNode const* wp, uint8 lvl) -> bool { + return (lvl + 2 >= wp->GetLevels().first && lvl <= wp->GetLevels().second); + }; + + uint32 faction = bot->GetFaction(); + + //Node got deleted (or forced)! Select close point and go from there + NodeList links; + if (curNode->GetLinks().empty() || random) + { + if (bot->IsInWorld() && !bot->GetMap()->IsBattlegroundOrArena()) + { + WanderNode::DoForAllMapWPs(curNode->GetMapId(), [&links, lvl = lvl, fac = faction, pos = fromPos](WanderNode const* wp) { + if (pos->GetExactDist2d(wp) < MAX_WANDER_NODE_DISTANCE && + IsWanderNodeAvailableForBotFaction(wp, fac, true) && node_viable(wp, lvl)) + links.push_back(wp); + }); + if (!links.empty()) + return links.size() == 1u ? links.front() : Trinity::Containers::SelectRandomContainerElement(links); + } + + //Select closest + WanderNode const* node_new = nullptr; + float mindist = 50000.0f; // Anywhere + WanderNode::DoForAllMapWPs(curNode->GetMapId(), [&node_new, &mindist, lvl = lvl, fac = faction, pos = fromPos](WanderNode const* wp) { + float dist = pos->GetExactDist2d(wp); + if (dist < mindist && + IsWanderNodeAvailableForBotFaction(wp, fac, false) && node_viable(wp, lvl)) + { + mindist = dist; + node_new = wp; + } + }); + return node_new; + } + + if (bot_ai::IsFlagCarrier(bot)) + { + NodeList flagDropNodes; + TeamId teamId = GetTeamIdForFaction(faction); + static const auto is_my_flag_drop_node = [](WanderNode const* dwp, TeamId tId) { + if (dwp->HasFlag(BotWPFlags::BOTWP_FLAG_BG_FLAG_DELIVER_TARGET)) + { + //must only select own faction drop node + return (!dwp->HasFlag(BotWPFlags::BOTWP_FLAG_ALLIANCE_OR_HORDE_ONLY) || + (tId == TEAM_ALLIANCE && dwp->HasFlag(BotWPFlags::BOTWP_FLAG_ALLIANCE_ONLY)) || + (tId == TEAM_HORDE && dwp->HasFlag(BotWPFlags::BOTWP_FLAG_HORDE_ONLY))); + } + return false; + }; + //check two levels of links, enough for: WSG + for (WanderNode const* dwp : curNode->GetLinks()) + { + if (is_my_flag_drop_node(dwp, teamId)) + flagDropNodes.push_back(dwp); + else + { + for (WanderNode const* dwpl : dwp->GetLinks()) + { + if (dwpl != curNode && is_my_flag_drop_node(dwpl, teamId)) + { + flagDropNodes.push_back(dwp); + break; + } + } + } + } + if (!flagDropNodes.empty()) + return flagDropNodes.size() == 1u ? flagDropNodes.front() : Trinity::Containers::SelectRandomContainerElement(flagDropNodes); + } + + for (WanderNode const* wp : curNode->GetLinks()) + { + if (IsWanderNodeAvailableForBotFaction(wp, faction, false) && node_viable(wp, lvl)) + links.push_back(wp); + } + if (links.size() > 1 && lastNode && !curNode->HasFlag(BotWPFlags::BOTWP_FLAG_CAN_BACKTRACK_FROM)) + links.remove(lastNode); + + //Overleveled or died: no viable nodes in reach, find one for teleport + if (links.empty()) + { + WanderNode::DoForAllWPs([&links, lvl = lvl, fac = faction](WanderNode const* wp) { + if (IsWanderNodeAvailableForBotFaction(wp, fac, true) && wp->HasFlag(BotWPFlags::BOTWP_FLAG_SPAWN) && node_viable(wp, lvl)) + links.push_back(wp); + }); + } + + ASSERT(!links.empty()); + return links.size() == 1u ? links.front() : Trinity::Containers::SelectRandomContainerElement(links); +} + +WanderNode const* BotDataMgr::GetClosestWanderNode(WorldLocation const* loc) +{ + float mindist = 50000.0f; + WanderNode const* closestNode = nullptr; + WanderNode::DoForAllMapWPs(loc->GetMapId(), [&mindist, &closestNode, loc = loc](WanderNode const* wp) { + float dist = wp->GetExactDist2d(loc); + if (dist < mindist) + { + mindist = dist; + closestNode = wp; + } + }); + + return closestNode; +} + +BotBankItemContainer const* BotDataMgr::GetBotBankItems(ObjectGuid playerGuid) +{ + decltype(_botStoredGearMap)::iterator mci = _botStoredGearMap.find(playerGuid); + return mci == _botStoredGearMap.cend() ? nullptr : &mci->second; +} + +Item* BotDataMgr::WithdrawBotBankItem(ObjectGuid playerGuid, ObjectGuid::LowType itemGuidLow) +{ + decltype(_botStoredGearMap)::iterator mci = _botStoredGearMap.find(playerGuid); + if (mci != _botStoredGearMap.cend()) + { + auto ici = std::find_if(std::cbegin(mci->second), std::cend(mci->second), [guidLow = itemGuidLow](Item const* item) { + return item->GetGUID().GetCounter() == guidLow; + }); + if (ici != mci->second.cend()) + { + Item* item = *ici; + mci->second.erase(ici); + return item; + } + } + + return nullptr; +} + +void BotDataMgr::DepositBotBankItem(ObjectGuid playerGuid, Item* item) +{ + _botStoredGearMap[playerGuid].insert(item); +} + +void BotDataMgr::SaveNpcBotStoredGear(ObjectGuid playerGuid, CharacterDatabaseTransaction trans) +{ + decltype(_botStoredGearMap)::iterator mci = _botStoredGearMap.find(playerGuid); + // we don't check if container is empty! + // we have to be able to erase items always + if (mci == _botStoredGearMap.cend()) + return; + + trans->PAppend("DELETE FROM characters_npcbot_gear_storage WHERE guid = {}", mci->first.GetCounter()); + for (Item* item : mci->second) + { + //order is important here + item->SaveToDB(trans); + item->DeleteFromInventoryDB(trans); + trans->PAppend("INSERT INTO characters_npcbot_gear_storage (guid, item_guid) VALUES ({}, {})", mci->first.GetCounter(), item->GetGUID().GetCounter()); + } +} + +class TC_GAME_API WanderingBotXpGainFormulaScript : public FormulaScript +{ + static constexpr float WANDERING_BOT_XP_GAIN_MULT = 10.0f; + +public: + WanderingBotXpGainFormulaScript() : FormulaScript("WanderingBotXpGainFormulaScript") {} + + void OnGainCalculation(uint32& gain, Player* /*player*/, Unit* unit) override + { + if (gain && unit->IsNPCBot() && unit->ToCreature()->IsWandererBot()) + gain *= WANDERING_BOT_XP_GAIN_MULT; + } +}; + +class TC_GAME_API BotDataMgrShutdownScript : public WorldScript +{ +public: + BotDataMgrShutdownScript() : WorldScript("BotDataMgrShutdownScript") {} + + void OnShutdown() override + { + botSpawnEvents.KillAllEvents(true); + for (auto& kv : botBGJoinEvents) + kv.second.KillAllEvents(true); + } +}; + +void AddSC_botdatamgr_scripts() +{ + new WanderingBotXpGainFormulaScript(); + new BotDataMgrShutdownScript(); +} + +#ifdef _MSC_VER +# pragma warning(pop) +#endif diff --git a/src/server/game/AI/NpcBots/botdatamgr.h b/src/server/game/AI/NpcBots/botdatamgr.h new file mode 100644 index 000000000..04805eee2 --- /dev/null +++ b/src/server/game/AI/NpcBots/botdatamgr.h @@ -0,0 +1,230 @@ +#ifndef _BOTDATAMGR_H +#define _BOTDATAMGR_H + +#include "botcommon.h" +#include "DatabaseEnvFwd.h" +#include "DBCEnums.h" + +#include +#include +#include +#include + +class BattlegroundQueue; +class Creature; +class Group; +class Item; +class Player; +class WanderNode; +class WorldLocation; + +struct EquipmentInfo; +struct CreatureTemplate; +struct FactionEntry; +struct GroupQueueInfo; +struct ItemTemplate; +struct Position; +struct PvPDifficultyEntry; + +enum LocaleConstant : uint8; + +constexpr float MIN_WANDER_NODE_DISTANCE = 50.0f; // VISIBILITY_DISTANCE_NORMAL * 0.5f; +constexpr float MAX_WANDER_NODE_DISTANCE = 800.0f; //SIZE_OF_GRIDS * 1.5f; + +enum NpcBotDataUpdateType +{ + NPCBOT_UPDATE_OWNER = 1, + NPCBOT_UPDATE_ROLES, + NPCBOT_UPDATE_SPEC, + NPCBOT_UPDATE_DISABLED_SPELLS, + NPCBOT_UPDATE_FACTION, + NPCBOT_UPDATE_EQUIPS, + NPCBOT_UPDATE_ERASE, + NPCBOT_UPDATE_TRANSMOG_ERASE, + NPCBOT_UPDATE_END +}; + +struct NpcBotData +{ + typedef std::set DisabledSpellsContainer; + + friend class BotDataMgr; + friend struct WanderingBotsGenerator; +public: + uint32 owner; + uint64 hire_time; + uint32 roles; + uint32 faction; + uint8 spec; + uint32 equips[BOT_INVENTORY_SIZE]; + DisabledSpellsContainer disabled_spells; + +private: + explicit NpcBotData(uint32 iroles, uint32 ifaction, uint8 ispec = 1) : owner(0), hire_time(0), roles(iroles), faction(ifaction), spec(ispec) + { + for (uint8 i = 0; i != BOT_INVENTORY_SIZE; ++i) + equips[i] = 0; + } + NpcBotData(NpcBotData const&); +}; + +struct NpcBotAppearanceData +{ + friend class BotDataMgr; + friend struct WanderingBotsGenerator; +public: + uint8 gender; + uint8 skin; + uint8 face; + uint8 hair; + uint8 haircolor; + uint8 features; +private: + explicit NpcBotAppearanceData() {} + NpcBotAppearanceData(NpcBotAppearanceData const&); +}; + +struct NpcBotExtras +{ + friend class BotDataMgr; + friend struct WanderingBotsGenerator; +public: + uint8 race; + uint8 bclass; +private: + explicit NpcBotExtras() {} + NpcBotExtras(NpcBotExtras const&); +}; + +struct NpcBotTransmogData +{ + friend class BotDataMgr; +public: + std::pair transmogs[BOT_TRANSMOG_INVENTORY_SIZE]; +private: + explicit NpcBotTransmogData() + { + for (uint8 i = 0; i != BOT_TRANSMOG_INVENTORY_SIZE; ++i) + transmogs[i] = { 0, -1 }; + } + NpcBotTransmogData(NpcBotTransmogData const&); +}; + +struct NpcBotStats +{ +public: + NpcBotStats() {} + + uint32 entry; + uint32 maxhealth; + uint32 maxpower; + uint32 strength; + uint32 agility; + uint32 stamina; + uint32 intellect; + uint32 spirit; + uint32 armor; + uint32 defense; + uint32 resHoly; + uint32 resFire; + uint32 resNature; + uint32 resFrost; + uint32 resShadow; + uint32 resArcane; + float blockPct; + float dodgePct; + float parryPct; + float critPct; + uint32 attackPower; + uint32 spellPower; + uint32 spellPen; + float hastePct; + float hitBonusPct; + uint32 expertise; + float armorPenPct; +}; + +typedef std::set NpcBotRegistry; + +struct BotBankItemCompare{ bool operator()(Item const* item1, Item const* item2) const; }; +typedef std::multiset BotBankItemContainer; + +constexpr uint8 ITEM_SORTING_LEVEL_STEP = 5; +constexpr uint8 LEVEL_STEPS = DEFAULT_MAX_LEVEL / ITEM_SORTING_LEVEL_STEP + 1; +typedef std::vector ItemIdVector; +typedef std::array ItemLeveledArr; +typedef std::array ItemPerSlot; +typedef std::array ItemPerBotClassMap; + +class BotDataMgr +{ + public: + static void Update(uint32 diff); + + static void LoadNpcBots(bool spawn = true); + static void LoadNpcBotGroupData(); + static void LoadNpcBotGearStorage(); + + static void DeleteOldLogs(); + + static void AddNpcBotData(uint32 entry, uint32 roles, uint8 spec, uint32 faction); + static NpcBotData const* SelectNpcBotData(uint32 entry); + static void UpdateNpcBotData(uint32 entry, NpcBotDataUpdateType updateType, void* data = nullptr); + static void UpdateNpcBotDataAll(uint32 playerGuid, NpcBotDataUpdateType updateType, void* data = nullptr); + static void SaveNpcBotStats(NpcBotStats const* stats); + + static NpcBotAppearanceData const* SelectNpcBotAppearance(uint32 entry); + static NpcBotExtras const* SelectNpcBotExtras(uint32 entry); + + static NpcBotTransmogData const* SelectNpcBotTransmogs(uint32 entry); + static void UpdateNpcBotTransmogData(uint32 entry, uint8 slot, uint32 item_id, int32 fake_id, bool update_db = true); + static void ResetNpcBotTransmogData(uint32 entry, bool update_db = true); + + static bool AllBotsLoaded(); + + static void RegisterBot(Creature const* bot); + static void UnregisterBot(Creature const* bot); + static Creature const* FindBot(uint32 entry); + static Creature const* FindBot(std::string_view name, LocaleConstant loc, std::vector const* not_ids = nullptr); + static NpcBotRegistry const& GetExistingNPCBots(); + static void GetNPCBotGuidsByOwner(std::vector &guids_vec, ObjectGuid owner_guid); + static ObjectGuid GetNPCBotGuid(uint32 entry); + static std::vector GetExistingNPCBotIds(); + static uint8 GetOwnedBotsCount(ObjectGuid owner_guid, uint32 class_mask = 0); + static uint8 GetAccountBotsCount(uint32 account_id); + + static void DespawnWandererBot(uint32 entry); + static void LoadWanderMap(bool reload = false); + static void GenerateWanderingBots(); + static bool GenerateBattlegroundBots(Player const* groupLeader, Group const* group, BattlegroundQueue* queue, PvPDifficultyEntry const* bracketEntry, GroupQueueInfo const* gqinfo); + static void CreateWanderingBotsSortedGear(); + static ItemPerBotClassMap const& GetWanderingBotsSortedGearMap(); + static Item* GenerateWanderingBotItem(uint8 slot, uint8 botclass, uint8 level, std::function&& check); + static bool GenerateWanderingBotItemEnchants(Item* item, uint8 slot, uint8 spec); + static CreatureTemplate const* GetBotExtraCreatureTemplate(uint32 entry); + static EquipmentInfo const* GetBotEquipmentInfo(uint32 entry); + + static uint8 GetLevelBonusForBotRank(uint32 rank); + static uint8 GetMinLevelForMapId(uint32 mapId); + static uint8 GetMaxLevelForMapId(uint32 mapId); + static uint8 GetMinLevelForBotClass(uint8 m_class); + static int32 GetBotBaseReputation(Creature const* bot, FactionEntry const* factionEntry); + static TeamId GetTeamIdForFaction(uint32 factionTemplateId); + static uint32 GetTeamForFaction(uint32 factionTemplateId); + static bool IsWanderNodeAvailableForBotFaction(WanderNode const* wp, uint32 factionTemplateId, bool teleport); + static WanderNode const* GetNextWanderNode(WanderNode const* curNode, WanderNode const* lastNode, Position const* fromPos, Creature const* bot, uint8 lvl, bool random); + static WanderNode const* GetClosestWanderNode(WorldLocation const* loc); + + static BotBankItemContainer const* GetBotBankItems(ObjectGuid playerGuid); + static Item* WithdrawBotBankItem(ObjectGuid playerGuid, ObjectGuid::LowType itemGuidLow); + static void DepositBotBankItem(ObjectGuid playerGuid, Item* item); + static void SaveNpcBotStoredGear(ObjectGuid playerGuid, CharacterDatabaseTransaction trans); + + static std::shared_mutex* GetLock(); + + private: + BotDataMgr() {} + BotDataMgr(BotDataMgr const&); +}; + +#endif diff --git a/src/server/game/AI/NpcBots/botdpstracker.cpp b/src/server/game/AI/NpcBots/botdpstracker.cpp new file mode 100644 index 000000000..6a87eb155 --- /dev/null +++ b/src/server/game/AI/NpcBots/botdpstracker.cpp @@ -0,0 +1,133 @@ +#include "botdpstracker.h" +#include "Unit.h" + +/* +Name: bot_dps_tracker +%Complete: 100 +Comment: dps taken tracker for NPCBot system by Trickerer (onlysuffering@gmail.com) +DPS trackers may collect data from different bot owners if in party but this overdoing has no significance whatsoever +*/ + +enum DPSTrackerConstants : uint32 +{ + DPS_UPDATE_TIMER = 500, //recalculate dps every x ms + MAX_DPS_TRACK_TIME = 5000, //track damage taken for last x ms + DPS_INACTIVE_TIMER = 5000, //reset if combat not active for botparty for x ms + //maximum tracked damage taken periods of DPS_UPDATE_TIMER during MAX_DPS_TRACK_TIME + MAX_DAMAGES = MAX_DPS_TRACK_TIME/DPS_UPDATE_TIMER +}; + +DPSTracker::DPSTracker() +{ + _updateTimer = 0; + _inactiveTimer = 0; + _trackTimer = 0; + _active = false; +} + +DPSTracker::~DPSTracker() +{ + for (DamageTakenMap::const_iterator itr = _damages.begin(); itr != _damages.end(); ++itr) + delete[] itr->second; + + _damages.clear(); + _DPSes.clear(); +} + +void DPSTracker::Update(uint32 diff) +{ + if (_active) + { + _inactiveTimer += diff; + _updateTimer += diff; + _trackTimer += diff; + + if (_inactiveTimer >= DPS_INACTIVE_TIMER) + { + _Reset(); + } + else if (_updateTimer >= DPS_UPDATE_TIMER) + { + _updateTimer -= DPS_UPDATE_TIMER; + _Release(); + } + } +} + +void DPSTracker::_Reset() +{ + if (_active) + { + _active = false; + + for (DamageTakenMap::const_iterator itr = _damages.begin(); itr != _damages.end(); ++itr) + for (uint8 i = 0; i != MAX_DAMAGES; ++i) + itr->second[i] = 0; + for (DPSTakenMap::iterator itr = _DPSes.begin(); itr != _DPSes.end(); ++itr) + itr->second = 0; + + _updateTimer = 0; + _inactiveTimer = 0; + _trackTimer = 0; + } +} + +void DPSTracker::_Release() +{ + for (DamageTakenMap::const_iterator itr = _damages.begin(); itr != _damages.end(); ++itr) + { + uint32* dmgs = itr->second; + uint32 total_damage = 0; + for (uint8 i = 0; i != MAX_DAMAGES; ++i) + total_damage += dmgs[i]; + + _DPSes[itr->first] = uint32(total_damage / (0.001f * std::max(1 * IN_MILLISECONDS, std::min(_trackTimer, MAX_DPS_TRACK_TIME)))); + //TC_LOG_ERROR("entities.player", "DPSTracker::Release(): guidlow = {}, time = {}, tick damage {}, total {}, dps = {}", + // itr->first, _trackTimer, dmgs[0], total_damage, _DPSes[itr->first]); + + //shift + for (int8 i = MAX_DAMAGES-1; i > 0; --i) + dmgs[i] = dmgs[i-1]; + dmgs[0] = 0; + } +} + +void DPSTracker::_AccumulateDamage(uint64 guid, uint32 damage) +{ + DamageTakenMap::const_iterator itr = _damages.find(guid); + + if (itr == _damages.end()) + { + uint32* dmgarray = new uint32[MAX_DAMAGES]; + memset(dmgarray, 0, sizeof(uint32)*MAX_DAMAGES); + + dmgarray[0] = damage; + + _damages[guid] = dmgarray; + return; + } + + itr->second[0] += damage; +} +//victim is bot owner, bot, party player or party bot; checked in Unit::DealDamage() +void DPSTracker::TrackDamage(Unit const* victim, uint32 damage) +{ + //TC_LOG_ERROR("entities.player", "DPSTracker::OnDamage(): on {}, damage {}", victim->GetName(), damage); + + _SetActive(); + _AccumulateDamage(victim->GetGUID().GetRawValue(), damage); +} + +void DPSTracker::_SetActive() +{ + _inactiveTimer = 0; + if (!_active) + _active = true; +} + +uint32 DPSTracker::GetDPSTaken(uint64 guid) const +{ + DPSTakenMap::const_iterator itr = _DPSes.find(guid); + //TC_LOG_ERROR("entities.player", "DPSTracker::GetDPSTaken(): from {}, damage {}", guid, itr != _DPSes.end() ? itr->second : 0); + return itr != _DPSes.end() ? itr->second : 0; +} diff --git a/src/server/game/AI/NpcBots/botdpstracker.h b/src/server/game/AI/NpcBots/botdpstracker.h new file mode 100644 index 000000000..c061ff5d1 --- /dev/null +++ b/src/server/game/AI/NpcBots/botdpstracker.h @@ -0,0 +1,38 @@ +#ifndef _BOT_DPSTRACKER_H +#define _BOT_DPSTRACKER_H + +#include "Define.h" + +#include + +class Unit; + +class DPSTracker +{ + public: + DPSTracker(); + ~DPSTracker(); + + void Update(uint32 diff); + + void TrackDamage(Unit const* victim, uint32 damage); + uint32 GetDPSTaken(uint64 guid) const; + + private: + void _Reset(); + void _Release(); + void _AccumulateDamage(uint64 guid, uint32 damage); + void _SetActive(); + + typedef std::unordered_map DamageTakenMap; + typedef std::unordered_map DPSTakenMap; + DamageTakenMap _damages; + DPSTakenMap _DPSes; + + uint32 _updateTimer; + uint32 _inactiveTimer; + uint32 _trackTimer; + bool _active; +}; + +#endif diff --git a/src/server/game/AI/NpcBots/botdump.cpp b/src/server/game/AI/NpcBots/botdump.cpp new file mode 100644 index 000000000..7c3d88a4d --- /dev/null +++ b/src/server/game/AI/NpcBots/botdump.cpp @@ -0,0 +1,1024 @@ +/* + * NpcBots Data Migration System by Trickerer (onlysuffering@gmail.com) + * + * Last update: *09 Apr 2023* + * + * Saved data: + * 1) `characters_npcbot` - spawned bots' BOT info + * 2) `characters_npcbot_transmog` - bots' transmogs + * 3) `item_instance` - bots' equipment + * 4) `creature` - bot spawns + * + * Make sure you have bots installed, or you are in for an unpleasant surprise. + */ + +#include "botdump.h" +#include "botdatamgr.h" +#include "DatabaseEnv.h" +#include "Log.h" +#include "ObjectMgr.h" + +#include + +class BotStringTransaction +{ +public: + BotStringTransaction() : _buf() {} + + void Append(std::string const& sql) + { + _buf += sql; + } + + std::string const& GetBuffer() const + { + return _buf; + } + +private: + std::string _buf; +}; + +enum ImportDataTableType : uint8 +{ + TABLE_TYPE_CHARACTERS_NPCBOT = 0, + TABLE_TYPE_NPCBOT_TRANSMOG = 1, + TABLE_TYPE_ITEM_INSTANCE = 2, + TABLE_TYPE_CREATURE = 3, + + IMPORT_TABLES_COUNT = 4, + IMPORT_TABLE_INVALID = 255 +}; + +struct TableImportData +{ + std::string const name; + std::string const fieldsStr; + uint32 paramsCount; + size_t guidOffsetBegin; + size_t guidOffsetEnd; +}; + +TableImportData TableImportDatas[IMPORT_TABLES_COUNT] = +{ + { "`characters_npcbot` ", + "(" + //0 1 2 3 4 5 6 7 8 9 + "`entry`,`owner`,`roles`,`spec`,`faction`,`spells_disabled`,`equipMhEx`,`equipOhEx`,`equipRhEx`,`equipHead`," + //10 11 12 13 14 15 16 17 + "`equipShoulders`,`equipChest`,`equipWaist`,`equipLegs`,`equipFeet`,`equipWrist`,`equipHands`,`equipBack`," + //18 19 20 21 22 23 + "`equipBody`,`equipFinger1`,`equipFinger2`,`equipTrinket1`,`equipTrinket2`,`equipNeck`" + ") VALUES ", 24, 6, 23 }, + + { "`characters_npcbot_transmog` ", + "(" + //0 1 2 3 + "`entry`,`slot`,`item_id`,`fake_id`" + ") VALUES ", 4, 0, 0 }, + + { "`item_instance` ", + "(" + //0 1 2 3 4 5 6 + "`creatorGuid`,`giftCreatorGuid`,`count`,`duration`,`charges`,`flags`,`enchantments`," + //7 8 9 10 11 12 13 + "`randomPropertyId`,`durability`,`playedTime`,`text`,`guid`,`itemEntry`,`owner_guid`" + ") VALUES ", 14, 11, 11 }, + + { "`creature` ", + "(" + //0 1 2 3 4 5 6 7 8 9 10 + "`guid`,`id`,`map`,`spawnMask`,`phaseMask`,`position_x`,`position_y`,`position_z`,`orientation`,`curhealth`,`curmana`" + ") VALUES ", 11, 0, 0 } +}; + +ImportDataTableType GetImportDataTableType(std::string const& name) +{ + for (uint8 i = TABLE_TYPE_CHARACTERS_NPCBOT; i != IMPORT_TABLES_COUNT; ++i) + { + //TC_LOG_ERROR("scripts", "import: GetImportDataTableType"); + if (!TableImportDatas[i].name.compare(name)) + return ImportDataTableType(i); + } + + return IMPORT_TABLE_INVALID; +} + +inline uint8 GetImportLineParamsCount(std::string const& line) +{ + static std::string const ParamSeparator = "','"; + uint8 count = 0; + size_t pos = line.find(ParamSeparator); + while (pos != std::string::npos) + { + //TC_LOG_ERROR("scripts", "import: GetImportLineParamsCount"); + ++count; + pos = line.find(ParamSeparator, pos + 1); + } + return count + 1; //separators count is params count - 1 +} + +inline void FixNULLfields(std::string& line) +{ + static std::string const NullString = "'NULL'"; + size_t pos = line.find(NullString); + while (pos != std::string::npos) + { + //TC_LOG_ERROR("scripts", "import: FixNULLfields"); + line.replace(pos, NullString.length(), "NULL"); + pos = line.find(NullString); + } +} + +std::set ExistingNPCBots; +std::set ExistingNPCBotTransmogs; + +template +void StringToVal(std::string const& /*line*/, T& /*v*/, size_t /*begin_pos*/, size_t /*end_pos*/) +{ + TC_LOG_ERROR("scripts", "StringToVal misuse"); +} +/* +template<> +void StringToVal(std::string const& line, float& v, size_t begin_pos, size_t end_pos) +{ + v = atof(line.substr(begin_pos, end_pos).c_str()); + TC_LOG_ERROR("scripts", "import: StringToVal returned {}", v); +} +*/ +template<> +void StringToVal(std::string const& line, uint32& v, size_t begin_pos, size_t end_pos) +{ + std::string subst = line.substr(begin_pos, end_pos - begin_pos).c_str(); + v = (uint32)atoi(subst.c_str()); + //TC_LOG_ERROR("scripts", "import: StringToVal returned {} ({} to {}: {})", + // v, uint32(begin_pos), uint32(end_pos), subst.c_str()); +} + +template +std::string ValToString(T /*v*/) +{ + TC_LOG_ERROR("scripts", "ValToString misuse"); + return ""; +} +template<> +std::string ValToString(uint32 v) +{ + std::ostringstream stv; + stv << v; + return stv.str(); +} + +template +bool ExtractValueFromString(std::string const& line, T& v, size_t offset, std::string const sep = "'") +{ + uint32 sepNum = 0; + + size_t begin_pos = 0, end_pos = 0; + + size_t pos = line.find(sep); + while (pos != std::string::npos) + { + ++sepNum; + if (begin_pos == 0 && !((sepNum-1) % 2) && ((sepNum-1) / 2) == offset) + { + begin_pos = pos + 1; + //TC_LOG_ERROR("scripts", "import: ExtractValueFromString begin_pos {}", uint32(begin_pos)); + } + else if (end_pos == 0 && ((sepNum-1) % 2) && ((sepNum-1) / 2) == offset) + { + end_pos = pos; + //TC_LOG_ERROR("scripts", "import: ExtractValueFromString end_pos {}", uint32(end_pos)); + } + + if (begin_pos && end_pos) + break; + + pos = line.find(sep, pos + 1); + } + + if (begin_pos && end_pos) + { + StringToVal(line, v, begin_pos, end_pos); + return true; + } + + return false; +} + +typedef std::map ReGuidMap; +ReGuidMap itemReguidMap; + +inline bool ReGuidBotEquip(std::string& line, size_t ne_guid_offset) +{ + /* + INSERT INTO `characters_npcbot` (`entry`,`owner`,`roles`,`spec`,`faction`,`spell + s_disabled`,`equipMhEx`,`equipOhEx`,`equipRhEx`,`equipHead`,`equipShoulders`,`eq + uipChest`,`equipWaist`,`equipLegs`,`equipFeet`,`equipWrist`,`equipHands`,`equipB + ack`,`equipBody`,`equipFinger1`,`equipFinger2`,`equipTrinket1`,`equipTrinket2`,` + equipNeck`) VALUES ('70027','2204','19','3','35','NULL','4305063','4305032','0','0 + ','4305069','4237321','4237326','4305049','4305067','4305055','0','4305054','430 + 5029','4303835','0','0','0','0'); + */ + static const std::string ne_vals_sep = "('"; + static const std::string ne_sep = "'"; + + bool reguidDone = false; + uint32 sepNum = 0; + size_t begin_pos = 0, end_pos = 0; + + size_t pos = line.find(ne_vals_sep); + ASSERT(pos != std::string::npos); + pos = line.find(ne_sep); + ASSERT(pos != std::string::npos); + while (pos != std::string::npos) + { + ++sepNum; + //TC_LOG_ERROR("scripts", "import: ReGuidBotEquip sepNum {}", sepNum); + if (begin_pos == 0 && !((sepNum-1) % 2) && ((sepNum-1) / 2) == ne_guid_offset) + { + begin_pos = pos + 1; + //TC_LOG_ERROR("scripts", "import: ReGuidBotEquip begin_pos {}", uint32(begin_pos)); + } + else if (end_pos == 0 && ((sepNum-1) % 2) && ((sepNum-1) / 2) == ne_guid_offset) + { + end_pos = pos; + //TC_LOG_ERROR("scripts", "import: ReGuidBotEquip end_pos {}", uint32(end_pos)); + } + + if (begin_pos && end_pos) + { + uint32 guidVal; + StringToVal(line, guidVal, begin_pos, end_pos); + if (!guidVal) + { + //ignore no equip + if (line.substr(begin_pos, end_pos - begin_pos) == "0") + return true; + + TC_LOG_ERROR("scripts", "import: ReGuidBotEquip no guidVal from {} offset {}!", + line.substr(begin_pos, end_pos - begin_pos), uint32(ne_guid_offset)); + break; + } + + if (!itemReguidMap.contains(guidVal)) + { + TC_LOG_ERROR("scripts", "import: ReGuidBotEquip reguid value not found for {}!", guidVal); + break; + } + + uint32 neVal = itemReguidMap[guidVal]; + //TC_LOG_ERROR("scripts", "import: ReGuidBotEquip replacing {} with {}", guidVal, neVal); + line.replace(begin_pos, end_pos - begin_pos, ValToString(neVal)); + reguidDone = true; + break; + } + + pos = line.find(ne_sep, pos + 1); + } + + return reguidDone; +} +inline bool ReGuidBotEquips(std::string& line) +{ + static const size_t ne_guid_offset_s = TableImportDatas[TABLE_TYPE_CHARACTERS_NPCBOT].guidOffsetBegin; + static const size_t ne_guid_offset_e = TableImportDatas[TABLE_TYPE_CHARACTERS_NPCBOT].guidOffsetEnd; + //TC_LOG_ERROR("scripts", "import: ReGuidBotEquips ne_guid_offset_s {} ne_guid_offset_e {}", uint32(ne_guid_offset_s), uint32(ne_guid_offset_e)); + + for (size_t i = ne_guid_offset_s; i <= ne_guid_offset_e; ++i) + { + if (!ReGuidBotEquip(line, i)) + return false; + } + + return true; +} + +inline bool ReGuidItemInstance(std::string& line, uint32& nextGuid) +{ + /* + INSERT INTO `item_instance` (`creatorGuid`,`giftCreatorGuid`,`count`,`duration`, + `charges`,`flags`,`enchantments`,`randomPropertyId`,`durability`,`playedTime`,`t + ext`,`guid`,`itemEntry`,`owner_guid`) VALUES ('0','0','1','0','0 0 0 0 0 ','1',' + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ','0','9 + 0','7200','','4296510','42490','0'),('0','0','1','0','0 0 0 0 0 ','0','0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ','0','100','0',' + ','4303949','48468','0'), etc. + */ + static const size_t ii_guid_offset = TableImportDatas[TABLE_TYPE_ITEM_INSTANCE].guidOffsetBegin; + static const std::string ii_vals_sep = "('"; + static const std::string ii_sep = "'"; + + //TC_LOG_ERROR("scripts", "import: ReGuidItemInstance ii_guid_offset {}", uint32(ii_guid_offset)); + + size_t pos1 = line.find(ii_vals_sep); + ASSERT(pos1 != std::string::npos); + while (pos1 != std::string::npos) + { + //TC_LOG_ERROR("scripts", "cur pos1 {}", int32(pos1)); + bool reguidDone = false; + uint32 sepNum = 0; + size_t begin_pos = 0, end_pos = 0; + size_t pos2 = line.find(ii_sep, pos1 + 1); + ASSERT(pos2 != std::string::npos); + while (pos2 != std::string::npos) + { + ++sepNum; + //TC_LOG_ERROR("scripts", "cur pos2 {} sep {} s {} e {}, cval {}", + // int32(pos2), sepNum, uint32(begin_pos), uint32(end_pos), uint32((sepNum-1) / 2)); + if (begin_pos == 0 && !((sepNum-1) % 2) && ((sepNum-1) / 2) == ii_guid_offset) + { + begin_pos = pos2 + 1; + //TC_LOG_ERROR("scripts", "import: ReGuidItemInstance begin_pos {}", uint32(begin_pos)); + } + else if (end_pos == 0 && ((sepNum-1) % 2) && ((sepNum-1) / 2) == ii_guid_offset) + { + end_pos = pos2; + //TC_LOG_ERROR("scripts", "import: ReGuidItemInstance end_pos {}", uint32(end_pos)); + } + + if (begin_pos && end_pos) + { + uint32 guidVal; + StringToVal(line, guidVal, begin_pos, end_pos); + if (!guidVal) + { + TC_LOG_ERROR("scripts", "import: ReGuidItemInstance no guidVal from {}!", + line.substr(begin_pos, end_pos - begin_pos)); + return false; + } + //this is not checked at dump save + if (!itemReguidMap.contains(guidVal)) + itemReguidMap[guidVal] = nextGuid; + else + TC_LOG_ERROR("scripts", "import: ReGuidItemInstance item guid {} was already reguided to {}. Saved dump contains duplicate item guids - you'll have to fix them manually, proceeding anyways...", + guidVal, itemReguidMap[guidVal]); + + //TC_LOG_ERROR("scripts", "import: ReGuidItemInstance replacing {} with {}", guidVal, nextGuid); + line.replace(begin_pos, end_pos - begin_pos, ValToString(nextGuid)); + + ++nextGuid; + reguidDone = true; + break; + } + + pos2 = line.find(ii_sep, pos2 + 1); + } + + if (!reguidDone) + { + TC_LOG_ERROR("scripts", "import: ReGuidItemInstance reguid failed for string! Was:\n{}", line); + return false; + } + + pos1 = line.find(ii_vals_sep, pos1 + 1); + } + + return true; +} + +inline bool ReGuidCreature(std::string& line) +{ + /* + INSERT INTO `item_instance` (`guid`,`id`,`map`,`spawnMask`,`phaseMask`,`position + _x`,`position_y`,`position_z`,`orientation`,`curhealth`,`curmana` VALUES ('12561 + 3','30102','571','0','0','1','1','0','0','5735.7','-3037.58','296.551','0.558505 + ','120','0','0','1','0','0','0','0','0','','0'); + */ + static const size_t cr_guid_offset = TableImportDatas[TABLE_TYPE_CREATURE].guidOffsetBegin; + static const std::string cr_vals_sep = "('"; + static const std::string cr_sep = "'"; + + bool reguidDone = false; + uint32 sepNum = 0; + size_t begin_pos = 0, end_pos = 0; + + size_t pos = line.find(cr_vals_sep); + ASSERT(pos != std::string::npos); + pos = line.find(cr_sep); + ASSERT(pos != std::string::npos); + while (pos != std::string::npos) + { + ++sepNum; + //TC_LOG_ERROR("scripts", "import: ReGuidCreature sepNum {}", sepNum); + if (begin_pos == 0 && !((sepNum-1) % 2) && ((sepNum-1) / 2) == cr_guid_offset) + { + begin_pos = pos + 1; + //TC_LOG_ERROR("scripts", "import: ReGuidCreature begin_pos {}", uint32(begin_pos)); + } + else if (end_pos == 0 && ((sepNum-1) % 2) && ((sepNum-1) / 2) == cr_guid_offset) + { + end_pos = pos; + //TC_LOG_ERROR("scripts", "import: ReGuidCreature end_pos {}", uint32(end_pos)); + } + + if (begin_pos && end_pos) + { + uint32 guidVal; + StringToVal(line, guidVal, begin_pos, end_pos); + if (!guidVal) + { + TC_LOG_ERROR("scripts", "import: ReGuidCreature no guidVal from {}!", + line.substr(begin_pos, end_pos - begin_pos)); + return false; + } + + uint32 nextGuid = sObjectMgr->GenerateCreatureSpawnId(); + //TC_LOG_ERROR("scripts", "import: ReGuidCreature replacing {} with {}", guidVal, nextGuid); + line.replace(begin_pos, end_pos - begin_pos, ValToString(nextGuid)); + + reguidDone = true; + break; + } + + pos = line.find(cr_sep, pos + 1); + } + + return reguidDone; +} + +BotDataDumpResult NPCBotsDump::Load(std::string const& file) +{ + std::ifstream input(file.c_str()); + if (!input) + return BOT_DUMP_FAIL_FILE_NOT_EXIST; + + return LoadDump(input); +} + +BotDataDumpResult NPCBotsDump::LoadDump(std::ifstream& input) +{ + //prepare data for existing entries checks + //bot entry + //first - from `characters_npcbot` + QueryResult result = CharacterDatabase.Query("SELECT `entry` FROM `characters_npcbot`"); + Field* fields; + if (result) + { + fields = result->Fetch(); + do + { + ExistingNPCBots.insert((*fields).GetUInt32()); + } while (result->NextRow()); + } + //second - join with entries from `creature` table (who knows what you have spawned there before you needed to import bots eh?) + result = WorldDatabase.Query("SELECT `id` FROM `creature` WHERE `id` IN (SELECT `entry` FROM `creature_template_npcbot_extras`) ORDER BY `id`"); + if (result) + { + fields = result->Fetch(); + do + { + ExistingNPCBots.insert((*fields).GetUInt32()); + } while (result->NextRow()); + } + //bot transmogs + result = CharacterDatabase.Query("SELECT `entry` FROM `characters_npcbot_transmog`"); + if (result) + { + fields = result->Fetch(); + do + { + ExistingNPCBotTransmogs.insert((*fields).GetUInt32()); + } while (result->NextRow()); + } + //item guid + result = CharacterDatabase.Query("SELECT MAX(`guid`) FROM `item_instance`"); + ASSERT(result); + fields = result->Fetch(); + static uint32 NextItemGuid = (*fields).GetUInt32() + 1; + //TC_LOG_ERROR("scripts", "import: NextItemGuid {}", NextItemGuid); + + CharacterDatabaseTransaction ctrans = CharacterDatabase.BeginTransaction(); + WorldDatabaseTransaction wtrans = WorldDatabase.BeginTransaction(); + + std::list ctransStrings; + std::list wtransStrings; + + ImportDataTableType curImportDataTableType = IMPORT_TABLE_INVALID; + std::string curFieldsStr; + std::string curExecLine; + uint8 curParamCount = 0; + + std::string line; + uint32 lineNum = 0; + while (std::getline(input, line)) + { + ++lineNum; + + size_t nw_pos = line.find_first_not_of(" \t\n\r\7"); + if (nw_pos == std::string::npos) + continue; + + static std::string const NoteLine = "IMPORTANT NOTE:"; + if (line.substr(nw_pos, NoteLine.size()) == NoteLine) + continue; + + if (curFieldsStr.empty()) + { + std::string table_name = line.substr(line.find_first_of('`')); + //TC_LOG_ERROR("scripts", "import: found table {}", table_name); + curImportDataTableType = GetImportDataTableType(table_name); + switch (curImportDataTableType) + { + case TABLE_TYPE_CHARACTERS_NPCBOT: + case TABLE_TYPE_NPCBOT_TRANSMOG: + case TABLE_TYPE_ITEM_INSTANCE: + case TABLE_TYPE_CREATURE: + curFieldsStr = TableImportDatas[curImportDataTableType].fieldsStr; + break; + default: + TC_LOG_ERROR("scripts", "import: unknown table {} at line {}", table_name, lineNum); + return BOT_DUMP_FAIL_FILE_CORRUPTED; + } + + curExecLine += line; + continue; + } + else if (curParamCount == 0) + { + if (line.compare(curFieldsStr)) + return BOT_DUMP_FAIL_FILE_CORRUPTED; + + curParamCount = TableImportDatas[curImportDataTableType].paramsCount; + //TC_LOG_ERROR("scripts", "import: param count {}", uint32(curParamCount)); + + curExecLine += line; + continue; + } + else + { + if (GetImportLineParamsCount(line) != curParamCount) + { + TC_LOG_ERROR("scripts", "import: invalid param count {} at line {}", uint32(curParamCount), lineNum); + return BOT_DUMP_FAIL_FILE_CORRUPTED; + } + + //check values conflicts, abort on existing values + size_t checkOffset = 0; + bool needCheckVal = false; + switch (curImportDataTableType) + { + case TABLE_TYPE_CHARACTERS_NPCBOT: + //entry + //checkOffset = 0; + needCheckVal = true; + break; + case TABLE_TYPE_NPCBOT_TRANSMOG: + //entry + //checkOffset = 0; + needCheckVal = true; + break; + default: + break; + } + uint32 checkVal; + if (needCheckVal && !ExtractValueFromString(line, checkVal, checkOffset)) + { + TC_LOG_ERROR("scripts", "import: unable to extract value from line {} at offset {} type {}", + lineNum, uint32(checkOffset), uint32(curImportDataTableType)); + return BOT_DUMP_FAIL_FILE_CORRUPTED; + } + switch (curImportDataTableType) + { + case TABLE_TYPE_CHARACTERS_NPCBOT: + if (ExistingNPCBots.find(checkVal) != ExistingNPCBots.end()) + { + TC_LOG_ERROR("scripts", "import: NPCBot id {} already exists in `characters_npcbot` or `creature` table! Aborting", checkVal); + return BOT_DUMP_FAIL_DATA_OCCUPIED; + } + break; + case TABLE_TYPE_NPCBOT_TRANSMOG: + if (ExistingNPCBotTransmogs.find(checkVal) != ExistingNPCBotTransmogs.end()) + { + TC_LOG_ERROR("scripts", "import: NPCBot id {} already exists in `characters_npcbot_transmog` table! Aborting", checkVal); + return BOT_DUMP_FAIL_DATA_OCCUPIED; + } + break; + default: + break; + } + + curExecLine += line; + + //multi-line import + if (line[line.size()-1] == ',') + continue; + else if (line[line.size()-1] != ';') + { + TC_LOG_ERROR("scripts", "import: unexpected line ending at line {}", lineNum); + return BOT_DUMP_FAIL_FILE_CORRUPTED; + } + } + + //reguid if needed + switch (curImportDataTableType) + { + case TABLE_TYPE_ITEM_INSTANCE: + if (!ReGuidItemInstance(curExecLine, NextItemGuid)) + { + TC_LOG_ERROR("scripts", "import: unable to reguid item instance at line {}!", lineNum); + return BOT_DUMP_FAIL_FILE_CORRUPTED; + } + if (!ReGuidBotEquips(ctransStrings.back())) + { + TC_LOG_ERROR("scripts", "import: unable to reguid bot equips at line {}:\n{}!", lineNum, ctransStrings.back()); + return BOT_DUMP_FAIL_FILE_CORRUPTED; + } + break; + case TABLE_TYPE_CREATURE: + if (!ReGuidCreature(curExecLine)) + { + TC_LOG_ERROR("scripts", "import: unable to reguid creature at line {}!", lineNum); + return BOT_DUMP_FAIL_FILE_CORRUPTED; + } + break; + default: + break; + } + + switch (curImportDataTableType) + { + case TABLE_TYPE_CHARACTERS_NPCBOT: + case TABLE_TYPE_NPCBOT_TRANSMOG: + case TABLE_TYPE_ITEM_INSTANCE: + //TC_LOG_ERROR("scripts", "import: adding to chars DB"); + ctransStrings.push_back(curExecLine); + //ctrans->Append(curExecLine.c_str()); + break; + case TABLE_TYPE_CREATURE: + //TC_LOG_ERROR("scripts", "import: adding to world DB"); + wtransStrings.push_back(curExecLine); + //wtrans->Append(curExecLine.c_str()); + break; + default: + ASSERT(false); + } + + curParamCount = 0; + curExecLine.clear(); + curFieldsStr.clear(); + curImportDataTableType = IMPORT_TABLE_INVALID; + } + + //check incomplete last query + if (!curExecLine.empty() || !curFieldsStr.empty() || curParamCount > 0 || + curImportDataTableType != IMPORT_TABLE_INVALID) + { + TC_LOG_ERROR("scripts", "import: unexpected file ending, incomplete query {}, fields {}, type {}!", + curExecLine, curFieldsStr, uint32(curImportDataTableType)); + + return BOT_DUMP_FAIL_FILE_CORRUPTED; + } + + //Replace all 'NULL' values as they are saved in dump with plain NULL + for (std::list::iterator ci = ctransStrings.begin(); ci != ctransStrings.end(); ++ci) + FixNULLfields(*ci); + for (std::list::iterator wi = wtransStrings.begin(); wi != wtransStrings.end(); ++wi) + FixNULLfields(*wi); + + //TC_LOG_ERROR("scripts", "import: charDb execLines:"); + for (std::list::const_iterator ci = ctransStrings.begin(); ci != ctransStrings.end(); ++ci) + { + //TC_LOG_ERROR("scripts", "{}", (*ci)); + ctrans->Append((*ci).c_str()); + } + //TC_LOG_ERROR("scripts", "import: worldDb execLines:"); + for (std::list::const_iterator wi = wtransStrings.begin(); wi != wtransStrings.end(); ++wi) + { + //TC_LOG_ERROR("scripts", "{}", (*wi)); + wtrans->Append((*wi).c_str()); + } + + CharacterDatabase.CommitTransaction(ctrans); + WorldDatabase.CommitTransaction(wtrans); + + return BOT_DUMP_SUCCESS; +} + +BotDataDumpResult NPCBotsDump::Write(std::string const& file) +{ + if (FILE* f = fopen(file.c_str(), "r")) + { + fclose(f); + return BOT_DUMP_FAIL_FILE_ALREADY_EXISTS; + } + + BotDataDumpResult ret = BOT_DUMP_SUCCESS; + std::string dumpstr; + if (!GetDump(dumpstr)) + ret = BOT_DUMP_FAIL_INCOMPLETE; + else + { + FILE* fout = fopen(file.c_str(), "w"); + if (!fout) + return BOT_DUMP_FAIL_CANT_WRITE_TO_FILE; + + fprintf(fout, "%s", dumpstr.c_str()); + fclose(fout); + } + + return ret; +} + +bool NPCBotsDump::GetDump(std::string& dump) +{ + //bots are disabled but we need that data + if (!BotDataMgr::AllBotsLoaded()) + BotDataMgr::LoadNpcBots(false); + + dump = ""; + + dump += "IMPORTANT NOTE: THIS DUMPFILE IS MADE FOR USE WITH THE 'NPCBOT DUMP' COMMAND ONLY - EITHER THROUGH INGAME CHAT OR ON CONSOLE!\n"; + dump += "IMPORTANT NOTE: DO NOT apply it directly - it will irreversibly DAMAGE and CORRUPT your database! You have been warned!\n\n"; + + BotStringTransaction trans; + + std::set valid_ids; + bool integrityChecked = true; + for (uint32 i : BotDataMgr::GetExistingNPCBotIds()) + { + //skip generated bots + if (i >= BOT_ENTRY_CREATE_BEGIN && BotDataMgr::GetBotExtraCreatureTemplate(i)) + continue; + + BotDataVerificationResult res = VerifyWriteData(i); + if (res == BOT_DATA_INCOMPLETE) + { + if (integrityChecked) + integrityChecked = false; + } + else if (res == BOT_DATA_VALID) + valid_ids.insert(i); + } + + if (!integrityChecked || valid_ids.empty()) + return false; + + for (std::set::const_iterator ci = valid_ids.begin(); ci != valid_ids.end(); ++ci) + { + AppendBotNPCBotData(&trans, *ci); + AppendBotNPCBotTransmogData(&trans, *ci); + AppendBotEquipsData(&trans, *ci); + AppendBotCreatureData(&trans, *ci); + } + + dump += trans.GetBuffer(); + + return true; +} + +BotDataVerificationResult NPCBotsDump::VerifyWriteData(uint32 entry) const +{ + NpcBotData const* botData = BotDataMgr::SelectNpcBotData(entry); + + //bot of this entry is not spawned + if (!botData) + return BOT_DATA_NOT_EXIST; + + EquipmentInfo const* deinfo = BotDataMgr::GetBotEquipmentInfo(entry); + if (!deinfo) + { + TC_LOG_ERROR("scripts", "NPCBotsDump::AppendBotCreatureData creature {} is not found in `creature_equip_template` table!", entry); + return BOT_DATA_INCOMPLETE; + } + + QueryResult result = WorldDatabase.PQuery("SELECT `guid` FROM `creature` WHERE `id` = {}", entry); + + //creature is not spawned, corrupted + if (!result) + { + TC_LOG_ERROR("scripts", "NPCBotsDump::AppendBotCreatureData creature {} is not found in `creature` table!", entry); + return BOT_DATA_INCOMPLETE; + } + if (result->GetRowCount() > 1) + { + TC_LOG_ERROR("scripts", "NPCBotsDump::AppendBotCreatureData creature {} is spawned more that once!", entry); + return BOT_DATA_INCOMPLETE; + } + + return BOT_DATA_VALID; +} + +template +inline void AppendEscapedValue(std::ostringstream& ss, T const& val, bool end = false) +{ + ss << '\'' << val << '\''; + if (!end) + ss << ','; +} +inline void AppendNULL(std::ostringstream& ss, bool end = false) +{ + AppendEscapedValue(ss, "NULL", end); + //ss << "NULL"; + //if (!end) + // ss << ','; +} +std::string const EscapedString(char const* cstr) +{ + std::string s = cstr; + CharacterDatabase.EscapeString(s); + return s; +} + +void NPCBotsDump::AppendBotNPCBotData(BotStringTransaction* trans, uint32 entry) const +{ + NpcBotData const* botData = BotDataMgr::SelectNpcBotData(entry); + ASSERT(botData); + + std::ostringstream ss; + ss << "INSERT INTO " << TableImportDatas[TABLE_TYPE_CHARACTERS_NPCBOT].name << '\n' + << TableImportDatas[TABLE_TYPE_CHARACTERS_NPCBOT].fieldsStr << '\n'; + + ss << '('; + + AppendEscapedValue(ss, entry); + AppendEscapedValue(ss, botData->owner); + AppendEscapedValue(ss, botData->roles); + AppendEscapedValue(ss, uint32(botData->spec)); + AppendEscapedValue(ss, botData->faction); + + if (botData->disabled_spells.empty()) + AppendNULL(ss); + else + { + std::ostringstream ssds; + for (NpcBotData::DisabledSpellsContainer::const_iterator ci = botData->disabled_spells.begin(); ci != botData->disabled_spells.end(); ++ci) + ssds << *ci << ' '; + AppendEscapedValue(ss, ssds.str()); + } + + for (uint8 i = BOT_SLOT_MAINHAND; i != BOT_INVENTORY_SIZE; ++i) + AppendEscapedValue(ss, botData->equips[i], i == BOT_INVENTORY_SIZE-1); + + ss << ");\n"; + + trans->Append(ss.str()); +} + +void NPCBotsDump::AppendBotNPCBotTransmogData(BotStringTransaction* trans, uint32 entry) const +{ + NpcBotData const* botData = BotDataMgr::SelectNpcBotData(entry); + ASSERT(botData); + + QueryResult tresult = CharacterDatabase.PQuery("SELECT `entry`,`slot`,`item_id`,`fake_id` FROM `characters_npcbot_transmog` WHERE entry = {}", entry); + + if (!tresult) + return; + + std::ostringstream ss; + ss << "INSERT INTO " << TableImportDatas[TABLE_TYPE_NPCBOT_TRANSMOG].name << '\n' + << TableImportDatas[TABLE_TYPE_NPCBOT_TRANSMOG].fieldsStr << '\n'; + + static const uint32 transmog_fields_count = TableImportDatas[TABLE_TYPE_NPCBOT_TRANSMOG].paramsCount; + + while (true) + { + Field* fields = tresult->Fetch(); + + ss << '('; + + for (uint8 i = 0; i != transmog_fields_count; ++i) + { + bool end = i == transmog_fields_count - 1; + switch (i) + { + case 1: //slot + AppendEscapedValue(ss, uint32(fields[i].GetUInt8()), end); + break; + default: + AppendEscapedValue(ss, fields[i].GetUInt32(), end); + break; + } + } + + if (tresult->NextRow()) + ss << "),\n"; + else + { + ss << ");\n"; + break; + } + } + + trans->Append(ss.str()); +} + +void NPCBotsDump::AppendBotEquipsData(BotStringTransaction* trans, uint32 entry) const +{ + NpcBotData const* botData = BotDataMgr::SelectNpcBotData(entry); + ASSERT(botData); + + EquipmentInfo const* deinfo = BotDataMgr::GetBotEquipmentInfo(entry); + ASSERT(deinfo); + + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_NPCBOT_EQUIP_BY_ITEM_INSTANCE); + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 + //"SELECT creatorGuid, giftCreatorGuid, count, duration, charges, flags, enchantments, randomPropertyId, durability, playedTime, text, guid, itemEntry, owner_guid " + // "FROM item_instance WHERE guid IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_SYNCH + + for (uint8 i = 0; i != BOT_INVENTORY_SIZE; ++i) + stmt->setUInt32(i, botData->equips[i]); + + PreparedQueryResult iiresult = CharacterDatabase.Query(stmt); + + //all zeros? or maybe broken entry + if (!iiresult) + return; + + std::ostringstream ss; + ss << "INSERT INTO " << TableImportDatas[TABLE_TYPE_ITEM_INSTANCE].name << '\n' + << TableImportDatas[TABLE_TYPE_ITEM_INSTANCE].fieldsStr << '\n'; + + static const uint32 item_instance_fields_count = TableImportDatas[TABLE_TYPE_ITEM_INSTANCE].paramsCount; + + while (true) + { + Field* fields = iiresult->Fetch(); + + ss << '('; + + for (uint8 i = 0; i != item_instance_fields_count; ++i) + { + bool end = i == item_instance_fields_count-1; + switch (i) + { + case 4: //charges + case 6: //enchantments + case 10: //text + { + char const* cstr = fields[i].GetCString(); + if (!cstr) + AppendNULL(ss, end); + else + AppendEscapedValue(ss, EscapedString(cstr), end); + break; + } + case 7: //randomPropertyId + AppendEscapedValue(ss, int32(fields[i].GetInt16()), end); + break; + case 8: //durability + AppendEscapedValue(ss, uint32(fields[i].GetUInt16()), end); + break; + default: + AppendEscapedValue(ss, fields[i].GetUInt32(), end); + break; + } + } + + if (iiresult->NextRow()) + ss << "),\n"; + else + { + ss << ");\n"; + break; + } + } + + trans->Append(ss.str()); +} + +void NPCBotsDump::AppendBotCreatureData(BotStringTransaction* trans, uint32 entry) const +{ + QueryResult cresult = WorldDatabase.PQuery("SELECT `guid`,`id`,`map`,`spawnMask`,`phaseMask`,`position_x`,`position_y`,`position_z`,`orientation`,`curhealth`,`curmana` FROM `creature` WHERE id = {}", entry); + + ASSERT(cresult); + + std::ostringstream ss; + ss << "INSERT INTO " << TableImportDatas[TABLE_TYPE_CREATURE].name << '\n' + << TableImportDatas[TABLE_TYPE_CREATURE].fieldsStr << '\n'; + + ss << '('; + + static const uint32 creature_fields_count = TableImportDatas[TABLE_TYPE_CREATURE].paramsCount; + + Field* fields = cresult->Fetch(); + + for (uint8 i = 0; i != creature_fields_count; ++i) + { + bool end = i == creature_fields_count-1; + switch (i) + { + case 5: //position_x + case 6: //position_y + case 7: //position_z + case 8: //orientation + ss.setf(std::ios_base::fixed); + ss.precision(6); + AppendEscapedValue(ss, fields[i].GetFloat(), end); + break; + case 3: //spawnMask + AppendEscapedValue(ss, uint32(fields[i].GetUInt8()), end); + break; + case 2: //map + AppendEscapedValue(ss, uint32(fields[i].GetUInt16()), end); + break; + default: + AppendEscapedValue(ss, fields[i].GetUInt32(), end); + break; + } + } + + ss << ");\n"; + + trans->Append(ss.str()); +} diff --git a/src/server/game/AI/NpcBots/botdump.h b/src/server/game/AI/NpcBots/botdump.h new file mode 100644 index 000000000..16a6440fb --- /dev/null +++ b/src/server/game/AI/NpcBots/botdump.h @@ -0,0 +1,51 @@ +#ifndef _BOTDUMP_H +#define _BOTDUMP_H + +#include "Define.h" + +#include +#include + +enum BotDataDumpResult +{ + //all + BOT_DUMP_SUCCESS = 0, + //write + BOT_DUMP_FAIL_FILE_ALREADY_EXISTS, + BOT_DUMP_FAIL_CANT_WRITE_TO_FILE, + BOT_DUMP_FAIL_INCOMPLETE, + //load + BOT_DUMP_FAIL_FILE_NOT_EXIST, + BOT_DUMP_FAIL_FILE_CORRUPTED, + BOT_DUMP_FAIL_DATA_OCCUPIED +}; + +enum BotDataVerificationResult +{ + BOT_DATA_VALID = 0, + BOT_DATA_NOT_EXIST, + BOT_DATA_INCOMPLETE +}; + +class BotStringTransaction; + +class NPCBotsDump +{ + public: + NPCBotsDump() {} + + BotDataDumpResult Write(std::string const& file); + BotDataDumpResult Load(std::string const& file); + + private: + bool GetDump(std::string& dump); + BotDataVerificationResult VerifyWriteData(uint32 entry) const; + void AppendBotNPCBotData(BotStringTransaction* trans, uint32 entry) const; + void AppendBotNPCBotTransmogData(BotStringTransaction* trans, uint32 entry) const; + void AppendBotEquipsData(BotStringTransaction* trans, uint32 entry) const; + void AppendBotCreatureData(BotStringTransaction* trans, uint32 entry) const; + + BotDataDumpResult LoadDump(std::ifstream& input); +}; + +#endif diff --git a/src/server/game/AI/NpcBots/botgearscore.cpp b/src/server/game/AI/NpcBots/botgearscore.cpp new file mode 100644 index 000000000..9fe3a3d0e --- /dev/null +++ b/src/server/game/AI/NpcBots/botgearscore.cpp @@ -0,0 +1,141 @@ +#include "bot_ai.h" +#include "botdatamgr.h" +#include "botgearscore.h" +#include "Creature.h" +#include "Item.h" + +#include + +#ifdef _MSC_VER +# pragma warning(push, 4) +#endif + +constexpr float GS_scale = 1.8618f; + +static const std::map ItemSlotMods = { + { INVTYPE_HEAD, 1.0f }, + { INVTYPE_NECK, 0.5625f }, + { INVTYPE_SHOULDERS, 0.75f }, + { INVTYPE_CHEST, 1.0f }, + { INVTYPE_WAIST, 0.75f }, + { INVTYPE_LEGS, 1.0f }, + { INVTYPE_FEET, 0.75f }, + { INVTYPE_WRISTS, 0.5625f }, + { INVTYPE_HANDS, 0.75f }, + { INVTYPE_FINGER, 0.5625f }, + { INVTYPE_TRINKET, 0.5625f }, + { INVTYPE_WEAPON, 1.0f }, + { INVTYPE_SHIELD, 1.0f }, + { INVTYPE_RANGED, 0.3164f }, + { INVTYPE_CLOAK, 0.5625f }, + { INVTYPE_2HWEAPON, 2.0f }, + { INVTYPE_ROBE, 1.0f }, + { INVTYPE_WEAPONMAINHAND, 1.0f }, + { INVTYPE_WEAPONOFFHAND, 1.0f }, + { INVTYPE_HOLDABLE, 1.0f }, + { INVTYPE_THROWN, 0.3164f }, + { INVTYPE_RANGEDRIGHT, 0.3164f }, + { INVTYPE_RELIC, 0.3164f } +}; + +constexpr std::pair ItemLevelFactors[2][5] = { + { + { 0.0f, 1.0f }, + { 0.0f, 1.0f }, + { 73.0f, 1.0f }, + { 81.375f, 0.8125f }, + { 91.45f, 0.65f } + }, + { + { 0.0f, 1.0f }, + { 0.0f, 2.25f }, + { 8.0f, 2.0f }, + { 0.75f, 1.8f }, + { 26.0f, 1.2f } + } +}; + +void CalculateRawItemScore(ItemTemplate const* proto, float& score) +{ + auto smcit = ItemSlotMods.find(proto->InventoryType); + if (smcit == ItemSlotMods.cend()) + return; + + uint32 quality = proto->Quality; + float itemlvl = proto->ItemLevel; + float slotmod = smcit->second; + float qscale = 1.0f; + + if (quality == ITEM_QUALITY_LEGENDARY) + { + quality = ITEM_QUALITY_EPIC; + qscale = 1.3f; + } + else if (quality <= ITEM_QUALITY_NORMAL) + { + quality = ITEM_QUALITY_UNCOMMON; + qscale = 0.005f; + } + else if (quality == ITEM_QUALITY_HEIRLOOM) + { + quality = ITEM_QUALITY_RARE; + itemlvl = 187.05f; + } + + if (!(quality >= ITEM_QUALITY_UNCOMMON && quality <= ITEM_QUALITY_EPIC)) + return; + + auto const& p = ItemLevelFactors[size_t(itemlvl <= 120.0f)][quality]; + score = floor(((itemlvl - p.first) / p.second) * slotmod * qscale * GS_scale); +} + +float CalculateItemGearScore(uint32 botentry, uint8 botlevel, uint8 botclass, uint8 botspec, uint8 slot, ItemTemplate const* proto) +{ + ASSERT(slot < BOT_INVENTORY_SIZE, "Invalid bot equip slot %u!", uint32(slot)); + EquipmentInfo const* einfo = BotDataMgr::GetBotEquipmentInfo(botentry); + ASSERT(einfo, "Trying to CalculateItemGearScore for bot %u with no equip info!", botentry); + + float itemscore = 0.0f; + + if (slot > BOT_SLOT_RANGED || einfo->ItemEntry[slot] != proto->ItemId) + { + CalculateRawItemScore(proto, itemscore); + + if (slot == BOT_SLOT_MAINHAND || slot == BOT_SLOT_OFFHAND) + { + if (botspec == BOT_SPEC_WARRIOR_FURY && botlevel >= 60 && proto->InventoryType == INVTYPE_2HWEAPON) + itemscore *= 0.5f; + else if (botclass == BOT_CLASS_HUNTER) + itemscore *= 0.3164f; + } + else if (slot == BOT_SLOT_RANGED && botclass == BOT_CLASS_HUNTER) + itemscore *= 5.3224f; + } + + return std::max(itemscore, 0.0f); +} + +std::pair CalculateBotGearScore(uint32 botentry, uint8 botlevel, uint8 botclass, uint8 botspec, Item const* const items[BOT_INVENTORY_SIZE]) +{ + uint8 items_count = 0; + float totalscore = 0.0f; + + for (uint8 i = 0; i < BOT_INVENTORY_SIZE; ++i) + { + if (Item const* item = items[i]) + { + float itemscore = CalculateItemGearScore(botentry, botlevel, botclass, botspec, i, item->GetTemplate()); + if (itemscore > 0.0f) + { + ++items_count; + totalscore += itemscore; + } + } + } + + return { totalscore, totalscore / std::max(items_count, 1) }; +} + +#ifdef _MSC_VER +# pragma warning(pop) +#endif diff --git a/src/server/game/AI/NpcBots/botgearscore.h b/src/server/game/AI/NpcBots/botgearscore.h new file mode 100644 index 000000000..cb96c9ca2 --- /dev/null +++ b/src/server/game/AI/NpcBots/botgearscore.h @@ -0,0 +1,16 @@ +#ifndef BOT_GEARSCORE_H_ +#define BOT_GEARSCORE_H_ + +#include "botcommon.h" + +#include + +class Creature; +class Item; + +struct ItemTemplate; + +float CalculateItemGearScore(uint32 botentry, uint8 botlevel, uint8 botclass, uint8 botspec, uint8 slot, ItemTemplate const* proto); +std::pair CalculateBotGearScore(uint32 botentry, uint8 botlevel, uint8 botclass, uint8 botspec, Item const* const items[BOT_INVENTORY_SIZE]); + +#endif diff --git a/src/server/game/AI/NpcBots/botgiver.cpp b/src/server/game/AI/NpcBots/botgiver.cpp new file mode 100644 index 000000000..99ef3b2a0 --- /dev/null +++ b/src/server/game/AI/NpcBots/botgiver.cpp @@ -0,0 +1,298 @@ +#include "bot_ai.h" +#include "botcommon.h" +#include "botdatamgr.h" +#include "botgossip.h" +#include "botspell.h" +#include "bottext.h" +#include "botmgr.h" +#include "Chat.h" +#include "Creature.h" +#include "Log.h" +#include "Player.h" +#include "ScriptedGossip.h" +#include "ScriptMgr.h" +/* +NPCbot giver NPC by Trickerer ( ) +Complete - 100% +*/ + +#define HIRE GOSSIP_SENDER_BOTGIVER_HIRE +#define HIRE_CLASS GOSSIP_SENDER_BOTGIVER_HIRE_CLASS +#define HIRE_ENTRY GOSSIP_SENDER_BOTGIVER_HIRE_ENTRY + +class script_bot_giver : public CreatureScript +{ +public: + script_bot_giver() : CreatureScript("script_bot_giver") { } + + struct bot_giver_AI : public CreatureAI + { + bot_giver_AI(Creature* creature) : CreatureAI(creature) {} + + void UpdateAI(uint32 /*diff*/) override {} + + bool OnGossipHello(Player* player) override + { + if (!BotMgr::IsNpcBotModEnabled()) + { + player->PlayerTalkClass->SendCloseGossip(); + return true; + } + + if (me->isMoving()) + me->BotStopMovement(); + + AddGossipItemFor(player, GOSSIP_ICON_TALK, bot_ai::LocalizedNpcText(player, BOT_TEXT_BOTGIVER_SERVICE), HIRE, GOSSIP_ACTION_INFO_DEF + 1); + + AddGossipItemFor(player, GOSSIP_ICON_CHAT, bot_ai::LocalizedNpcText(player, BOT_TEXT_NEVERMIND), 0, GOSSIP_ACTION_INFO_DEF + 2); + + player->PlayerTalkClass->SendGossipMenu(GOSSIP_BOTGIVER_GREET, me->GetGUID()); + return true; + } + + bool OnGossipSelect(Player* player, uint32 /*menuId*/, uint32 gossipListId) override + { + if (!BotMgr::IsNpcBotModEnabled()) + { + player->PlayerTalkClass->SendCloseGossip(); + return true; + } + + uint32 sender = player->PlayerTalkClass->GetGossipOptionSender(gossipListId); + uint32 action = player->PlayerTalkClass->GetGossipOptionAction(gossipListId); + + player->PlayerTalkClass->ClearMenus(); + bool subMenu = false; + + uint32 gossipTextId = GOSSIP_BOTGIVER_GREET; + + switch (sender) + { + case 0: //exit + break; + case 1: //BACK: return to main menu + return OnGossipHello(player); + case HIRE: + { + gossipTextId = GOSSIP_BOTGIVER_HIRE; + + if (player->GetNpcBotsCount() >= BotMgr::GetMaxNpcBots(player->GetLevel())) + { + WhisperTo(player, bot_ai::LocalizedNpcText(player, BOT_TEXT_BOTGIVER_TOO_MANY_BOTS).c_str()); + break; + } + + if (uint32 maxBotsPerAccount = BotMgr::GetMaxAccountBots()) + { + uint32 accountBotsCount = BotDataMgr::GetAccountBotsCount(player->GetSession()->GetAccountId()); + if (accountBotsCount >= maxBotsPerAccount) + { + ChatHandler ch(player->GetSession()); + ch.PSendSysMessage(bot_ai::LocalizedNpcText(player, BOT_TEXT_HIREFAIL_MAXBOTS_ACCOUNT).c_str(), accountBotsCount, maxBotsPerAccount); + break; + } + } + + subMenu = true; + + uint8 availCount = 0; + std::array npcbot_count_per_class{ 0 }; + + { + std::unique_lock lock(*BotDataMgr::GetLock()); + for (Creature const* bot : BotDataMgr::GetExistingNPCBots()) + { + if (!bot->IsAlive() || bot->IsTempBot() || bot->IsWandererBot() || bot->GetBotAI()->GetBotOwnerGuid() || bot->HasAura(BERSERK)) + continue; + if (BotMgr::FilterRaces() && bot->GetBotClass() < BOT_CLASS_EX_START && (bot->GetRaceMask() & RACEMASK_ALL_PLAYABLE) && + !(bot->GetRaceMask() & ((player->GetRaceMask() & RACEMASK_ALLIANCE) ? RACEMASK_ALLIANCE : RACEMASK_HORDE))) + continue; + + ++npcbot_count_per_class[bot->GetBotClass()]; + } + } + + for (uint8 botclass = BOT_CLASS_WARRIOR; botclass < BOT_CLASS_END; ++botclass) + { + if (!BotMgr::IsClassEnabled(botclass)) + continue; + + if (player->HaveBot() && BotMgr::GetMaxClassBots()) + { + uint8 count = 0; + BotMap const* map = player->GetBotMgr()->GetBotMap(); + for (BotMap::const_iterator itr = map->begin(); itr != map->end(); ++itr) + if (itr->second->GetBotClass() == botclass) + ++count; + if (count >= BotMgr::GetMaxClassBots()) + continue; + } + + uint32 textId; + switch (botclass) + { + case BOT_CLASS_WARRIOR: textId = BOT_TEXT_CLASS_WARRIOR_PLU; break; + case BOT_CLASS_PALADIN: textId = BOT_TEXT_CLASS_PALADIN_PLU; break; + case BOT_CLASS_MAGE: textId = BOT_TEXT_CLASS_MAGE_PLU; break; + case BOT_CLASS_PRIEST: textId = BOT_TEXT_CLASS_PRIEST_PLU; break; + case BOT_CLASS_WARLOCK: textId = BOT_TEXT_CLASS_WARLOCK_PLU; break; + case BOT_CLASS_DRUID: textId = BOT_TEXT_CLASS_DRUID_PLU; break; + case BOT_CLASS_DEATH_KNIGHT:textId = BOT_TEXT_CLASS_DEATH_KNIGHT_PLU; break; + case BOT_CLASS_ROGUE: textId = BOT_TEXT_CLASS_ROGUE_PLU; break; + case BOT_CLASS_SHAMAN: textId = BOT_TEXT_CLASS_SHAMAN_PLU; break; + case BOT_CLASS_HUNTER: textId = BOT_TEXT_CLASS_HUNTER_PLU; break; + case BOT_CLASS_BM: textId = BOT_TEXT_CLASS_BM_PLU; break; + case BOT_CLASS_SPHYNX: textId = BOT_TEXT_CLASS_SPHYNX_PLU; break; + case BOT_CLASS_ARCHMAGE: textId = BOT_TEXT_CLASS_ARCHMAGE_PLU; break; + case BOT_CLASS_DREADLORD: textId = BOT_TEXT_CLASS_DREADLORD_PLU; break; + case BOT_CLASS_SPELLBREAKER:textId = BOT_TEXT_CLASS_SPELLBREAKER_PLU; break; + case BOT_CLASS_DARK_RANGER: textId = BOT_TEXT_CLASS_DARK_RANGER_PLU; break; + case BOT_CLASS_NECROMANCER: textId = BOT_TEXT_CLASS_NECROMANCER_PLU; break; + case BOT_CLASS_SEA_WITCH: textId = BOT_TEXT_CLASS_SEAWITCH_PLU; break; + case BOT_CLASS_CRYPT_LORD: textId = BOT_TEXT_CLASS_CRYPT_LORD_PLU; break; + default: textId = 0; break; + } + + if (!textId) + continue; + + std::ostringstream bclass; + bclass << npcbot_count_per_class[botclass] << " " << bot_ai::LocalizedNpcText(player, textId) << " (" << BotMgr::GetNpcBotCostStr(player->GetLevel(), botclass) << ")"; + + AddGossipItemFor(player, GOSSIP_ICON_TALK, bclass.str(), HIRE_CLASS, GOSSIP_ACTION_INFO_DEF + botclass); + + if (++availCount >= BOT_GOSSIP_MAX_ITEMS - 1) //back + break; + } + + if (availCount == 0) + gossipTextId = GOSSIP_BOTGIVER_HIRE_EMPTY; + + AddGossipItemFor(player, GOSSIP_ICON_CHAT, bot_ai::LocalizedNpcText(player, BOT_TEXT_NEVERMIND), 0, GOSSIP_ACTION_INFO_DEF + 1); + + break; + } + case HIRE_CLASS: + { + gossipTextId = GOSSIP_BOTGIVER_HIRE_CLASS; + + uint8 botclass = action - GOSSIP_ACTION_INFO_DEF; + + uint32 cost = BotMgr::GetNpcBotCost(player->GetLevel(), botclass); + if (!player->HasEnoughMoney(cost)) + { + WhisperTo(player, bot_ai::LocalizedNpcText(player, BOT_TEXT_HIREFAIL_COST).c_str()); + break; + } + + subMenu = true; + + uint8 availCount = 0; + + //go through bots map to find what bots are available + std::unique_lock lock(*BotDataMgr::GetLock()); + NpcBotRegistry const& allBots = BotDataMgr::GetExistingNPCBots(); + for (NpcBotRegistry::const_iterator ci = allBots.begin(); ci != allBots.end(); ++ci) + { + Creature const* bot = *ci; + bot_ai const* ai = bot->GetBotAI(); + if (bot->GetBotClass() != botclass || !bot->IsAlive() || ai->IsTempBot() || bot->IsWandererBot() || ai->GetBotOwnerGuid() || bot->HasAura(BERSERK)) + continue; + if (BotMgr::FilterRaces() && botclass < BOT_CLASS_EX_START && (bot->GetRaceMask() & RACEMASK_ALL_PLAYABLE) && + !(bot->GetRaceMask() & ((player->GetRaceMask() & RACEMASK_ALLIANCE) ? RACEMASK_ALLIANCE : RACEMASK_HORDE))) + continue; + + std::ostringstream message1; + message1 << bot_ai::LocalizedNpcText(player, BOT_TEXT_BOTGIVER_WISH_TO_HIRE_) << bot->GetName() << '?'; + + std::ostringstream info_ostr; + uint32 raceTextId; + switch (bot->GetRace()) + { + case RACE_HUMAN: raceTextId = BOT_TEXT_RACE_HUMAN; break; + case RACE_ORC: raceTextId = BOT_TEXT_RACE_ORC; break; + case RACE_DWARF: raceTextId = BOT_TEXT_RACE_DWARF; break; + case RACE_NIGHTELF: raceTextId = BOT_TEXT_RACE_NELF; break; + case RACE_UNDEAD_PLAYER:raceTextId = BOT_TEXT_RACE_UNDEAD; break; + case RACE_TAUREN: raceTextId = BOT_TEXT_RACE_TAUREN; break; + case RACE_GNOME: raceTextId = BOT_TEXT_RACE_GNOME; break; + case RACE_TROLL: raceTextId = BOT_TEXT_RACE_TROLL; break; + case RACE_BLOODELF: raceTextId = BOT_TEXT_RACE_BELF; break; + case RACE_DRAENEI: raceTextId = BOT_TEXT_RACE_DRAENEI; break; + default: raceTextId = BOT_TEXT_RACE_UNKNOWN; break; + } + info_ostr << bot->GetName() << " (" << ( + bot->GetGender() == GENDER_MALE ? bot_ai::LocalizedNpcText(player, BOT_TEXT_GENDER_MALE) + ' ' : + bot->GetGender() == GENDER_FEMALE ? bot_ai::LocalizedNpcText(player, BOT_TEXT_GENDER_FEMALE) + ' ' : + "") << bot_ai::LocalizedNpcText(player, raceTextId) << ')'; + + player->PlayerTalkClass->GetGossipMenu().AddMenuItem(-1, GOSSIP_ICON_TALK, info_ostr.str(), + HIRE_ENTRY, GOSSIP_ACTION_INFO_DEF + bot->GetEntry(), message1.str(), cost, false); + + if (++availCount >= BOT_GOSSIP_MAX_ITEMS - 1) //back + break; + } + + if (availCount == 0) + gossipTextId = GOSSIP_BOTGIVER_HIRE_EMPTY; + + AddGossipItemFor(player, GOSSIP_ICON_CHAT, bot_ai::LocalizedNpcText(player, BOT_TEXT_BACK), HIRE, GOSSIP_ACTION_INFO_DEF + 1); + + break; + } + case HIRE_ENTRY: + { + uint32 entry = action - GOSSIP_ACTION_INFO_DEF; + Creature const* bot = BotDataMgr::FindBot(entry); + if (!bot) + { + //possible but still + TC_LOG_ERROR("entities.unit", "HIRE_NBOT_ENTRY: bot {} not found!", entry); + break; + } + + bot_ai const* ai = bot->GetBotAI(); + if (bot->IsInCombat() || !bot->IsAlive() || bot_ai::CCed(bot) || + bot->HasUnitState(UNIT_STATE_CASTING) || ai->GetBotOwnerGuid() || bot->HasAura(BERSERK)) + { + //TC_LOG_ERROR("entities.unit", "HIRE_NBOT_ENTRY: bot {} ({}) is unavailable all of the sudden!", entry); + std::ostringstream failMsg; + failMsg << bot->GetName() << bot_ai::LocalizedNpcText(player, BOT_TEXT_BOTGIVER__BOT_BUSY); + WhisperTo(player, failMsg.str().c_str()); + break; + } + + //laways returns true + bot->GetBotAI()->OnGossipSelect(player, me, GOSSIP_SENDER_HIRE, GOSSIP_ACTION_INFO_DEF); + + if (player->HaveBot() && player->GetBotMgr()->GetBot(bot->GetGUID())) + WhisperTo(player, bot_ai::LocalizedNpcText(player, BOT_TEXT_BOTGIVER_HIRESUCCESS).c_str()); + + break; + } + } + + if (subMenu) + player->PlayerTalkClass->SendGossipMenu(gossipTextId, me->GetGUID()); + else + player->PlayerTalkClass->SendCloseGossip(); + + return true; + } + + void WhisperTo(Player* player, char const* message) + { + me->Whisper(message, LANG_UNIVERSAL, player); + } + }; + + CreatureAI* GetAI(Creature* creature) const override + { + return new bot_giver_AI(creature); + } +}; + +void AddSC_script_bot_giver() +{ + new script_bot_giver(); +} diff --git a/src/server/game/AI/NpcBots/botgossip.h b/src/server/game/AI/NpcBots/botgossip.h new file mode 100644 index 000000000..96bb60f9d --- /dev/null +++ b/src/server/game/AI/NpcBots/botgossip.h @@ -0,0 +1,141 @@ +#ifndef BOTGOSSIP_H +#define BOTGOSSIP_H + +#include "Define.h" + +enum BotGossips : uint32 +{ + GOSSIP_SENDER_BEGIN = 6000, + GOSSIP_SENDER_BOTGIVER_HIRE, + GOSSIP_SENDER_BOTGIVER_HIRE_CLASS, + GOSSIP_SENDER_BOTGIVER_HIRE_ENTRY, + GOSSIP_SENDER_CLASS, + GOSSIP_SENDER_CLASS_ACTION1, + GOSSIP_SENDER_CLASS_ACTION2, + GOSSIP_SENDER_CLASS_ACTION3, + GOSSIP_SENDER_CLASS_ACTION4, + GOSSIP_SENDER_EQUIPMENT, + GOSSIP_SENDER_EQUIPMENT_LIST, + GOSSIP_SENDER_EQUIPMENT_SHOW, + GOSSIP_SENDER_EQUIPMENT_INFO, + GOSSIP_SENDER_EQUIP_TRANSMOGS, + GOSSIP_SENDER_EQUIP_TRANSMOG_INFO, + GOSSIP_SENDER_EQUIP_TRANSMOGRIFY, + GOSSIP_SENDER_EQUIP_TRANSMOGRIFY_BEGIN = GOSSIP_SENDER_EQUIP_TRANSMOGRIFY, + GOSSIP_SENDER_EQUIP_TRANSMOGRIFY_MHAND = GOSSIP_SENDER_EQUIP_TRANSMOGRIFY_BEGIN, + GOSSIP_SENDER_EQUIP_TRANSMOGRIFY_OHAND, + GOSSIP_SENDER_EQUIP_TRANSMOGRIFY_RANGED, + GOSSIP_SENDER_EQUIP_TRANSMOGRIFY_HEAD, + GOSSIP_SENDER_EQUIP_TRANSMOGRIFY_SHOULDERS, + GOSSIP_SENDER_EQUIP_TRANSMOGRIFY_CHEST, + GOSSIP_SENDER_EQUIP_TRANSMOGRIFY_WAIST, + GOSSIP_SENDER_EQUIP_TRANSMOGRIFY_LEGS, + GOSSIP_SENDER_EQUIP_TRANSMOGRIFY_FEET, + GOSSIP_SENDER_EQUIP_TRANSMOGRIFY_WRIST, + GOSSIP_SENDER_EQUIP_TRANSMOGRIFY_HANDS, + GOSSIP_SENDER_EQUIP_TRANSMOGRIFY_BACK, + GOSSIP_SENDER_EQUIP_TRANSMOGRIFY_BODY, + GOSSIP_SENDER_UNEQUIP, + GOSSIP_SENDER_UNEQUIP_ALL, + GOSSIP_SENDER_EQUIP_AUTOEQUIP, + GOSSIP_SENDER_EQUIP_AUTOEQUIP_EQUIP, + GOSSIP_SENDER_EQUIP_AUTOEQUIP_BEGIN = GOSSIP_SENDER_EQUIP_AUTOEQUIP_EQUIP, + GOSSIP_SENDER_EQUIP_AUTOEQUIP_MHAND = GOSSIP_SENDER_EQUIP_AUTOEQUIP_BEGIN, + GOSSIP_SENDER_EQUIP_AUTOEQUIP_OHAND, + GOSSIP_SENDER_EQUIP_AUTOEQUIP_RANGED, + GOSSIP_SENDER_EQUIP_AUTOEQUIP_HEAD, + GOSSIP_SENDER_EQUIP_AUTOEQUIP_SHOULDERS, + GOSSIP_SENDER_EQUIP_AUTOEQUIP_CHEST, + GOSSIP_SENDER_EQUIP_AUTOEQUIP_WAIST, + GOSSIP_SENDER_EQUIP_AUTOEQUIP_LEGS, + GOSSIP_SENDER_EQUIP_AUTOEQUIP_FEET, + GOSSIP_SENDER_EQUIP_AUTOEQUIP_WRIST, + GOSSIP_SENDER_EQUIP_AUTOEQUIP_HANDS, + GOSSIP_SENDER_EQUIP_AUTOEQUIP_BACK, + GOSSIP_SENDER_EQUIP_AUTOEQUIP_BODY, + GOSSIP_SENDER_EQUIP_AUTOEQUIP_FINGER1, + GOSSIP_SENDER_EQUIP_AUTOEQUIP_FINGER2, + GOSSIP_SENDER_EQUIP_AUTOEQUIP_TRINKET1, + GOSSIP_SENDER_EQUIP_AUTOEQUIP_TRINKET2, + GOSSIP_SENDER_EQUIP_AUTOEQUIP_NECK, + GOSSIP_SENDER_EQUIP_RESET, + GOSSIP_SENDER_EQUIP, + GOSSIP_SENDER_EQUIP_BEGIN = GOSSIP_SENDER_EQUIP, + GOSSIP_SENDER_EQUIP_MHAND = GOSSIP_SENDER_EQUIP_BEGIN, + GOSSIP_SENDER_EQUIP_OHAND, + GOSSIP_SENDER_EQUIP_RANGED, + GOSSIP_SENDER_EQUIP_HEAD, + GOSSIP_SENDER_EQUIP_SHOULDERS, + GOSSIP_SENDER_EQUIP_CHEST, + GOSSIP_SENDER_EQUIP_WAIST, + GOSSIP_SENDER_EQUIP_LEGS, + GOSSIP_SENDER_EQUIP_FEET, + GOSSIP_SENDER_EQUIP_WRIST, + GOSSIP_SENDER_EQUIP_HANDS, + GOSSIP_SENDER_EQUIP_BACK, + GOSSIP_SENDER_EQUIP_BODY, + GOSSIP_SENDER_EQUIP_FINGER1, + GOSSIP_SENDER_EQUIP_FINGER2, + GOSSIP_SENDER_EQUIP_TRINKET1, + GOSSIP_SENDER_EQUIP_TRINKET2, + GOSSIP_SENDER_EQUIP_NECK, + GOSSIP_SENDER_EQUIPMENT_BANK_MENU, + GOSSIP_SENDER_EQUIPMENT_BANK_DEPOSIT, + GOSSIP_SENDER_EQUIPMENT_BANK_WITHDRAW, + GOSSIP_SENDER_EQUIPMENT_BANK_DEPOSIT_ITEM, + GOSSIP_SENDER_EQUIPMENT_BANK_WITHDRAW_ITEM, + GOSSIP_SENDER_ROLES_MAIN, + GOSSIP_SENDER_ROLES_MAIN_TOGGLE, + GOSSIP_SENDER_ROLES_GATHERING, + GOSSIP_SENDER_ROLES_GATHERING_TOGGLE, + GOSSIP_SENDER_ROLES_LOOTING, + GOSSIP_SENDER_ROLES_LOOTING_TOGGLE, + GOSSIP_SENDER_ABILITIES, + GOSSIP_SENDER_ABILITIES_USE, + GOSSIP_SENDER_ABILITIES_SPECIFICS_LIST, + GOSSIP_SENDER_ABILITIES_USAGE_LIST, + GOSSIP_SENDER_ABILITIES_USAGE_LIST_DAMAGE, + GOSSIP_SENDER_ABILITIES_USAGE_LIST_CC, + GOSSIP_SENDER_ABILITIES_USAGE_LIST_HEAL, + GOSSIP_SENDER_ABILITIES_USAGE_LIST_SUPPORT, + GOSSIP_SENDER_ABILITIES_USAGE_TOGGLE_DAMAGE, + GOSSIP_SENDER_ABILITIES_USAGE_TOGGLE_CC, + GOSSIP_SENDER_ABILITIES_USAGE_TOGGLE_HEAL, + GOSSIP_SENDER_ABILITIES_USAGE_TOGGLE_SUPPORT, + GOSSIP_SENDER_SPEC, + GOSSIP_SENDER_SPEC_SET, + GOSSIP_SENDER_USEITEM, + GOSSIP_SENDER_USEITEM_USE, + GOSSIP_SENDER_HIRE, + GOSSIP_SENDER_DISMISS, + GOSSIP_SENDER_JOIN_GROUP, + GOSSIP_SENDER_LEAVE_GROUP, + GOSSIP_SENDER_FORMATION, + GOSSIP_SENDER_FORMATION_FOLLOW_DISTANCE_SET, + GOSSIP_SENDER_FORMATION_TOGGLE_COMBAT_POSITIONING, + GOSSIP_SENDER_FORMATION_ATTACK_DISTANCE, + GOSSIP_SENDER_FORMATION_ATTACK_DISTANCE_SET, + GOSSIP_SENDER_FORMATION_ATTACK_ANGLE, + GOSSIP_SENDER_FORMATION_ATTACK_ANGLE_SET, + GOSSIP_SENDER_MODEL_UPDATE, + GOSSIP_SENDER_HOLDPOSITION, + GOSSIP_SENDER_DONOTHING, + GOSSIP_SENDER_FOLLOWME, + GOSSIP_SENDER_ENGAGE_BEHAVIOR, + GOSSIP_SENDER_ENGAGE_DELAY_SET_ATTACK, + GOSSIP_SENDER_ENGAGE_DELAY_SET_HEALING, + GOSSIP_SENDER_HEAL_HEALTH_THRESHOLD_SET, + GOSSIP_SENDER_PRIORITY_TARGET, + GOSSIP_SENDER_PRIORITY_TARGET_SET_TANK, + GOSSIP_SENDER_PRIORITY_TARGET_SET_DPS = GOSSIP_SENDER_PRIORITY_TARGET_SET_TANK + 3, // BOT_ROLE_DPS - BOT_ROLE_TANK + GOSSIP_SENDER_TROUBLESHOOTING, + GOSSIP_SENDER_TROUBLESHOOTING_FIX, + GOSSIP_SENDER_TROUBLESHOOTING_AURA, + GOSSIP_SENDER_SCAN, + GOSSIP_SENDER_DEBUG, + GOSSIP_SENDER_DEBUG_ACTION, +//GOSSIP CONST + BOT_GOSSIP_MAX_ITEMS = 32, // Client limitation 3.3.5 code confirmed +}; + +#endif //BOTGOSSIP_H diff --git a/src/server/game/AI/NpcBots/botlog.cpp b/src/server/game/AI/NpcBots/botlog.cpp new file mode 100644 index 000000000..cd36726c7 --- /dev/null +++ b/src/server/game/AI/NpcBots/botlog.cpp @@ -0,0 +1,87 @@ +#include "bot_ai.h" +#include "botdatamgr.h" +#include "botmgr.h" +#include "botlog.h" +#include "Creature.h" +#include "DatabaseEnvFwd.h" +#include "Log.h" + +template +static void BotLogImpl(uint16 log_type, uint32 entry, int32 owner, int32 mapid, int8 inmap, int8 inworld, Args&&... params) +{ + std::vector sparams; + sparams.reserve(MAX_BOT_LOG_PARAMS); + using compounder = int[]; + (void)compounder { 0, ((void)sparams.push_back(NPCBots::StringConvert::ToString(params)), 0) ... }; + sparams.resize(MAX_BOT_LOG_PARAMS, {}); + for (uint8 i = 0; i < MAX_BOT_LOG_PARAMS; ++i) + { + if (sparams[i].size() > MAX_BOT_LOG_PARAM_LENGTH) + { + TC_LOG_DEBUG("npcbots", "Bot logger: while writing type {} entry {} owner {} param {} '{}' was truncated to {} symbols!", + log_type, entry, owner, uint32(i+1), sparams[i], MAX_BOT_LOG_PARAM_LENGTH); + sparams[i] = sparams[i].substr(0, MAX_BOT_LOG_PARAM_LENGTH); + } + } + + CharacterDatabasePreparedStatement* bstmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_NPCBOT_LOG); + //"INSERT INTO characters_npcbot_logs (entry, owner, mapid, inmap, inworld, type, param1, param2, param3, param4, param5) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC + uint32 index = 0; + bstmt->setUInt32( index, entry); + bstmt->setInt32 (++index, owner); + bstmt->setInt32 (++index, mapid); + bstmt->setInt8 (++index, inmap); + bstmt->setInt8 (++index, inworld); + bstmt->setUInt16(++index, log_type); + for (std::string const& param : sparams) + bstmt->setString(++index, param); + CharacterDatabase.Execute(bstmt); +} + +template +inline static void BotLogImpl(uint16 log_type, Creature const* bot, int32 owner, Args&&... params) +{ + BotLogImpl(log_type, bot->GetEntry(), owner, (int32)bot->GetMapId(), (int8)!!bot->FindMap(), (int8)bot->IsInWorld(), std::forward(params)...); +} + +template +requires NPCBots::LoggableArguments +void BotLogger::Log(uint16 log_type, Creature const* bot, Args&&... params) +{ + if (!BotMgr::IsNpcBotLogEnabled()) + return; + + BotLogImpl(log_type, bot, int32(bot->GetBotAI() ? bot->GetBotAI()->GetBotOwnerGuid() : -1), std::forward(params)...); +} + +template +requires NPCBots::LoggableArguments +void BotLogger::Log(uint16 log_type, uint32 entry, Args&&... params) +{ + if (!BotMgr::IsNpcBotLogEnabled()) + return; + + if (Creature const* bot = entry ? BotDataMgr::FindBot(entry) : nullptr) + BotLogger::Log(log_type, bot, std::forward(params)...); + else + { + if (entry) + { + std::stringstream ss; + using compounder = int[]; + (void)compounder { 0, ((void)(ss << ' ' << params), 0) ... }; + TC_LOG_DEBUG("npcbots", "Logging unregistered bot entry {}: type {} params:{}", entry, log_type, ss.str()); + } + BotLogImpl(log_type, entry, -1, -1, -1, -1, std::forward(params)...); + } +} + +template void BotLogger::Log(uint16, Creature const*); +template void BotLogger::Log(uint16, Creature const*, bool&&, bool&&, bool&&); +template void BotLogger::Log(uint16, Creature const*, bool&&, bool&&, bool&&, uint32&&, bool&&); +template void BotLogger::Log(uint16, Creature const*, uint32&&, uint32&&, uint32&&); +template void BotLogger::Log(uint16, Creature const*, uint32&&, uint32&&, uint32&&, uint32&&); +template void BotLogger::Log(uint16, Creature const*, uint32&&, uint32&&, uint32&&, uint32&&, uint32&&); +template void BotLogger::Log(uint16, uint32); +template void BotLogger::Log(uint16, uint32, std::string_view&&); +template void BotLogger::Log(uint16, uint32, std::string&, std::string&, std::string&, std::string&, std::string&); diff --git a/src/server/game/AI/NpcBots/botlog.h b/src/server/game/AI/NpcBots/botlog.h new file mode 100644 index 000000000..5041c0bb2 --- /dev/null +++ b/src/server/game/AI/NpcBots/botlog.h @@ -0,0 +1,39 @@ +#ifndef BOTLOG_H_ +#define BOTLOG_H_ + +#include "Define.h" + +#include "botlogtraits.h" + +class Creature; + +constexpr uint32 BOT_LOG_KEEP_DAYS = 30; + +enum BotLogType : uint16 +{ + NPCBOT_LOG_SPAWN = 1, + NPCBOT_LOG_TELEPORT_START = 2, + NPCBOT_LOG_TELEPORT_FINISH = 3, + NPCBOT_LOG_INIT_EQUIP = 4, + NPCBOT_LOG_EQUIP = 5, + NPCBOT_LOG_UNEQUIP = 6, + NPCBOT_LOG_EQUIP_RESET = 7, + + NPCBOT_LOG_SYSTEM_START = 100, + NPCBOT_LOG_CONFIG_RELOAD = 101, + + NPCBOT_LOG_END +}; + +class BotLogger +{ + public: + template + requires NPCBots::LoggableArguments + static void Log(uint16 log_type, Creature const* bot, Args&&... params); + template + requires NPCBots::LoggableArguments + static void Log(uint16 log_type, uint32 entry, Args&&... params); +}; + +#endif //BOTLOG_H_ diff --git a/src/server/game/AI/NpcBots/botmgr.cpp b/src/server/game/AI/NpcBots/botmgr.cpp new file mode 100644 index 000000000..730ea4e26 --- /dev/null +++ b/src/server/game/AI/NpcBots/botmgr.cpp @@ -0,0 +1,3110 @@ +#include "Battleground.h" +#include "BattlegroundMgr.h" +#include "bot_ai.h" +#include "bot_Events.h" +#include "botdatamgr.h" +#include "botdpstracker.h" +#include "botlog.h" +#include "botmgr.h" +#include "botspell.h" +#include "bottext.h" +#include "bpet_ai.h" +#include "Chat.h" +#include "CombatPackets.h" +#include "Config.h" +#include "GroupMgr.h" +#include "GridNotifiers.h" +#include "GridNotifiersImpl.h" +#include "Group.h" +#include "InstanceScript.h" +#include "Language.h" +#include "Log.h" +#include "Map.h" +#include "MapManager.h" +#include "MotionMaster.h" +#include "ObjectMgr.h" +#include "Player.h" +#include "ScriptMgr.h" +#include "SpellAuraEffects.h" +#include "Vehicle.h" +#include "Transport.h" +#include "World.h" +#include "revision_data.h" +/* +Npc Bot Manager by Trickerer (onlysuffering@gmail.com) +Player NpcBots management +TODO: Move creature hooks here +*/ + +#ifdef _MSC_VER +# pragma warning(push, 4) +#endif + +#ifdef AC_COMPILER +# define GetBoolDefault GetOption +# define GetIntDefault GetOption +# define GetFloatDefault GetOption +# define GetStringDefault GetOption +#endif + +static std::list delayed_bot_teleports; + +//config +uint8 _basefollowdist; +uint8 _maxClassNpcBots; +uint8 _maxAccountNpcBots; +uint8 _xpReductionAmount; +uint8 _xpReductionStartingNumber; +uint8 _mountLevel60; +uint8 _mountLevel100; +uint8 _healTargetIconFlags; +uint8 _tankingTargetIconFlags; +uint8 _offTankingTargetIconFlags; +uint8 _dpsTargetIconFlags; +uint8 _rangedDpsTargetIconFlags; +uint8 _noDpsTargetIconFlags; +uint8 _npcBotOwnerExpireMode; +int32 _botInfoPacketsLimit; +uint32 _npcBotsCost; +uint32 _npcBotUpdateDelayBase; +uint32 _npcBotEngageDelayDPS_default; +uint32 _npcBotEngageDelayHeal_default; +uint32 _npcBotOwnerExpireTime; +uint32 _desiredWanderingBotsCount; +uint32 _targetBGPlayersPerTeamCount_AV; +uint32 _targetBGPlayersPerTeamCount_WS; +uint32 _targetBGPlayersPerTeamCount_AB; +uint32 _targetBGPlayersPerTeamCount_EY; +uint32 _targetBGPlayersPerTeamCount_SA; +uint32 _targetBGPlayersPerTeamCount_IC; +bool _enableNpcBots; +bool _logToDB; +bool _enableNpcBotsDungeons; +bool _enableNpcBotsRaids; +bool _enableNpcBotsBGs; +bool _enableNpcBotsArenas; +bool _enableDungeonFinder; +bool _limitNpcBotsDungeons; +bool _limitNpcBotsRaids; +bool _hideSpawns; +bool _botPvP; +bool _botMovementFoodInterrupt; +bool _filterRaces; +bool _displayEquipment; +bool _showCloak; +bool _showHelm; +bool _sendEquipListItems; +bool _enableBotGearBank; +bool _transmog_enable; +bool _transmog_mixArmorClasses; +bool _transmog_mixWeaponClasses; +bool _transmog_mixWeaponInvTypes; +bool _transmog_useEquipmentSlots; +bool _enableclass_warrior; +bool _enableclass_paladin; +bool _enableclass_hunter; +bool _enableclass_rogue; +bool _enableclass_priest; +bool _enableclass_deathknight; +bool _enableclass_shaman; +bool _enableclass_mage; +bool _enableclass_warlock; +bool _enableclass_druid; +bool _enableclass_blademaster; +bool _enableclass_sphynx; +bool _enableclass_archmage; +bool _enableclass_dreadlord; +bool _enableclass_spellbreaker; +bool _enableclass_darkranger; +bool _enableclass_necromancer; +bool _enableclass_seawitch; +bool _enableclass_cryptlord; +bool _enableclass_wander_warrior; +bool _enableclass_wander_paladin; +bool _enableclass_wander_hunter; +bool _enableclass_wander_rogue; +bool _enableclass_wander_priest; +bool _enableclass_wander_deathknight; +bool _enableclass_wander_shaman; +bool _enableclass_wander_mage; +bool _enableclass_wander_warlock; +bool _enableclass_wander_druid; +bool _enableclass_wander_blademaster; +bool _enableclass_wander_sphynx; +bool _enableclass_wander_archmage; +bool _enableclass_wander_dreadlord; +bool _enableclass_wander_spellbreaker; +bool _enableclass_wander_darkranger; +bool _enableclass_wander_necromancer; +bool _enableclass_wander_seawitch; +bool _enableclass_wander_cryptlord; +bool _enrageOnDismiss; +bool _botStatLimits; +bool _enableWanderingBotsBG; +bool _enableConfigLevelCapBG; +bool _enableConfigLevelCapBGFirst; +bool _bothk_enable; +bool _bothk_message_enable; +bool _bothk_achievements_enable; +float _botStatLimits_dodge; +float _botStatLimits_parry; +float _botStatLimits_block; +float _botStatLimits_crit; +float _mult_dmg_physical; +float _mult_dmg_spell; +float _mult_healing; +float _mult_hp; +float _mult_dmg_wanderer; +float _mult_healing_wanderer; +float _mult_hp_wanderer; +float _mult_speed_wanderer; +float _mult_xpgain_wanderer; +float _mult_dmg_warrior; +float _mult_dmg_paladin; +float _mult_dmg_hunter; +float _mult_dmg_rogue; +float _mult_dmg_priest; +float _mult_dmg_deathknight; +float _mult_dmg_shaman; +float _mult_dmg_mage; +float _mult_dmg_warlock; +float _mult_dmg_druid; +float _mult_dmg_blademaster; +float _mult_dmg_obsidiandestroyer; +float _mult_dmg_archmage; +float _mult_dmg_dreadlord; +float _mult_dmg_spellbreaker; +float _mult_dmg_darkranger; +float _mult_dmg_necromancer; +float _mult_dmg_seawitch; +float _mult_dmg_cryptlord; +float _bothk_rate_honor; +std::vector _mult_dmg_levels; +LvlBrackets _max_npcbots; +PctBrackets _botwanderer_pct_level_brackets; +std::vector _disabled_instance_maps; +std::vector _enabled_wander_node_maps; + +bool __firstload = true; + +void AddSC_death_knight_bot(); +void AddSC_druid_bot(); +void AddSC_hunter_bot(); +void AddSC_mage_bot(); +void AddSC_paladin_bot(); +void AddSC_priest_bot(); +void AddSC_rogue_bot(); +void AddSC_shaman_bot(); +void AddSC_warlock_bot(); +void AddSC_warrior_bot(); +void AddSC_blademaster_bot(); +void AddSC_sphynx_bot(); +void AddSC_archmage_bot(); +void AddSC_dreadlord_bot(); +void AddSC_spellbreaker_bot(); +void AddSC_dark_ranger_bot(); +void AddSC_necromancer_bot(); +void AddSC_sea_witch_bot(); +void AddSC_crypt_lord_bot(); +void AddSC_archmage_bot_pets(); +void AddSC_dreadlord_bot_pets(); +void AddSC_dark_ranger_bot_pets(); +void AddSC_necromancer_bot_pets(); +void AddSC_sea_witch_bot_pets(); +void AddSC_crypt_lord_bot_pets(); +void AddSC_hunter_bot_pets(); +void AddSC_warlock_bot_pets(); +void AddSC_deathknight_bot_pets(); +void AddSC_priest_bot_pets(); +void AddSC_shaman_bot_pets(); +void AddSC_mage_bot_pets(); +void AddSC_druid_bot_pets(); +void AddSC_script_bot_commands(); +void AddSC_script_bot_giver(); +void AddSC_botdatamgr_scripts(); + +void AddNpcBotScripts() +{ + AddSC_death_knight_bot(); + AddSC_druid_bot(); + AddSC_hunter_bot(); + AddSC_mage_bot(); + AddSC_paladin_bot(); + AddSC_priest_bot(); + AddSC_rogue_bot(); + AddSC_shaman_bot(); + AddSC_warlock_bot(); + AddSC_warrior_bot(); + AddSC_blademaster_bot(); + AddSC_sphynx_bot(); + AddSC_archmage_bot(); + AddSC_dreadlord_bot(); + AddSC_spellbreaker_bot(); + AddSC_dark_ranger_bot(); + AddSC_necromancer_bot(); + AddSC_sea_witch_bot(); + AddSC_crypt_lord_bot(); + AddSC_archmage_bot_pets(); + AddSC_dreadlord_bot_pets(); + AddSC_dark_ranger_bot_pets(); + AddSC_necromancer_bot_pets(); + AddSC_sea_witch_bot_pets(); + AddSC_crypt_lord_bot_pets(); + AddSC_hunter_bot_pets(); + AddSC_warlock_bot_pets(); + AddSC_deathknight_bot_pets(); + AddSC_priest_bot_pets(); + AddSC_shaman_bot_pets(); + AddSC_mage_bot_pets(); + AddSC_druid_bot_pets(); + AddSC_script_bot_commands(); + AddSC_script_bot_giver(); + AddSC_botdatamgr_scripts(); +} + +BotMgr::BotMgr(Player* const master) : _owner(master), _dpstracker(new DPSTracker()) +{ + //LoadConfig(); already loaded (MapManager.cpp) + _followdist = _basefollowdist; + _exactAttackRange = 0; + _attackRangeMode = BOT_ATTACK_RANGE_SHORT; + _attackAngleMode = BOT_ATTACK_ANGLE_NORMAL; + _allowCombatPositioning = true; + _npcBotEngageDelayDPS = _npcBotEngageDelayDPS_default; + _npcBotEngageDelayHeal = _npcBotEngageDelayHeal_default; + + _botsHidden = false; + _quickrecall = false; +} +BotMgr::~BotMgr() +{ + delete _dpstracker; +} + +void BotMgr::Initialize() +{ + LoadConfig(); + BotLogger::Log(NPCBOT_LOG_SYSTEM_START, uint32(0), std::string_view{ VER_FILEVERSION_STR }.substr(0, MAX_BOT_LOG_PARAM_LENGTH)); + + BotDataMgr::LoadNpcBots(); + BotDataMgr::LoadWanderMap(); + BotDataMgr::GenerateWanderingBots(); + BotDataMgr::CreateWanderingBotsSortedGear(); + BotDataMgr::LoadNpcBotGroupData(); + BotDataMgr::LoadNpcBotGearStorage(); + BotDataMgr::DeleteOldLogs(); + + ResolveConfigConflicts(); +} + +void BotMgr::ReloadConfig() +{ + LoadConfig(true); +} + +void BotMgr::LoadConfig(bool reload) +{ + if (__firstload) + __firstload = false; + else if (!reload) + return; + + _enableNpcBots = sConfigMgr->GetBoolDefault("NpcBot.Enable", true); + _logToDB = sConfigMgr->GetBoolDefault("NpcBot.LogToDB", true); + _maxClassNpcBots = sConfigMgr->GetIntDefault("NpcBot.MaxBotsPerClass", 1); + _maxAccountNpcBots = sConfigMgr->GetIntDefault("NpcBot.MaxBotsPerAccount", 0); + _filterRaces = sConfigMgr->GetBoolDefault("NpcBot.Botgiver.FilterRaces", false); + _basefollowdist = sConfigMgr->GetIntDefault("NpcBot.BaseFollowDistance", 30); + _xpReductionAmount = sConfigMgr->GetIntDefault("NpcBot.XpReduction.Amount", 0); + _xpReductionStartingNumber = sConfigMgr->GetIntDefault("NpcBot.XpReduction.StartingNumber", 2); + _mountLevel60 = sConfigMgr->GetIntDefault("NpcBot.MountLevel.60", 20); + _mountLevel100 = sConfigMgr->GetIntDefault("NpcBot.MountLevel.100", 40); + _healTargetIconFlags = sConfigMgr->GetIntDefault("NpcBot.HealTargetIconMask", 0); + _tankingTargetIconFlags = sConfigMgr->GetIntDefault("NpcBot.TankTargetIconMask", 0); + _offTankingTargetIconFlags = sConfigMgr->GetIntDefault("NpcBot.OffTankTargetIconMask", 0); + _dpsTargetIconFlags = sConfigMgr->GetIntDefault("NpcBot.DPSTargetIconMask", 0); + _rangedDpsTargetIconFlags = sConfigMgr->GetIntDefault("NpcBot.RangedDPSTargetIconMask", 0); + _noDpsTargetIconFlags = sConfigMgr->GetIntDefault("NpcBot.NoDPSTargetIconMask", 0); + _mult_dmg_physical = sConfigMgr->GetFloatDefault("NpcBot.Mult.Damage.Physical", 1.0f); + _mult_dmg_spell = sConfigMgr->GetFloatDefault("NpcBot.Mult.Damage.Spell", 1.0f); + _mult_healing = sConfigMgr->GetFloatDefault("NpcBot.Mult.Healing", 1.0f); + _mult_hp = sConfigMgr->GetFloatDefault("NpcBot.Mult.HP", 1.0f); + _mult_dmg_wanderer = sConfigMgr->GetFloatDefault("NpcBot.Mult.Wanderer.Damage", 1.0f); + _mult_healing_wanderer = sConfigMgr->GetFloatDefault("NpcBot.Mult.Wanderer.Healing", 1.0f); + _mult_hp_wanderer = sConfigMgr->GetFloatDefault("NpcBot.Mult.Wanderer.HP", 1.0f); + _mult_speed_wanderer = sConfigMgr->GetFloatDefault("NpcBot.Mult.Wanderer.Speed", 1.0f); + _mult_dmg_warrior = sConfigMgr->GetFloatDefault("NpcBot.Mult.Damage.Warrior", 1.0f); + _mult_dmg_paladin = sConfigMgr->GetFloatDefault("NpcBot.Mult.Damage.Paladin", 1.0f); + _mult_dmg_hunter = sConfigMgr->GetFloatDefault("NpcBot.Mult.Damage.Hunter", 1.0f); + _mult_dmg_rogue = sConfigMgr->GetFloatDefault("NpcBot.Mult.Damage.Rogue", 1.0f); + _mult_dmg_priest = sConfigMgr->GetFloatDefault("NpcBot.Mult.Damage.Priest", 1.0f); + _mult_dmg_deathknight = sConfigMgr->GetFloatDefault("NpcBot.Mult.Damage.DeathKnight", 1.0f); + _mult_dmg_shaman = sConfigMgr->GetFloatDefault("NpcBot.Mult.Damage.Shaman", 1.0f); + _mult_dmg_mage = sConfigMgr->GetFloatDefault("NpcBot.Mult.Damage.Mage", 1.0f); + _mult_dmg_warlock = sConfigMgr->GetFloatDefault("NpcBot.Mult.Damage.Warlock", 1.0f); + _mult_dmg_druid = sConfigMgr->GetFloatDefault("NpcBot.Mult.Damage.Druid", 1.0f); + _mult_dmg_blademaster = sConfigMgr->GetFloatDefault("NpcBot.Mult.Damage.Blademaster", 1.0f); + _mult_dmg_obsidiandestroyer = sConfigMgr->GetFloatDefault("NpcBot.Mult.Damage.ObsidianDestroyer", 1.0f); + _mult_dmg_archmage = sConfigMgr->GetFloatDefault("NpcBot.Mult.Damage.Archmage", 1.0f); + _mult_dmg_dreadlord = sConfigMgr->GetFloatDefault("NpcBot.Mult.Damage.Dreadlord", 1.0f); + _mult_dmg_spellbreaker = sConfigMgr->GetFloatDefault("NpcBot.Mult.Damage.SpellBreaker", 1.0f); + _mult_dmg_darkranger = sConfigMgr->GetFloatDefault("NpcBot.Mult.Damage.DarkRanger", 1.0f); + _mult_dmg_necromancer = sConfigMgr->GetFloatDefault("NpcBot.Mult.Damage.Necromancer", 1.0f); + _mult_dmg_seawitch = sConfigMgr->GetFloatDefault("NpcBot.Mult.Damage.SeaWitch", 1.0f); + _mult_dmg_cryptlord = sConfigMgr->GetFloatDefault("NpcBot.Mult.Damage.CryptLord", 1.0f); + _enableNpcBotsDungeons = sConfigMgr->GetBoolDefault("NpcBot.Enable.Dungeon", true); + _enableNpcBotsRaids = sConfigMgr->GetBoolDefault("NpcBot.Enable.Raid", false); + _enableNpcBotsBGs = sConfigMgr->GetBoolDefault("NpcBot.Enable.BG", false); + _enableNpcBotsArenas = sConfigMgr->GetBoolDefault("NpcBot.Enable.Arena", false); + _enableDungeonFinder = sConfigMgr->GetBoolDefault("NpcBot.Enable.DungeonFinder", true); + _limitNpcBotsDungeons = sConfigMgr->GetBoolDefault("NpcBot.Limit.Dungeon", true); + _limitNpcBotsRaids = sConfigMgr->GetBoolDefault("NpcBot.Limit.Raid", true); + _hideSpawns = sConfigMgr->GetBoolDefault("NpcBot.HideSpawns", false); + _botInfoPacketsLimit = sConfigMgr->GetIntDefault("NpcBot.InfoPacketsLimit", -1); + _npcBotsCost = sConfigMgr->GetIntDefault("NpcBot.Cost", 1000000); + _npcBotUpdateDelayBase = sConfigMgr->GetIntDefault("NpcBot.UpdateDelay.Base", 0); + _npcBotEngageDelayDPS_default = sConfigMgr->GetIntDefault("NpcBot.EngageDelay.DPS", 0); + _npcBotEngageDelayHeal_default = sConfigMgr->GetIntDefault("NpcBot.EngageDelay.Heal", 0); + _npcBotOwnerExpireTime = sConfigMgr->GetIntDefault("NpcBot.OwnershipExpireTime", 0); + _npcBotOwnerExpireMode = sConfigMgr->GetIntDefault("NpcBot.OwnershipExpireMode", 0); + _botPvP = sConfigMgr->GetBoolDefault("NpcBot.PvP", true); + _botMovementFoodInterrupt = sConfigMgr->GetBoolDefault("NpcBot.Movements.InterruptFood", false); + _displayEquipment = sConfigMgr->GetBoolDefault("NpcBot.EquipmentDisplay.Enable", true); + _showCloak = sConfigMgr->GetBoolDefault("NpcBot.EquipmentDisplay.ShowCloak", true); + _showHelm = sConfigMgr->GetBoolDefault("NpcBot.EquipmentDisplay.ShowHelm", false); + _sendEquipListItems = sConfigMgr->GetBoolDefault("NpcBot.Gossip.ShowEquipmentListItems", false); + _enableBotGearBank = sConfigMgr->GetBoolDefault("NpcBot.GearBank.Enable", false); + _transmog_enable = sConfigMgr->GetBoolDefault("NpcBot.Transmog.Enable", false); + _transmog_mixArmorClasses = sConfigMgr->GetBoolDefault("NpcBot.Transmog.MixArmorClasses", false); + _transmog_mixWeaponClasses = sConfigMgr->GetBoolDefault("NpcBot.Transmog.MixWeaponClasses", false); + _transmog_mixWeaponInvTypes = sConfigMgr->GetBoolDefault("NpcBot.Transmog.MixWeaponInventoryTypes", false); + _transmog_useEquipmentSlots = sConfigMgr->GetBoolDefault("NpcBot.Transmog.UseEquipmentSlots", false); + _enableclass_warrior = sConfigMgr->GetBoolDefault("NpcBot.Classes.Warrior.Enable", true); + _enableclass_paladin = sConfigMgr->GetBoolDefault("NpcBot.Classes.Paladin.Enable", true); + _enableclass_hunter = sConfigMgr->GetBoolDefault("NpcBot.Classes.Hunter.Enable", true); + _enableclass_rogue = sConfigMgr->GetBoolDefault("NpcBot.Classes.Rogue.Enable", true); + _enableclass_priest = sConfigMgr->GetBoolDefault("NpcBot.Classes.Priest.Enable", true); + _enableclass_deathknight = sConfigMgr->GetBoolDefault("NpcBot.Classes.DeathKnight.Enable", true); + _enableclass_shaman = sConfigMgr->GetBoolDefault("NpcBot.Classes.Shaman.Enable", true); + _enableclass_mage = sConfigMgr->GetBoolDefault("NpcBot.Classes.Mage.Enable", true); + _enableclass_warlock = sConfigMgr->GetBoolDefault("NpcBot.Classes.Warlock.Enable", true); + _enableclass_druid = sConfigMgr->GetBoolDefault("NpcBot.Classes.Druid.Enable", true); + _enableclass_blademaster = false; // sConfigMgr->GetBoolDefault("NpcBot.Classes.Blademaster.Enable", false); + _enableclass_sphynx = sConfigMgr->GetBoolDefault("NpcBot.Classes.ObsidianDestroyer.Enable", true); + _enableclass_archmage = sConfigMgr->GetBoolDefault("NpcBot.Classes.Archmage.Enable", true); + _enableclass_dreadlord = sConfigMgr->GetBoolDefault("NpcBot.Classes.Dreadlord.Enable", true); + _enableclass_spellbreaker = sConfigMgr->GetBoolDefault("NpcBot.Classes.SpellBreaker.Enable", true); + _enableclass_darkranger = sConfigMgr->GetBoolDefault("NpcBot.Classes.DarkRanger.Enable", true); + _enableclass_necromancer = sConfigMgr->GetBoolDefault("NpcBot.Classes.Necromancer.Enable", true); + _enableclass_seawitch = sConfigMgr->GetBoolDefault("NpcBot.Classes.SeaWitch.Enable", true); + _enableclass_cryptlord = sConfigMgr->GetBoolDefault("NpcBot.Classes.CryptLord.Enable", true); + _enableclass_wander_warrior = sConfigMgr->GetBoolDefault("NpcBot.WanderingBots.Classes.Warrior.Enable", true); + _enableclass_wander_paladin = sConfigMgr->GetBoolDefault("NpcBot.WanderingBots.Classes.Paladin.Enable", true); + _enableclass_wander_hunter = sConfigMgr->GetBoolDefault("NpcBot.WanderingBots.Classes.Hunter.Enable", true); + _enableclass_wander_rogue = sConfigMgr->GetBoolDefault("NpcBot.WanderingBots.Classes.Rogue.Enable", true); + _enableclass_wander_priest = sConfigMgr->GetBoolDefault("NpcBot.WanderingBots.Classes.Priest.Enable", true); + _enableclass_wander_deathknight = sConfigMgr->GetBoolDefault("NpcBot.WanderingBots.Classes.DeathKnight.Enable", true); + _enableclass_wander_shaman = sConfigMgr->GetBoolDefault("NpcBot.WanderingBots.Classes.Shaman.Enable", true); + _enableclass_wander_mage = sConfigMgr->GetBoolDefault("NpcBot.WanderingBots.Classes.Mage.Enable", true); + _enableclass_wander_warlock = sConfigMgr->GetBoolDefault("NpcBot.WanderingBots.Classes.Warlock.Enable", true); + _enableclass_wander_druid = sConfigMgr->GetBoolDefault("NpcBot.WanderingBots.Classes.Druid.Enable", true); + _enableclass_wander_blademaster = false; // sConfigMgr->GetBoolDefault("NpcBot.WanderingBots.Classes.Blademaster.Enable", false); + _enableclass_wander_sphynx = sConfigMgr->GetBoolDefault("NpcBot.WanderingBots.Classes.ObsidianDestroyer.Enable", true); + _enableclass_wander_archmage = sConfigMgr->GetBoolDefault("NpcBot.WanderingBots.Classes.Archmage.Enable", true); + _enableclass_wander_dreadlord = sConfigMgr->GetBoolDefault("NpcBot.WanderingBots.Classes.Dreadlord.Enable", true); + _enableclass_wander_spellbreaker= sConfigMgr->GetBoolDefault("NpcBot.WanderingBots.Classes.SpellBreaker.Enable", true); + _enableclass_wander_darkranger = sConfigMgr->GetBoolDefault("NpcBot.WanderingBots.Classes.DarkRanger.Enable", true); + _enableclass_wander_necromancer = sConfigMgr->GetBoolDefault("NpcBot.WanderingBots.Classes.Necromancer.Enable", true); + _enableclass_wander_seawitch = sConfigMgr->GetBoolDefault("NpcBot.WanderingBots.Classes.SeaWitch.Enable", true); + _enableclass_wander_cryptlord = sConfigMgr->GetBoolDefault("NpcBot.WanderingBots.Classes.CryptLord.Enable", true); + _enrageOnDismiss = sConfigMgr->GetBoolDefault("NpcBot.EnrageOnDismiss", true); + _botStatLimits = sConfigMgr->GetBoolDefault("NpcBot.Stats.Limits.Enable", false); + _botStatLimits_dodge = sConfigMgr->GetFloatDefault("NpcBot.Stats.Limits.Dodge", 95.0f); + _botStatLimits_parry = sConfigMgr->GetFloatDefault("NpcBot.Stats.Limits.Parry", 95.0f); + _botStatLimits_block = sConfigMgr->GetFloatDefault("NpcBot.Stats.Limits.Block", 95.0f); + _botStatLimits_crit = sConfigMgr->GetFloatDefault("NpcBot.Stats.Limits.Crit", 95.0f); + _desiredWanderingBotsCount = sConfigMgr->GetIntDefault("NpcBot.WanderingBots.Continents.Count", 0); + _mult_xpgain_wanderer = sConfigMgr->GetFloatDefault("NpcBot.WanderingBots.Continents.XPGain", 1.0f); + _enableWanderingBotsBG = sConfigMgr->GetBoolDefault("NpcBot.WanderingBots.BG.Enable", false); + _enableConfigLevelCapBG = sConfigMgr->GetBoolDefault("NpcBot.WanderingBots.BG.CapLevel", false); + _enableConfigLevelCapBGFirst = sConfigMgr->GetBoolDefault("NpcBot.WanderingBots.BG.CapLevelByFirstPlayer", false); + _targetBGPlayersPerTeamCount_AV = sConfigMgr->GetIntDefault("NpcBot.WanderingBots.BG.TargetTeamPlayersCount.AV", 0); + _targetBGPlayersPerTeamCount_WS = sConfigMgr->GetIntDefault("NpcBot.WanderingBots.BG.TargetTeamPlayersCount.WS", 8); + _targetBGPlayersPerTeamCount_AB = sConfigMgr->GetIntDefault("NpcBot.WanderingBots.BG.TargetTeamPlayersCount.AB", 12); + _targetBGPlayersPerTeamCount_EY = sConfigMgr->GetIntDefault("NpcBot.WanderingBots.BG.TargetTeamPlayersCount.EY", 0); + _targetBGPlayersPerTeamCount_SA = sConfigMgr->GetIntDefault("NpcBot.WanderingBots.BG.TargetTeamPlayersCount.SA", 0); + _targetBGPlayersPerTeamCount_IC = sConfigMgr->GetIntDefault("NpcBot.WanderingBots.BG.TargetTeamPlayersCount.IC", 0); + _bothk_enable = sConfigMgr->GetBoolDefault("NpcBot.HK.Enable", true); + _bothk_message_enable = sConfigMgr->GetBoolDefault("NpcBot.HK.Message.Enable", false); + _bothk_achievements_enable = sConfigMgr->GetBoolDefault("NpcBot.HK.Achievements.Enable", false); + _bothk_rate_honor = sConfigMgr->GetFloatDefault("NpcBot.HK.Rate.Honor", 1.0); + + if (reload) + BotLogger::Log(NPCBOT_LOG_CONFIG_RELOAD, uint32(0)); + + _max_npcbots = {}; + std::string max_npcbots_by_levels = sConfigMgr->GetStringDefault("NpcBot.MaxBots", "1,1,1,1,1,1,1,1,1"); + std::vector toks0 = Trinity::Tokenize(max_npcbots_by_levels, ',', false); + ASSERT(toks0.size() == BracketsCount, "NpcBot.MaxBots must have exactly %u values", uint32(BracketsCount)); + for (decltype(toks0)::size_type i = 0; i != toks0.size(); ++i) + { + Optional val = Trinity::StringTo(toks0[i]); + if (val == std::nullopt) + TC_LOG_ERROR("server.loading", "NpcBot.MaxBots contains invalid uint8 value '{}', set to default", toks0[i]); + uint8 uval = val.value_or(uint8(0)); + if (i > 0) + { + uint8 prev = _max_npcbots[i - 1]; + if (prev > uval) + { + TC_LOG_WARN("server.loading", "NpcBot.MaxBots value at offset {} is {} which is lower than previous value {}!", uint32(i), uint32(uval), uint32(prev)); + //uval = prev; + } + if (uval >= MAX_RAID_SIZE) + { + TC_LOG_ERROR("server.loading", "NpcBot.MaxBots value at offset {} is {} > 39, enforcing max value!", uint32(i), uint32(uval)); + uval = uint8(MAX_RAID_SIZE - 1); + } + } + _max_npcbots[i] = uval; + } + + _mult_dmg_levels.clear(); + std::string mult_dps_by_levels = sConfigMgr->GetStringDefault("NpcBot.Mult.Damage.Levels", "1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0"); + std::vector toks1 = Trinity::Tokenize(mult_dps_by_levels, ',', false); + ASSERT(toks1.size() >= BracketsCount, "NpcBot.Mult.Damage.Levels must have at least %u values", uint32(BracketsCount)); + for (decltype(toks1)::size_type i = 0; i != toks1.size(); ++i) + { + Optional val = Trinity::StringTo(toks1[i]); + if (val == std::nullopt) + TC_LOG_ERROR("server.loading", "NpcBot.Mult.Damage.Levels contains invalid float value '{}', set to default", toks1[i]); + float fval = val.value_or(1.0f); + RoundToInterval(fval, 0.1f, 10.f); + _mult_dmg_levels.push_back(fval); + } + + _botwanderer_pct_level_brackets = {}; + std::string wanderers_by_levels = sConfigMgr->GetStringDefault("NpcBot.WanderingBots.Continents.Levels", "20,15,15,10,10,15,15,0,0"); + std::vector toks2 = Trinity::Tokenize(wanderers_by_levels, ',', false); + ASSERT(toks2.size() == BracketsCount, "NpcBot.WanderingBots.Continents.Levels must have exactly %u values", uint32(BracketsCount)); + uint32 total_pct = 0; + for (decltype(toks2)::size_type i = 0; i != toks2.size(); ++i) + { + Optional val = Trinity::StringTo(toks2[i]); + if (val == std::nullopt) + TC_LOG_ERROR("server.loading", "NpcBot.Mult.Damage.Levels contains invalid uint32 value '{}', set to default", toks2[i]); + uint32 uval = val.value_or(uint32(0)); + total_pct += uval; + _botwanderer_pct_level_brackets[i] = uval; + } + ASSERT(total_pct == 100u, "NpcBot.WanderingBots.Continents.Levels sum of values must be exactly 100!"); + + _enabled_wander_node_maps.clear(); + std::string enabled_wander_node_maps = sConfigMgr->GetStringDefault("NpcBot.WanderingBots.Continents.Maps", "0,1,530,571"); + std::vector toks3 = Trinity::Tokenize(enabled_wander_node_maps, ',', false); + for (decltype(toks3)::size_type i = 0; i != toks3.size(); ++i) + { + Optional val = Trinity::StringTo(toks3[i]); + if (val == std::nullopt) + { + TC_LOG_ERROR("server.loading", "NpcBot.WanderingBots.Continents.Maps contains invalid uint32 value '{}', skipped", toks3[i]); + continue; + } + uint32 uval = val.value_or(uint32(0)); + MapEntry const* mapEntry = sMapStore.LookupEntry(uval); + if (!mapEntry || !mapEntry->IsContinent()) + { + TC_LOG_ERROR("server.loading", "NpcBot.WanderingBots.Continents.Maps contains invalid continent map id '{}', skipped", uval); + continue; + } + _enabled_wander_node_maps.push_back(uval); + } + if (_enabled_wander_node_maps.empty()) + { + TC_LOG_ERROR("server.loading", "NpcBot.WanderingBots.Continents.Maps does not provide any valid maps! Wandering bots will not be spawned!"); + _desiredWanderingBotsCount = 0; + } + + _disabled_instance_maps.clear(); + std::string disabled_instance_maps = sConfigMgr->GetStringDefault("NpcBot.DisableInstances", ""); + std::vector toks4 = Trinity::Tokenize(disabled_instance_maps, ',', false); + for (decltype(toks4)::size_type i = 0; i != toks4.size(); ++i) + { + Optional val = Trinity::StringTo(toks4[i]); + if (val == std::nullopt) + { + TC_LOG_ERROR("server.loading", "NpcBot.DisableInstances contains invalid uint32 value '{}', skipped", toks4[i]); + continue; + } + uint32 uval = val.value_or(uint32(0)); + MapEntry const* mapEntry = sMapStore.LookupEntry(uval); + if (!mapEntry || !mapEntry->IsDungeon()) + { + TC_LOG_ERROR("server.loading", "NpcBot.DisableInstances contains invalid instance map id '{}', skipped", uval); + continue; + } + _disabled_instance_maps.push_back(uval); + } + + //limits + _mountLevel100 = std::max(_mountLevel100, _mountLevel60); + RoundToInterval(_mult_dmg_physical, 0.1f, 10.f); + RoundToInterval(_mult_dmg_spell, 0.1f, 10.f); + RoundToInterval(_mult_healing, 0.1f, 10.f); + RoundToInterval(_mult_hp, 0.1f, 10.f); + RoundToInterval(_mult_dmg_wanderer, 0.1f, 10.f); + RoundToInterval(_mult_healing_wanderer, 0.1f, 10.f); + RoundToInterval(_mult_hp_wanderer, 0.1f, 10.f); + RoundToInterval(_mult_speed_wanderer, 0.1f, 10.f); + RoundToInterval(_mult_xpgain_wanderer, 0.0f, 100.f); + RoundToInterval(_mult_dmg_warrior, 0.1f, 10.f); + RoundToInterval(_mult_dmg_paladin, 0.1f, 10.f); + RoundToInterval(_mult_dmg_hunter, 0.1f, 10.f); + RoundToInterval(_mult_dmg_rogue, 0.1f, 10.f); + RoundToInterval(_mult_dmg_priest, 0.1f, 10.f); + RoundToInterval(_mult_dmg_deathknight, 0.1f, 10.f); + RoundToInterval(_mult_dmg_shaman, 0.1f, 10.f); + RoundToInterval(_mult_dmg_mage, 0.1f, 10.f); + RoundToInterval(_mult_dmg_warlock, 0.1f, 10.f); + RoundToInterval(_mult_dmg_druid, 0.1f, 10.f); + RoundToInterval(_mult_dmg_blademaster, 0.1f, 10.f); + RoundToInterval(_mult_dmg_obsidiandestroyer, 0.1f, 10.f); + RoundToInterval(_mult_dmg_archmage, 0.1f, 10.f); + RoundToInterval(_mult_dmg_dreadlord, 0.1f, 10.f); + RoundToInterval(_mult_dmg_spellbreaker, 0.1f, 10.f); + RoundToInterval(_mult_dmg_darkranger, 0.1f, 10.f); + RoundToInterval(_mult_dmg_necromancer, 0.1f, 10.f); + RoundToInterval(_mult_dmg_seawitch, 0.1f, 10.f); + RoundToInterval(_mult_dmg_cryptlord, 0.1f, 10.f); + RoundToInterval(_bothk_rate_honor, 0.1f, 10.f); +} + +void BotMgr::ResolveConfigConflicts() +{ + uint8 dpsFlags = /*_tankingTargetIconFlags | _offTankingTargetIconFlags | */_dpsTargetIconFlags | _rangedDpsTargetIconFlags; + if (uint8 interFlags = (_noDpsTargetIconFlags & dpsFlags)) + { + _noDpsTargetIconFlags &= ~interFlags; + TC_LOG_ERROR("server.loading", "BotMgr::LoadConfig: NoDPSTargetIconMask intersects with dps targets flags {:#X}! Removed, new mask: {:#X}", + uint32(interFlags), uint32(_noDpsTargetIconFlags)); + } + + if (!_enabled_wander_node_maps.empty()) + { + uint8 minbotlevel = DEFAULT_MAX_LEVEL; + uint8 maxbotlevel = 0; + for (uint32 mapid : _enabled_wander_node_maps) + { + minbotlevel = std::min(minbotlevel, BotDataMgr::GetMinLevelForMapId(mapid)); + maxbotlevel = std::max(maxbotlevel, BotDataMgr::GetMaxLevelForMapId(mapid)); + } + for (int8 j = minbotlevel / 10 - 1; j >= 0; --j) + { + if (_botwanderer_pct_level_brackets[j] > 0) + { + uint32 pct = _botwanderer_pct_level_brackets[j]; + _botwanderer_pct_level_brackets[minbotlevel / 10] += pct; + _botwanderer_pct_level_brackets[j] = 0; + TC_LOG_WARN("server.loading", "NpcBot.WanderingBots.Continents.Levels conflicts with NpcBot.WanderingBots.Continents.Maps: no map for levels {}-{}! Transferring extra {}% to levels {}-{}", + uint32(j ? j * 10 : 1), uint32(j * 10 + 9), pct, std::max(minbotlevel / 10 * 10, 1), uint32(minbotlevel / 10 * 10 + 9)); + } + } + for (uint8 i = maxbotlevel / 10 + 1; i < _botwanderer_pct_level_brackets.size(); ++i) + { + if (_botwanderer_pct_level_brackets[i] > 0) + { + uint32 pct = _botwanderer_pct_level_brackets[i]; + _botwanderer_pct_level_brackets[maxbotlevel / 10] += pct; + _botwanderer_pct_level_brackets[i] = 0; + TC_LOG_WARN("server.loading", "NpcBot.WanderingBots.Continents.Levels conflicts with NpcBot.WanderingBots.Continents.Maps: no map for levels {}-{}! Transferring extra {}% to levels {}-{}", + uint32(i ? i * 10 : 1), uint32(i * 10 + 9), pct, std::max(maxbotlevel, 1), uint32(maxbotlevel + 9)); + } + } + } +} + +uint8 BotMgr::GetNpcBotsCount() const +{ + //if (!inWorldOnly) + return (uint8)_bots.size(); + + //CRITICAL SECTION + //inWorldOnly is only for one-shot cases (opcodes, etc.) + //maybe convert to (bot && bot->isInWorld()) ? + //uint8 count = 0; + //for (BotMap::const_iterator itr = _bots.begin(); itr != _bots.end(); ++itr) + // if (ObjectAccessor::GetObjectInWorld(itr->first, (Creature*)nullptr)) + // ++count; + //return count; +} + +uint8 BotMgr::GetNpcBotsCountByRole(uint32 roles) const +{ + uint8 count = 0; + for (BotMap::const_iterator itr = _bots.begin(); itr != _bots.end(); ++itr) + if (itr->second && (roles & itr->second->GetBotRoles())) + ++count; + return count; +} + +uint8 BotMgr::GetNpcBotsCountByVehicleEntry(uint32 creEntry) const +{ + uint8 count = 0; + for (BotMap::const_iterator itr = _bots.begin(); itr != _bots.end(); ++itr) + if (itr->second && itr->second->GetVehicle() && itr->second->GetVehicleBase()->GetEntry() == creEntry) + ++count; + return count; +} + +uint8 BotMgr::GetNpcBotSlot(Creature const* bot) const +{ + uint8 count = 0; + for (BotMap::const_iterator itr = _bots.begin(); itr != _bots.end(); ++itr) + { + ++count; + if (itr->second == bot) + return count; + } + return 1; +} + +uint8 BotMgr::GetNpcBotSlotByRole(uint32 roles, Creature const* bot) const +{ + uint8 count = 0; + for (BotMap::const_iterator itr = _bots.begin(); itr != _bots.end(); ++itr) + { + if (roles & itr->second->GetBotRoles()) + { + if (!(roles == BOT_ROLE_DPS && (itr->second->GetBotRoles() & BOT_ROLE_TANK))) + ++count; + if (itr->second == bot) + return count; + } + } + return 1; +} + +uint32 BotMgr::GetAllNpcBotsClassMask() const +{ + uint32 classMask = 0; + for (BotMap::const_iterator itr = _bots.begin(); itr != _bots.end(); ++itr) + classMask |= (1 << (BotMgr::GetBotEquipmentClass(itr->second->GetBotClass()) - 1)); + + return classMask; +} + +bool BotMgr::IsNpcBotModEnabled() +{ + return _enableNpcBots; +} + +bool BotMgr::IsNpcBotLogEnabled() +{ + return _logToDB; +} + +bool BotMgr::IsNpcBotDungeonFinderEnabled() +{ + return _enableDungeonFinder; +} + +bool BotMgr::DisplayEquipment() +{ + return _displayEquipment; +} + +bool BotMgr::ShowEquippedCloak() +{ + return _showCloak; +} + +bool BotMgr::ShowEquippedHelm() +{ + return _showHelm; +} + +bool BotMgr::SendEquipListItems() +{ + return _sendEquipListItems; +} + +bool BotMgr::IsGearBankEnabled() +{ + return _enableBotGearBank; +} + +bool BotMgr::IsTransmogEnabled() +{ + return _transmog_enable; +} +bool BotMgr::MixArmorClasses() +{ + return _transmog_mixArmorClasses; +} +bool BotMgr::MixWeaponClasses() +{ + return _transmog_mixWeaponClasses; +} +bool BotMgr::MixWeaponInventoryTypes() +{ + return _transmog_mixWeaponInvTypes; +} +bool BotMgr::TransmogUseEquipmentSlots() +{ + return _transmog_useEquipmentSlots; +} + +bool BotMgr::IsClassEnabled(uint8 m_class) +{ + switch (m_class) + { + case BOT_CLASS_WARRIOR: + return _enableclass_warrior; + case BOT_CLASS_PALADIN: + return _enableclass_paladin; + case BOT_CLASS_HUNTER: + return _enableclass_hunter; + case BOT_CLASS_ROGUE: + return _enableclass_rogue; + case BOT_CLASS_PRIEST: + return _enableclass_priest; + case BOT_CLASS_DEATH_KNIGHT: + return _enableclass_deathknight; + case BOT_CLASS_SHAMAN: + return _enableclass_shaman; + case BOT_CLASS_MAGE: + return _enableclass_mage; + case BOT_CLASS_WARLOCK: + return _enableclass_warlock; + case BOT_CLASS_DRUID: + return _enableclass_druid; + case BOT_CLASS_BM: + return _enableclass_blademaster; + case BOT_CLASS_SPHYNX: + return _enableclass_sphynx; + case BOT_CLASS_ARCHMAGE: + return _enableclass_archmage; + case BOT_CLASS_DREADLORD: + return _enableclass_dreadlord; + case BOT_CLASS_SPELLBREAKER: + return _enableclass_spellbreaker; + case BOT_CLASS_DARK_RANGER: + return _enableclass_darkranger; + case BOT_CLASS_NECROMANCER: + return _enableclass_necromancer; + case BOT_CLASS_SEA_WITCH: + return _enableclass_seawitch; + case BOT_CLASS_CRYPT_LORD: + return _enableclass_cryptlord; + default: + return true; + } +} + +bool BotMgr::IsWanderingClassEnabled(uint8 m_class) +{ + switch (m_class) + { + case BOT_CLASS_WARRIOR: + return _enableclass_wander_warrior; + case BOT_CLASS_PALADIN: + return _enableclass_wander_paladin; + case BOT_CLASS_HUNTER: + return _enableclass_wander_hunter; + case BOT_CLASS_ROGUE: + return _enableclass_wander_rogue; + case BOT_CLASS_PRIEST: + return _enableclass_wander_priest; + case BOT_CLASS_DEATH_KNIGHT: + return _enableclass_wander_deathknight; + case BOT_CLASS_SHAMAN: + return _enableclass_wander_shaman; + case BOT_CLASS_MAGE: + return _enableclass_wander_mage; + case BOT_CLASS_WARLOCK: + return _enableclass_wander_warlock; + case BOT_CLASS_DRUID: + return _enableclass_wander_druid; + case BOT_CLASS_BM: + return _enableclass_wander_blademaster; + case BOT_CLASS_SPHYNX: + return _enableclass_wander_sphynx; + case BOT_CLASS_ARCHMAGE: + return _enableclass_wander_archmage; + case BOT_CLASS_DREADLORD: + return _enableclass_wander_dreadlord; + case BOT_CLASS_SPELLBREAKER: + return _enableclass_wander_spellbreaker; + case BOT_CLASS_DARK_RANGER: + return _enableclass_wander_darkranger; + case BOT_CLASS_NECROMANCER: + return _enableclass_wander_necromancer; + case BOT_CLASS_SEA_WITCH: + return _enableclass_wander_seawitch; + case BOT_CLASS_CRYPT_LORD: + return _enableclass_wander_cryptlord; + default: + return true; + } +} + +bool BotMgr::HideBotSpawns() +{ + return _hideSpawns; +} +bool BotMgr::IsEnrageOnDimissEnabled() +{ + return _enrageOnDismiss; +} +bool BotMgr::IsBotStatsLimitsEnabled() +{ + return _botStatLimits; +} +bool BotMgr::IsPvPEnabled() +{ + return _botPvP; +} +bool BotMgr::IsFoodInterruptedByMovement() +{ + return _botMovementFoodInterrupt; +} +bool BotMgr::FilterRaces() +{ + return _filterRaces; +} +bool BotMgr::IsBotGenerationEnabledBGs() +{ + return _enableWanderingBotsBG; +} +bool BotMgr::IsBotLevelCappedByConfigBG() +{ + return _enableConfigLevelCapBG; +} +bool BotMgr::IsBotLevelCappedByConfigBGFirstPlayer() +{ + return _enableConfigLevelCapBGFirst; +} +bool BotMgr::IsBotGenerationEnabledWorldMapId(uint32 mapId) +{ + return std::find(std::cbegin(_enabled_wander_node_maps), std::cend(_enabled_wander_node_maps), mapId) != std::cend(_enabled_wander_node_maps); +} +bool BotMgr::IsBotHKEnabled() +{ + return _bothk_enable; +} +bool BotMgr::IsBotHKMessageEnabled() +{ + return _bothk_message_enable; +} +bool BotMgr::IsBotHKAchievementsEnabled() +{ + return _bothk_achievements_enable; +} +uint8 BotMgr::GetMaxClassBots() +{ + return _maxClassNpcBots; +} +uint8 BotMgr::GetMaxAccountBots() +{ + return _maxAccountNpcBots; +} +uint8 BotMgr::GetHealTargetIconFlags() +{ + return _healTargetIconFlags; +} +uint8 BotMgr::GetTankTargetIconFlags() +{ + return _tankingTargetIconFlags; +} +uint8 BotMgr::GetOffTankTargetIconFlags() +{ + return _offTankingTargetIconFlags; +} +uint8 BotMgr::GetDPSTargetIconFlags() +{ + return _dpsTargetIconFlags; +} +uint8 BotMgr::GetRangedDPSTargetIconFlags() +{ + return _rangedDpsTargetIconFlags; +} +uint8 BotMgr::GetNoDPSTargetIconFlags() +{ + return _noDpsTargetIconFlags; +} +uint32 BotMgr::GetBaseUpdateDelay() +{ + return _npcBotUpdateDelayBase; +} +uint32 BotMgr::GetOwnershipExpireTime() +{ + return _npcBotOwnerExpireTime; +} +uint8 BotMgr::GetOwnershipExpireMode() +{ + return _npcBotOwnerExpireMode; +} +uint32 BotMgr::GetDesiredWanderingBotsCount() +{ + return _desiredWanderingBotsCount; +} +uint32 BotMgr::GetBGTargetTeamPlayersCount(BattlegroundTypeId bgTypeId) +{ + switch (bgTypeId) + { + case BATTLEGROUND_AV: + return _targetBGPlayersPerTeamCount_AV; + case BATTLEGROUND_WS: + return _targetBGPlayersPerTeamCount_WS; + case BATTLEGROUND_AB: + return _targetBGPlayersPerTeamCount_AB; + case BATTLEGROUND_EY: + return _targetBGPlayersPerTeamCount_EY; + case BATTLEGROUND_SA: + return _targetBGPlayersPerTeamCount_SA; + case BATTLEGROUND_IC: + return _targetBGPlayersPerTeamCount_IC; + default: + return 0; + } +} +float BotMgr::GetBotHKHonorRate() +{ + return _bothk_rate_honor; +} +float BotMgr::GetBotStatLimitDodge() +{ + return _botStatLimits_dodge; +} +float BotMgr::GetBotStatLimitParry() +{ + return _botStatLimits_parry; +} +float BotMgr::GetBotStatLimitBlock() +{ + return _botStatLimits_block; +} +float BotMgr::GetBotStatLimitCrit() +{ + return _botStatLimits_crit; +} + +uint8 BotMgr::GetNpcBotXpReduction() +{ + return _xpReductionAmount; +} +uint8 BotMgr::GetNpcBotXpReductionStartingNumber() +{ + return _xpReductionStartingNumber; +} + +uint8 BotMgr::GetNpcBotMountLevel60() +{ + return _mountLevel60; +} +uint8 BotMgr::GetNpcBotMountLevel100() +{ + return _mountLevel100; +} + +uint8 BotMgr::GetMaxNpcBots(uint8 level) +{ + return _max_npcbots[std::min(BracketsCount - 1, level / 10)]; +} + +int32 BotMgr::GetBotInfoPacketsLimit() +{ + return _botInfoPacketsLimit; +} + +bool BotMgr::LimitBots(Map const* map) +{ + if (map->IsBattlegroundOrArena()) + return true; + + if (_limitNpcBotsDungeons && map->IsNonRaidDungeon()) + return true; + if (_limitNpcBotsRaids && map->IsRaid()) + return true; + + return false; +} + +bool BotMgr::IsBotContestedPvP(Creature const* bot) +{ + return bot->GetBotAI()->IsContestedPvP(); +} + +void BotMgr::SetBotContestedPvP(Creature const* bot) +{ + bot->GetBotAI()->SetContestedPvP(); +} + +bool BotMgr::CanBotParryWhileCasting(Creature const* bot) +{ + switch (bot->GetBotClass()) + { + case BOT_CLASS_SEA_WITCH: + return true; + default: + return false; + } +} + +bool BotMgr::IsWanderingWorldBot(Creature const* bot) +{ + return bot->IsWandererBot() && (!bot->FindMap() || !bot->GetMap()->GetEntry() || bot->GetMap()->GetEntry()->IsWorldMap()); +} + +void BotMgr::Update(uint32 diff) +{ + //remove temp bots from bot map before updating it + while (!_removeList.empty()) + { + std::list::iterator itr = _removeList.begin(); + + BotMap::iterator bitr = _bots.find(*itr); + ASSERT(bitr != _bots.end()); + _bots.erase(bitr); + + _removeList.erase(itr); + } + + _dpstracker->Update(diff); + + if (!HaveBot()) + return; + + //ObjectGuid guid; + Creature* bot; + bot_ai* ai; + bool partyCombat = IsPartyInCombat(); + bool restrictBots = RestrictBots(_bots.begin()->second, false); + + _aoespots.clear(); + if (partyCombat) + bot_ai::CalculateAoeSpots(_owner, _aoespots); + + for (BotMap::const_iterator itr = _bots.begin(); itr != _bots.end(); ++itr) + { + //guid = itr->first; + bot = itr->second; + ai = bot->GetBotAI(); + + if (ai->IAmFree()) + continue; + + if (!bot->IsInWorld()) + { + ai->CommonTimers(diff); + continue; + } + + if (partyCombat == false || _owner->InBattleground()) + ai->UpdateReviveTimer(diff); + + //bot->IsAIEnabled = true; + + if (ai->GetReviveTimer() <= diff) + { + if (bot->IsInMap(_owner) && !bot->IsAlive() && !ai->IsDuringTeleport() && _owner->IsAlive() && !_owner->IsInCombat() && + !_owner->IsBeingTeleported() && !_owner->InArena() && !_owner->IsInFlight() && + !_owner->HasUnitFlag2(UNIT_FLAG2_FEIGN_DEATH) && + !_owner->HasInvisibilityAura() && !_owner->HasStealthAura()) + { + _reviveBot(bot); + continue; + } + + ai->SetReviveTimer(urand(1000, 5000)); + } + + if (_owner->IsAlive() && (bot->IsAlive() || restrictBots) && !ai->IsTempBot() && !ai->IsDuringTeleport() && + (restrictBots || bot->GetMap() != _owner->GetMap() || + (!bot->GetBotAI()->HasBotCommandState(BOT_COMMAND_STAY) && _owner->GetDistance(bot) > SIZE_OF_GRIDS))) + { + //_owner->m_Controlled.erase(bot); + TeleportBot(bot, _owner->GetMap(), _owner, _quickrecall); + continue; + } + + ai->canUpdate = true; + bot->Update(diff); + ai->canUpdate = false; + } + + if (_quickrecall) + { + _quickrecall = false; + _botsHidden = false; + } +} + +bool BotMgr::IsMapAllowedForBots(Map const* map) const +{ + if ((!_enableNpcBotsBGs && map->IsBattleground()) || + (!_enableNpcBotsArenas && map->IsBattleArena()) || + (!_enableNpcBotsDungeons && map->IsNonRaidDungeon()) || + (!_enableNpcBotsRaids && map->IsRaid())) + return false; + + if (map->IsDungeon() && !_disabled_instance_maps.empty() && std::find(_disabled_instance_maps.cbegin(), _disabled_instance_maps.cend(), map->GetId()) != _disabled_instance_maps.cend()) + return false; + + return true; +} + +bool BotMgr::RestrictBots(Creature const* bot, bool add) const +{ + if (!_owner->FindMap()) + return true; + + if (_owner->IsInFlight()) + return true; + + if (_botsHidden) + return true; + + Map const* currMap = _owner->GetMap(); + + if (!IsMapAllowedForBots(currMap)) + return true; + + if (LimitBots(currMap)) + { + //if bot is not in instance group - deny (only if trying to teleport to instance) + if (add) + { + Group const* gr = _owner->GetGroup(); + if (!gr || !gr->IsMember(bot->GetGUID())) + return true; + + //teleporting raid member bot to non-rain dungeon: prioritize owner sub-group members + if (gr->isRaidGroup() && currMap->IsNonRaidDungeon()) + { + uint32 max_members = currMap->ToInstanceMap()->GetMaxPlayers(); + if (gr->GetMembersCount() > max_members) + { + uint8 owner_subgroup = gr->GetMemberGroup(_owner->GetGUID()); + if (owner_subgroup != gr->GetMemberGroup(bot->GetGUID())) + { + const std::vector members = GetAllGroupMembers(gr); + uint32 sub_members = 0; + uint32 sub_members_inside = 0; + for (auto const& mslot : gr->GetMemberSlots()) + { + if (mslot.group == owner_subgroup) + { + decltype(members)::const_iterator it = std::find_if(members.cbegin(), members.cend(), [&](Unit const* unit) { return mslot.guid == unit->GetGUID(); }); + if (it != members.cend() && (*it)->IsInMap(_owner)) + ++sub_members_inside; + if (++sub_members >= max_members) + break; + } + } + if (sub_members >= max_members || sub_members_inside < sub_members) + return true; + } + } + } + } + + uint32 max_players = 0; + if (currMap->IsDungeon()) + max_players = currMap->ToInstanceMap()->GetMaxPlayers(); + else if (currMap->IsBattleground()) + max_players = _owner->GetBattleground()->GetMaxPlayersPerTeam(); + else if (currMap->IsBattleArena()) + max_players = _owner->GetBattleground()->GetArenaType(); + + if (max_players && currMap->GetPlayersCountExceptGMs() + uint32(add) > max_players) + return true; + } + + return false; +} + +bool BotMgr::IsPartyInCombat() const +{ + if (_owner->IsInCombat()) + return true; + + for (BotMap::const_iterator itr = _bots.begin(); itr != _bots.end(); ++itr) + { + if (!itr->second->IsInWorld()) + continue; + if (itr->second->IsInCombat()) + return true; + if (Unit const* pet = itr->second->GetBotsPet()) + if (pet->IsInCombat()) + return true; + } + + return false; +} + +bool BotMgr::HasBotClass(uint8 botclass) const +{ + for (BotMap::const_iterator itr = _bots.begin(); itr != _bots.end(); ++itr) + if (itr->second->GetBotClass() == botclass) + return true; + + return false; +} + +bool BotMgr::HasBotWithSpec(uint8 spec, bool alive) const +{ + for (BotMap::const_iterator itr = _bots.cbegin(); itr != _bots.cend(); ++itr) + if (itr->second->GetBotAI()->GetSpec() == spec && (!alive || itr->second->IsAlive())) + return true; + + return false; +} + +bool BotMgr::HasBotPetType(uint32 petType) const +{ + for (BotMap::const_iterator itr = _bots.begin(); itr != _bots.end(); ++itr) + if (itr->second->GetBotsPet() && itr->second->GetBotAI()->GetAIMiscValue(BOTAI_MISC_PET_TYPE) == petType) + return true; + + return false; +} + +bool BotMgr::IsBeingResurrected(WorldObject const* corpse) const +{ + std::vector casters; + if (_owner->IsNonMeleeSpellCast(false, true, true)) + casters.push_back(_owner); + for (BotMap::const_iterator itr = _bots.begin(); itr != _bots.end(); ++itr) + { + if (itr->second->IsNonMeleeSpellCast(false, true, true)) + casters.push_back(itr->second); + } + + if (Group const* group = _owner->GetGroup()) + { + for (GroupReference const* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player const* player = itr->GetSource(); + if (!player || player == _owner || player->FindMap() != corpse->GetMap()) + continue; + + if (player->IsNonMeleeSpellCast(false, true, true)) + casters.push_back(player); + + if (player->HaveBot()) + { + BotMap const* map = player->GetBotMgr()->GetBotMap(); + for (BotMap::const_iterator bitr = map->begin(); bitr != map->end(); ++bitr) + { + if (bitr->second->IsNonMeleeSpellCast(false, true, true)) + casters.push_back(bitr->second); + } + } + } + } + + for (Unit const* caster : casters) + { + if (Spell const* spell = caster->GetCurrentSpell(CURRENT_GENERIC_SPELL)) + { + if (corpse->GetGUID() == (corpse->ToCorpse() ? spell->m_targets.GetCorpseTargetGUID() : spell->m_targets.GetUnitTargetGUID())) + return true; + } + } + + return false; +} + +void BotMgr::_reviveBot(Creature* bot, WorldLocation* dest) +{ + if (bot->IsAlive() || !bot->IsInWorld()) + return; + + if (!bot->GetBotAI()->IAmFree()) + { + if (!dest) + bot->CastSpell(bot, COSMETIC_RESURRECTION, false); + + if (!dest) + dest = bot->GetBotOwner(); + + bot->NearTeleportTo(dest->GetPositionX(), dest->GetPositionY(), dest->GetPositionZ(), dest->GetOrientation()); + //some weird pos manipulation + if (dest != bot) + bot->Relocate(dest); + } + + bot->SetDisplayId(bot->GetNativeDisplayId()); + bot->ReplaceAllNpcFlags(NPCFlags(bot->GetCreatureTemplate()->npcflag)); + bot->ClearUnitState(UNIT_STATE_ALL_ERASABLE); + bot->ReplaceAllUnitFlags(UnitFlags(0)); + bot->SetLootRecipient(nullptr); + bot->ResetPlayerDamageReq(); + bot->SetPvP(bot->GetBotOwner()->IsPvP()); + bot->Motion_Initialize(); + bot->setDeathState(ALIVE); + //bot->GetBotAI()->Reset(); + bot->GetBotAI()->SetShouldUpdateStats(); + + uint8 restore_factor = (bot->IsWandererBot() || (!bot->GetBotAI()->IAmFree() && bot->GetBotOwner()->InBattleground())) ? 1 : 4; + bot->SetHealth(bot->GetMaxHealth() / restore_factor); //25% of max health + if (bot->GetMaxPower(POWER_MANA) > 1) + bot->SetPower(POWER_MANA, bot->GetMaxPower(POWER_MANA) / restore_factor); //25% of max mana + + if (!bot->GetBotAI()->IAmFree() && !bot->GetBotAI()->HasBotCommandState(BOT_COMMAND_MASK_UNMOVING)) + bot->GetBotAI()->SetBotCommandState(BOT_COMMAND_FOLLOW, true); +} + +Creature* BotMgr::GetBot(ObjectGuid guid) const +{ + BotMap::const_iterator itr = _bots.find(guid); + return itr != _bots.end() ? itr->second : nullptr; +} + +Creature* BotMgr::GetBotByName(std::string_view name) const +{ + std::wstring wname; + if (Utf8toWStr(name, wname)) + { + wstrToLower(wname); + for (BotMap::const_iterator itr = _bots.begin(); itr != _bots.end(); ++itr) + { + if (!itr->second) + continue; + + std::string basename = itr->second->GetName(); + if (CreatureLocale const* creatureInfo = sObjectMgr->GetCreatureLocale(itr->second->GetEntry())) + { + uint32 loc = _owner->GetSession()->GetSessionDbLocaleIndex(); + if (creatureInfo->Name.size() > loc && !creatureInfo->Name[loc].empty()) + basename = creatureInfo->Name[loc]; + } + + std::wstring wbname; + if (!Utf8toWStr(basename, wbname)) + continue; + + wstrToLower(wbname); + if (wbname == wname) + return itr->second; + } + } + + return nullptr; +} + +std::list BotMgr::GetAllBotsByClass(uint8 botclass) const +{ + std::list foundBots; + for (BotMap::const_iterator itr = _bots.begin(); itr != _bots.end(); ++itr) + { + if (!itr->second || !itr->second->IsInWorld() || !itr->second->IsAlive()) + continue; + + if (itr->second->GetBotClass() == botclass) + foundBots.push_back(itr->second); + } + + return foundBots; +} + +void BotMgr::OnOwnerSetGameMaster(bool on) +{ + Creature* bot; + for (BotMap::const_iterator itr = _bots.begin(); itr != _bots.end(); ++itr) + { + bot = itr->second; + if (!bot) + continue; + + bot->SetFaction(_owner->GetFaction()); + //bot->getHostileRefManager().setOnlineOfflineState(!on); + bot->SetByteValue(UNIT_FIELD_BYTES_2, 1, _owner->GetByteValue(UNIT_FIELD_BYTES_2, 1)); //pvp state + + if (on && bot->IsInWorld()) + bot->CombatStop(true); + + if (Unit* pet = bot->GetBotsPet()) + { + pet->SetFaction(_owner->GetFaction()); + //pet->getHostileRefManager().setOnlineOfflineState(!on); + pet->SetByteValue(UNIT_FIELD_BYTES_2, 1, _owner->GetByteValue(UNIT_FIELD_BYTES_2, 1)); //pvp state + + if (on) + pet->CombatStop(true); + } + } +} + +void BotMgr::OnTeleportFar(uint32 mapId, float x, float y, float z, float ori) +{ + Map* newMap = sMapMgr->CreateBaseMap(mapId); + Creature* bot; + Position pos; + pos.Relocate(x, y, z, ori); + + for (BotMap::const_iterator itr = _bots.begin(); itr != _bots.end(); ++itr) + { + bot = itr->second; + + if (bot->IsTempBot()) + continue; + + //_owner->m_Controlled.erase(bot); + TeleportBot(bot, newMap, &pos); + } +} + +void BotMgr::_teleportBot(Creature* bot, Map* newMap, float x, float y, float z, float ori, bool quick, bool reset, bot_ai* detached_ai) +{ + bot_ai* botai = detached_ai ? detached_ai : bot->GetBotAI(); + ASSERT(botai); + botai->AbortTeleport(); + botai->SetIsDuringTeleport(true); + botai->KillEvents(true); + bot->m_Events.KillAllEvents(false); + + BotLogger::Log(NPCBOT_LOG_TELEPORT_START, bot, bot->IsInGrid(), bot->IsWandererBot(), botai->CanAppearInWorld(), newMap->GetId(), bool(reset)); + + BotMgr::AddDelayedTeleportCallback([bot, botai, newMap, x, y, z, ori, quick, reset]() { + if (bot->GetVehicle()) + bot->ExitVehicle(); + + if (bot->GetTransport()) + { + bot->ClearUnitState(UNIT_STATE_IGNORE_PATHFINDING); + bot->GetTransport()->RemovePassenger(bot); + } + + Map* mymap = bot->FindMap(); + if (mymap) + { + bot->BotStopMovement(); + + if (mymap != newMap) + { + bot->RemoveAurasByType(SPELL_AURA_MOD_STUN); + bot->RemoveAurasByType(SPELL_AURA_MOD_FEAR); + bot->RemoveAurasByType(SPELL_AURA_MOD_CONFUSE); + bot->RemoveAurasByType(SPELL_AURA_MOD_ROOT); + bot->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TELEPORTED); + } + + bot->InterruptNonMeleeSpells(true); + + if (bot->IsInWorld()) + { + botai->UnsummonAll(!botai->IAmFree() || botai->IsWanderer()); + + if (Battleground* bg = bot->GetBotBG()) + bg->EventBotDroppedFlag(bot); + + bot->CastSpell(bot, COSMETIC_TELEPORT_EFFECT, true); + + if (!bot->IsFreeBot()) + if (InstanceScript* iscr = bot->GetBotOwner()->GetInstanceScript()) + iscr->OnNPCBotLeave(bot); + + bot->RemoveFromWorld(); + } + + ASSERT(bot->GetGUID()); + + bot->RemoveAllGameObjects(); + + bot->m_Events.KillAllEvents(false); + bot->CombatStop(); + bot->ClearComboPoints(); + bot->ClearComboPointHolders(); + + if (bot->IsInGrid()) + mymap->RemoveFromMap(bot, false); + } + + if (botai->IAmFree()) + { + bot->Relocate(x, y, z, ori); + if (bot->FindMap()) + bot->ResetMap(); + bot->SetMap(newMap); + if (!bot->IsWandererBot() && !botai->CanAppearInWorld()) + { + TeleportFinishEvent* delayedTeleportEvent = new TeleportFinishEvent(botai, reset); + std::chrono::milliseconds delay(urand(5000, 8000)); + botai->GetEvents()->AddEvent(delayedTeleportEvent, botai->GetEvents()->CalculateTime(delay)); + botai->SetTeleportFinishEvent(delayedTeleportEvent); + return; + } + + BotLogger::Log(NPCBOT_LOG_TELEPORT_FINISH, bot, bot->IsInGrid(), bot->IsWandererBot(), botai->CanAppearInWorld(), newMap->GetId(), bool(reset)); + + newMap->AddToMap(bot); + if (reset) + botai->Reset(); + botai->SetIsDuringTeleport(false); + botai->ResetContestedPvP(); + + if (newMap->IsBattleground()) + { + Battleground* bg = botai->GetBG(); + if (!bg) + { + BotDataMgr::DespawnWandererBot(bot->GetEntry()); + return; + } + + if (newMap != mymap) + { + //we teleport from base non-instanced map which normally doesn't exist + ASSERT(mymap->GetPlayersCountExceptGMs() == 0); + + bg->AddBot(bot); + } + + if (!bot->IsAlive()) + { + ObjectGuid shGuid = ObjectGuid::Empty; + float mindist = 0.0f; + for (ObjectGuid bgCreGuid : bg->BgCreatures) + { + if (Creature const* bgCre = newMap->GetCreature(bgCreGuid)) + { + if (bgCre->IsSpiritService()) + { + float dist = bot->GetExactDist2d(bgCre); + if (shGuid == ObjectGuid::Empty || dist < mindist) + { + mindist = dist; + shGuid = bgCreGuid; + } + } + } + } + if (shGuid) + bg->AddPlayerToResurrectQueue(shGuid, bot->GetGUID()); + else + { + TC_LOG_ERROR("npcbots", "TeleportBot: Bot {} '{}' can't find SpiritHealer in bg {}!", + bot->GetEntry(), bot->GetName(), bg->GetName()); + } + } + } + + botai->canUpdate = true; + + return; + } + + botai->AbortTeleport(); + + //update group member online state + if (Group* gr = bot->GetBotOwner()->GetGroup()) + if (gr->IsMember(bot->GetGUID())) + gr->SendUpdate(); + + TeleportFinishEvent* finishEvent = new TeleportFinishEvent(botai, reset); + std::chrono::milliseconds delay(quick ? urand(500, 1500) : urand(5000, 8000)); + botai->GetEvents()->AddEvent(finishEvent, botai->GetEvents()->CalculateTime(delay)); + botai->SetTeleportFinishEvent(finishEvent); + }); +} + +void BotMgr::TeleportBot(Creature* bot, Map* newMap, Position const* pos, bool quick, bool reset, bot_ai* detached_ai) +{ + _teleportBot(bot, newMap, pos->GetPositionX(), pos->GetPositionY(), pos->GetPositionZ(), pos->GetOrientation(), quick, reset, detached_ai); +} + +void BotMgr::CleanupsBeforeBotDelete(ObjectGuid guid, uint8 removetype) +{ + BotMap::const_iterator itr = _bots.find(guid); + ASSERT(itr != _bots.end(), "Trying to remove bot which does not belong to this botmgr(b)!!"); + //ASSERT(_owner->IsInWorld(), "Trying to remove bot while not in world(b)!!"); + + Creature* bot = itr->second; + + ASSERT(bot->GetCreator() && bot->GetCreator()->GetGUID() == _owner->GetGUID()); + + RemoveBotFromBGQueue(bot); + if (removetype != BOT_REMOVE_LOGOUT) + RemoveBotFromGroup(bot); + + CleanupsBeforeBotDelete(bot); +} + +void BotMgr::CleanupsBeforeBotDelete(Creature* bot) +{ + //don't allow removing bots while they are teleporting + if (!bot->IsInWorld()) + bot->GetBotAI()->AbortTeleport(); + + if (bot->GetVehicle()) + bot->ExitVehicle(); + + //remove any summons + bot->GetBotAI()->UnsummonAll(false); + bot->AttackStop(); + bot->CombatStopWithPets(true); + + //bot->SetOwnerGUID(ObjectGuid::Empty); + //_owner->m_Controlled.erase(bot); + bot->SetControlledByPlayer(false); + //bot->RemoveUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED); + //bot->RemoveUnitFlag(UNIT_FLAG_PVP_ATTACKABLE); + bot->SetByteValue(UNIT_FIELD_BYTES_2, 1, 0); + bot->SetCreator(nullptr); + //bot->SetCreatorGUID(ObjectGuid::Empty); + + Map* map = bot->FindMap(); + if (!map || map->IsDungeon()) + bot->RemoveFromWorld(); +} + +void BotMgr::RemoveAllBots(uint8 removetype) +{ + while (!_bots.empty()) + RemoveBot(_bots.begin()->second->GetGUID(), removetype); +} +//Bot is being abandoned by player +void BotMgr::RemoveBot(ObjectGuid guid, uint8 removetype) +{ + BotMap::const_iterator itr = _bots.find(guid); + ASSERT(itr != _bots.end(), "Trying to remove bot which does not belong to this botmgr(a)!!"); + //ASSERT(_owner->IsInWorld(), "Trying to remove bot while not in world(a)!!"); + + //trying to remove temp bot second time means removing all bots + //just erase from bots because already cleaned up + for (std::list::iterator it = _removeList.begin(); it != _removeList.end(); ++it) + { + if (*it == guid) + { + _removeList.erase(it); + _bots.erase(itr); + return; + } + } + + Creature* bot = itr->second; + CleanupsBeforeBotDelete(guid, removetype); + + ////remove control bar + //if (GetNpcBotsCount() <= 1 && !_owner->GetPetGUID() && _owner->m_Controlled.empty()) + // _owner->SendRemoveControlBar(); + + if (bot->GetBotAI()->IsTempBot()) + { + //bot->GetBotAI()->OnBotDespawn(bot); //send to self + _removeList.push_back(guid); + return; + } + + _bots.erase(itr); + + BotAIResetType resetType; + switch (removetype) + { + case BOT_REMOVE_DISMISS: resetType = BOTAI_RESET_DISMISS; break; + case BOT_REMOVE_UNBIND: resetType = BOTAI_RESET_UNBIND; break; + default: resetType = BOTAI_RESET_LOGOUT; break; + } + bot->GetBotAI()->ResetBotAI(resetType); + + bot->SetFaction(bot->GetCreatureTemplate()->faction); + bot->SetLevel(bot->GetCreatureTemplate()->minlevel); + + if (removetype == BOT_REMOVE_DISMISS) + { + BotDataMgr::ResetNpcBotTransmogData(bot->GetEntry(), false); + uint32 newOwner = 0; + BotDataMgr::UpdateNpcBotData(bot->GetEntry(), NPCBOT_UPDATE_OWNER, &newOwner); + } +} + +void BotMgr::UnbindBot(ObjectGuid guid) +{ + Creature const* bot = GetBot(guid); + ASSERT(bot); + + RemoveBot(guid, BOT_REMOVE_UNBIND); + bot->GetBotAI()->SetBotCommandState(BOT_COMMAND_UNBIND); +} +BotAddResult BotMgr::RebindBot(Creature* bot) +{ + BotAddResult res = AddBot(bot); + if (res == BOT_ADD_SUCCESS) + bot->GetBotAI()->RemoveBotCommandState(BOT_COMMAND_UNBIND); + return res; +} + +BotAddResult BotMgr::AddBot(Creature* bot) +{ + ASSERT(bot->IsNPCBot()); + ASSERT(bot->GetBotAI() != nullptr); + + bool owned = bot->GetBotAI()->IsTempBot() || bot->GetBotAI()->GetBotOwnerGuid() == _owner->GetGUID().GetCounter(); + uint8 owned_count = BotDataMgr::GetOwnedBotsCount(_owner->GetGUID()); + uint8 class_count = BotDataMgr::GetOwnedBotsCount(_owner->GetGUID(), bot->GetClassMask()); + + if (!_enableNpcBots) + { + ChatHandler ch(_owner->GetSession()); + ch.SendSysMessage(bot_ai::LocalizedNpcText(GetOwner(), BOT_TEXT_BOTADDFAIL_DISABLED).c_str()); + return BOT_ADD_DISABLED; + } + if (GetBot(bot->GetGUID())) + return BOT_ADD_ALREADY_HAVE; //Silent error, intended + if (!bot->GetBotAI()->IAmFree()) + { + ChatHandler ch(_owner->GetSession()); + ch.PSendSysMessage(bot_ai::LocalizedNpcText(GetOwner(), BOT_TEXT_BOTADDFAIL_OWNED).c_str(), + bot->GetName().c_str(), bot->GetBotOwner()->GetName().c_str()); + return BOT_ADD_NOT_AVAILABLE; + } + if (!owned && owned_count >= GetMaxNpcBots(_owner->GetLevel())) + { + ChatHandler ch(_owner->GetSession()); + ch.PSendSysMessage(bot_ai::LocalizedNpcText(GetOwner(), BOT_TEXT_HIREFAIL_MAXBOTS).c_str(), GetMaxNpcBots(_owner->GetLevel())); + return BOT_ADD_MAX_EXCEED; + } + if (!owned && _maxClassNpcBots && class_count >= _maxClassNpcBots) + { + ChatHandler ch(_owner->GetSession()); + ch.PSendSysMessage(bot_ai::LocalizedNpcText(GetOwner(), BOT_TEXT_HIREFAIL_MAXCLASSBOTS).c_str(), class_count, _maxClassNpcBots); + return BOT_ADD_MAX_CLASS_EXCEED; + } + //Map* curMap = _owner->GetMap(); + //if (!temporary && LimitBots(curMap)) + //{ + // InstanceMap* map = curMap->ToInstanceMap(); + // uint32 count = map->GetPlayersCountExceptGMs(); + // if (count >= map->GetMaxPlayers()) + // { + // ChatHandler ch(_owner->GetSession()); + // ch.PSendSysMessage("Instance players limit exceed (%u of %u)", count, map->GetMaxPlayers()); + // return BOT_ADD_INSTANCE_LIMIT; + // } + //} + if (!owned) + { + uint32 cost = GetNpcBotCost(_owner->GetLevel(), bot->GetBotClass()); + if (!_owner->HasEnoughMoney(cost)) + { + ChatHandler ch(_owner->GetSession()); + std::string str = bot_ai::LocalizedNpcText(GetOwner(), BOT_TEXT_HIREFAIL_COST) + " ("; + str += GetNpcBotCostStr(_owner->GetLevel(), bot->GetBotClass()); + str += ")!"; + ch.SendSysMessage(str.c_str()); + return BOT_ADD_CANT_AFFORD; + } + + _owner->ModifyMoney(-(int32(cost))); + } + + bot->GetBotAI()->canUpdate = false; + + if (!bot->IsAlive()) + _reviveBot(bot); + + bot->GetBotAI()->UnsummonAll(false); + + _bots[bot->GetGUID()] = bot; + + ASSERT(!bot->GetCreator()); + //ASSERT(!bot->GetOwnerGUID()); + //bot->SetOwnerGUID(_owner->GetGUID()); + bot->SetCreator(_owner); //needed in case of FFAPVP + //bot->SetCreatorGUID(_owner->GetGUID()); + //_owner->m_Controlled.insert(bot); + bot->SetControlledByPlayer(true); + //bot->SetUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED); + bot->SetByteValue(UNIT_FIELD_BYTES_2, 1, _owner->GetByteValue(UNIT_FIELD_BYTES_2, 1)); + bot->SetFaction(_owner->GetFaction()); + bot->SetPhaseMask(_owner->GetPhaseMask(), true); + + bot->GetBotAI()->SetBotOwner(_owner); + + bot->GetBotAI()->Reset(); + + if (!bot->IsInWorld()) + TeleportBot(bot, _owner->GetMap(), _owner); + + if (!bot->GetBotAI()->IsTempBot()) + { + bot->GetBotAI()->SetBotCommandState(BOT_COMMAND_FOLLOW, true); + if (bot->GetBotAI()->HasRole(BOT_ROLE_PARTY)) + AddBotToGroup(bot); + + uint32 newOwner = _owner->GetGUID().GetCounter(); + BotDataMgr::UpdateNpcBotData(bot->GetEntry(), NPCBOT_UPDATE_OWNER, &newOwner); + } + + return BOT_ADD_SUCCESS; +} + +bool BotMgr::AddBotToGroup(Creature* bot) +{ + ASSERT(GetBot(bot->GetGUID())); + + Group* gr = _owner->GetGroup(); + if (gr) + { + if (gr->IsMember(bot->GetGUID())) + return true; + + if (gr->IsFull()) + { + if (!gr->isRaidGroup()) //non-raid group is full + gr->ConvertToRaid(); + else + return false; + } + } + else + { + gr = new Group; + if (!gr->Create(_owner)) + { + delete gr; + return false; + } + sGroupMgr->AddGroup(gr); + } + + if (gr->AddMember(bot)) + { + if (!bot->GetBotAI()->HasRole(BOT_ROLE_PARTY)) + bot->GetBotAI()->ToggleRole(BOT_ROLE_PARTY, true); + + return true; + } + + return false; +} + +void BotMgr::RemoveBotFromBGQueue(Creature const* bot) +{ + for (uint8 i = 0; i < PLAYER_MAX_BATTLEGROUND_QUEUES; ++i) + { + if (BattlegroundQueueTypeId bgQueueTypeId = _owner->GetBattlegroundQueueTypeId(i)) + sBattlegroundMgr->GetBattlegroundQueue(bgQueueTypeId).RemovePlayer(bot->GetGUID(), true); + } +} + +bool BotMgr::RemoveBotFromGroup(Creature* bot) +{ + ASSERT(GetBot(bot->GetGUID())); + + Group* gr = _owner->GetGroup(); + if (!gr || !gr->IsMember(bot->GetGUID())) + return false; + + RemoveBotFromBGQueue(bot); + + if (bot->GetBotAI()->HasRole(BOT_ROLE_PARTY) && !_owner->GetSession()->PlayerLogout()) + bot->GetBotAI()->ToggleRole(BOT_ROLE_PARTY, true); + + //debug + //if (gr->RemoveMember(bot->GetGUID())) + // TC_LOG_ERROR("entities.player", "RemoveBotFromGroup(): bot {} removed from group", bot->GetName()); + //else + // TC_LOG_ERROR("entities.player", "RemoveBotFromGroup(): RemoveMember() returned FALSE on bot {}", bot->GetName()); + + gr->RemoveMember(bot->GetGUID()); + + //if removed from group while in instance / bg then remove from world immediately + if (bot->IsInWorld() && RestrictBots(bot, true)) + TeleportBot(bot, bot->GetMap(), bot); + + return true; +} + +bool BotMgr::RemoveAllBotsFromGroup() +{ + for (BotMap::const_iterator itr = _bots.begin(); itr != _bots.end(); ++itr) + RemoveBotFromGroup(itr->second); + + return true; +} + +uint32 BotMgr::GetNpcBotCost(uint8 level, uint8 botclass) +{ + //assuming default 1000000 + //level 1: 500 //5 silver + //10 : 10000 //1 gold + //20 : 50000 //5 gold + //30 : 200000 //20 gold + //40 : 500000 //50 gold + //rest is linear + //rare / rareelite bots have their cost adjusted + uint32 cost = + level < 10 ? _npcBotsCost / 2000 : //5 silver + level < 20 ? _npcBotsCost / 100 : //1 gold + level < 30 ? _npcBotsCost / 20 : //5 gold + level < 40 ? _npcBotsCost / 5 : //20 gold + (_npcBotsCost * (level - (level % 10))) / DEFAULT_MAX_LEVEL; //50 - 100 gold + + switch (botclass) + { + case BOT_CLASS_BM: + case BOT_CLASS_ARCHMAGE: + case BOT_CLASS_SPELLBREAKER: + case BOT_CLASS_NECROMANCER: + cost += cost; //200% + break; + case BOT_CLASS_SPHYNX: + case BOT_CLASS_DREADLORD: + case BOT_CLASS_DARK_RANGER: + case BOT_CLASS_SEA_WITCH: + case BOT_CLASS_CRYPT_LORD: + cost += cost * 4; //500% + break; + default: + break; + } + + return cost; +} + +std::string BotMgr::GetNpcBotCostStr(uint8 level, uint8 botclass) +{ + std::ostringstream money; + + if (uint32 cost = GetNpcBotCost(level, botclass)) + { + uint32 gold = uint32(cost / GOLD); + cost -= (gold * GOLD); + uint32 silver = uint32(cost / SILVER); + cost -= (silver * SILVER); + + if (gold != 0) + money << gold << " |TInterface\\Icons\\INV_Misc_Coin_01:8|t"; + if (silver != 0) + money << silver << " |TInterface\\Icons\\INV_Misc_Coin_03:8|t"; + if (cost) + money << cost << " |TInterface\\Icons\\INV_Misc_Coin_05:8|t"; + } + + return money.str(); +} + +uint8 BotMgr::BotClassByClassName(std::string const& className) +{ + static const std::map BotClassNamesMap = { + { "warrior", BOT_CLASS_WARRIOR }, + { "paladin", BOT_CLASS_PALADIN }, + { "hunter", BOT_CLASS_HUNTER }, + { "rogue", BOT_CLASS_ROGUE }, + { "priest", BOT_CLASS_PRIEST }, + { "deathknight", BOT_CLASS_DEATH_KNIGHT }, + { "death_knight", BOT_CLASS_DEATH_KNIGHT }, + { "shaman", BOT_CLASS_SHAMAN }, + { "mage", BOT_CLASS_MAGE }, + { "warlock", BOT_CLASS_WARLOCK }, + { "druid", BOT_CLASS_DRUID }, + { "blademaster", BOT_CLASS_BM }, + { "blade_master", BOT_CLASS_BM }, + { "sphynx", BOT_CLASS_SPHYNX }, + { "obsidiandestroyer", BOT_CLASS_SPHYNX }, + { "obsidian_destroyer", BOT_CLASS_SPHYNX }, + { "destroyer", BOT_CLASS_SPHYNX }, + { "archmage", BOT_CLASS_ARCHMAGE }, + { "dreadlord", BOT_CLASS_DREADLORD }, + { "spellbreaker", BOT_CLASS_SPELLBREAKER }, + { "spell_breaker", BOT_CLASS_SPELLBREAKER }, + { "darkranger", BOT_CLASS_DARK_RANGER }, + { "dark_ranger", BOT_CLASS_DARK_RANGER }, + { "necromancer", BOT_CLASS_NECROMANCER }, + { "necro", BOT_CLASS_NECROMANCER }, + { "seawitch", BOT_CLASS_SEA_WITCH }, + { "sea_witch", BOT_CLASS_SEA_WITCH }, + { "cryptlord", BOT_CLASS_CRYPT_LORD}, + { "crypt_lord", BOT_CLASS_CRYPT_LORD } + }; + + //std::transform(className.begin(), className.end(), className.begin(), std::tolower); + auto iter = BotClassNamesMap.find(className); + if (iter != BotClassNamesMap.end()) + return iter->second; + + return BOT_CLASS_NONE; +} + +uint8 BotMgr::GetBotPlayerClass(uint8 bot_class) +{ + if (bot_class >= BOT_CLASS_EX_START) + { + switch (bot_class) + { + case BOT_CLASS_BM: + return BOT_CLASS_WARRIOR; + case BOT_CLASS_SPHYNX: + return BOT_CLASS_WARLOCK; + case BOT_CLASS_ARCHMAGE: + return BOT_CLASS_MAGE; + case BOT_CLASS_DREADLORD: + return BOT_CLASS_WARLOCK; + case BOT_CLASS_SPELLBREAKER: + return BOT_CLASS_PALADIN; + case BOT_CLASS_DARK_RANGER: + return BOT_CLASS_HUNTER; + case BOT_CLASS_NECROMANCER: + return BOT_CLASS_WARLOCK; + case BOT_CLASS_SEA_WITCH: + return BOT_CLASS_MAGE; + case BOT_CLASS_CRYPT_LORD: + return BOT_CLASS_WARRIOR; + default: + TC_LOG_ERROR("npcbots", "GetPlayerClass: unknown Ex bot class {}!", bot_class); + return BOT_CLASS_PALADIN; + } + } + + return bot_class; +} + +uint8 BotMgr::GetBotPlayerRace(uint8 bot_class, uint8 bot_race) +{ + if (bot_class >= BOT_CLASS_EX_START) + { + switch (bot_class) + { + case BOT_CLASS_BM: + return RACE_ORC; + case BOT_CLASS_SPHYNX: + return RACE_UNDEAD_PLAYER; + case BOT_CLASS_ARCHMAGE: + return RACE_HUMAN; + case BOT_CLASS_DREADLORD: + return RACE_UNDEAD_PLAYER; + case BOT_CLASS_SPELLBREAKER: + return RACE_BLOODELF; + case BOT_CLASS_DARK_RANGER: + return RACE_BLOODELF; + case BOT_CLASS_NECROMANCER: + return RACE_HUMAN; + case BOT_CLASS_SEA_WITCH: + return RACE_TROLL; + case BOT_CLASS_CRYPT_LORD: + return RACE_UNDEAD_PLAYER; + default: + TC_LOG_ERROR("npcbots", "GetBotPlayerRace: unknown Ex bot class {}!", bot_class); + return RACE_HUMAN; + } + } + + return bot_race; +} + +uint8 BotMgr::GetBotPlayerClass(Creature const* bot) +{ + return GetBotPlayerClass(bot->GetBotAI()->GetBotClass()); +} + +uint8 BotMgr::GetBotPlayerRace(Creature const* bot) +{ + return GetBotPlayerRace(bot->GetBotAI()->GetBotClass(), bot->GetRace()); +} + +uint8 BotMgr::GetBotEquipmentClass(uint8 bot_class) +{ + if (bot_class >= BOT_CLASS_EX_START) + { + switch (bot_class) + { + case BOT_CLASS_BM: + return BOT_CLASS_WARRIOR; + case BOT_CLASS_SPHYNX: + return BOT_CLASS_PALADIN; + case BOT_CLASS_ARCHMAGE: + return BOT_CLASS_MAGE; + case BOT_CLASS_DREADLORD: + return BOT_CLASS_PALADIN; + case BOT_CLASS_SPELLBREAKER: + return BOT_CLASS_PALADIN; + case BOT_CLASS_DARK_RANGER: + return BOT_CLASS_HUNTER; + case BOT_CLASS_NECROMANCER: + return BOT_CLASS_PALADIN; + case BOT_CLASS_SEA_WITCH: + return BOT_CLASS_MAGE; + case BOT_CLASS_CRYPT_LORD: + return BOT_CLASS_WARRIOR; + default: + TC_LOG_ERROR("npcbots", "GetBotEquipmentClass: unknown Ex bot class {}!", bot_class); + return BOT_CLASS_PALADIN; + } + } + + return BotMgr::GetBotPlayerClass(bot_class); +} + +std::string BotMgr::GetTargetIconString(uint8 icon_idx) const +{ + std::ostringstream ss; + ss << "|TInterface\\TargetingFrame\\UI-RaidTargetingIcon_" << uint32(icon_idx + 1) << ":12|t"; + if (size_t(icon_idx) < TargetIconNamesCacheSize) + ss << _targetIconNamesCache[icon_idx]; + + return ss.str(); +} +void BotMgr::UpdateTargetIconName(uint8 id, std::string const& name) +{ + if (id >= TargetIconNamesCacheSize) + return; + + _targetIconNamesCache[id] = name; +} +void BotMgr::ResetTargetIconNames() +{ + _targetIconNamesCache = {}; +} + +void BotMgr::ReviveAllBots() +{ + for (BotMap::const_iterator itr = _bots.begin(); itr != _bots.end(); ++itr) + _reviveBot(itr->second); +} + +void BotMgr::SendBotCommandState(uint32 state) +{ + for (BotMap::const_iterator itr = _bots.begin(); itr != _bots.end(); ++itr) + itr->second->GetBotAI()->SetBotCommandState(state, true); +} + +void BotMgr::SendBotCommandStateRemove(uint32 state) +{ + for (BotMap::const_iterator itr = _bots.begin(); itr != _bots.end(); ++itr) + itr->second->GetBotAI()->RemoveBotCommandState(state); +} + +void BotMgr::SendBotAwaitState(uint8 state) +{ + for (BotMap::const_iterator itr = _bots.begin(); itr != _bots.end(); ++itr) + itr->second->GetBotAI()->SetBotAwaitState(state); +} + +void BotMgr::RecallAllBots(bool teleport) +{ + if (teleport) + { + _botsHidden = true; + _quickrecall = true; + } + else + { + for (BotMap::const_iterator itr = _bots.begin(); itr != _bots.end(); ++itr) + if (itr->second->IsInWorld() && itr->second->IsAlive() && !bot_ai::CCed(itr->second, true)) + itr->second->GetMotionMaster()->MovePoint(_owner->GetMapId(), *_owner, false); + } +} + +void BotMgr::RecallBot(Creature* bot) +{ + ASSERT(GetBot(bot->GetGUID())); + + if (bot->IsInWorld() && bot->IsAlive() && !bot_ai::CCed(bot, true)) + bot->GetMotionMaster()->MovePoint(_owner->GetMapId(), *_owner, false); +} + +void BotMgr::KillAllBots() +{ + for (BotMap::const_iterator itr = _bots.begin(); itr != _bots.end(); ++itr) + KillBot(itr->second); +} + +void BotMgr::KillBot(Creature* bot) const +{ + ASSERT(GetBot(bot->GetGUID())); + + if (bot->IsInWorld() && bot->IsAlive()) + { + bot->setDeathState(JUST_DIED); + bot->GetBotAI()->JustDied(bot); + //bot->Kill(bot); + } +} + +void BotMgr::SetBotsShouldUpdateStats() +{ + for (BotMap::const_iterator itr = _bots.begin(); itr != _bots.end(); ++itr) + itr->second->GetBotAI()->SetShouldUpdateStats(); +} + +void BotMgr::UpdatePhaseForBots() +{ + for (BotMap::const_iterator itr = _bots.begin(); itr != _bots.end(); ++itr) + { + itr->second->SetPhaseMask(_owner->GetPhaseMask(), itr->second->IsInWorld()); + if (itr->second->GetBotsPet()) + itr->second->GetBotsPet()->SetPhaseMask(_owner->GetPhaseMask(), itr->second->GetBotsPet()->IsInWorld()); + } +} + +void BotMgr::UpdatePvPForBots() +{ + for (BotMap::const_iterator itr = _bots.begin(); itr != _bots.end(); ++itr) + { + itr->second->SetByteValue(UNIT_FIELD_BYTES_2, 1, _owner->GetByteValue(UNIT_FIELD_BYTES_2, 1)); + if (itr->second->GetBotsPet()) + itr->second->GetBotsPet()->SetByteValue(UNIT_FIELD_BYTES_2, 1, _owner->GetByteValue(UNIT_FIELD_BYTES_2, 1)); + } +} + +void BotMgr::BuildBotPartyMemberStatsPacket(ObjectGuid bot_guid, WorldPacket* data) +{ + Creature const* bot = BotDataMgr::FindBot(bot_guid.GetEntry()); + if (!bot) + { + *data << uint8(0); + *data << bot_guid.WriteAsPacked(); + *data << uint32(GROUP_UPDATE_FLAG_STATUS); + *data << uint16(MEMBER_STATUS_OFFLINE); + return; + } + + Creature const* pet = nullptr; //bot->GetBotAI()->GetBotsPet(); + Powers powerType = bot->GetPowerType(); + + *data << uint8(0); // only for SMSG_PARTY_MEMBER_STATS_FULL, probably arena/bg related + *data << bot->GetPackGUID(); + + uint32 updateFlags = GROUP_UPDATE_FLAG_STATUS | GROUP_UPDATE_FLAG_CUR_HP | GROUP_UPDATE_FLAG_MAX_HP + | GROUP_UPDATE_FLAG_CUR_POWER | GROUP_UPDATE_FLAG_MAX_POWER | GROUP_UPDATE_FLAG_LEVEL + | GROUP_UPDATE_FLAG_ZONE | GROUP_UPDATE_FLAG_POSITION | GROUP_UPDATE_FLAG_AURAS + | GROUP_UPDATE_FLAG_PET_NAME | GROUP_UPDATE_FLAG_PET_MODEL_ID | GROUP_UPDATE_FLAG_PET_AURAS; + + if (powerType != POWER_MANA) + updateFlags |= GROUP_UPDATE_FLAG_POWER_TYPE; + + if (pet) + updateFlags |= GROUP_UPDATE_FLAG_PET_GUID | GROUP_UPDATE_FLAG_PET_CUR_HP | GROUP_UPDATE_FLAG_PET_MAX_HP + | GROUP_UPDATE_FLAG_PET_POWER_TYPE | GROUP_UPDATE_FLAG_PET_CUR_POWER | GROUP_UPDATE_FLAG_PET_MAX_POWER; + + if (bot->GetVehicle()) + updateFlags |= GROUP_UPDATE_FLAG_VEHICLE_SEAT; + + uint16 playerStatus = MEMBER_STATUS_ONLINE; + if (bot->IsPvP()) + playerStatus |= MEMBER_STATUS_PVP; + + if (!bot->IsAlive()) + playerStatus |= MEMBER_STATUS_DEAD; + + if (bot->IsFFAPvP()) + playerStatus |= MEMBER_STATUS_PVP_FFA; + + *data << uint32(updateFlags); + *data << uint16(playerStatus); // GROUP_UPDATE_FLAG_STATUS + *data << uint32(bot->GetHealth()); // GROUP_UPDATE_FLAG_CUR_HP + *data << uint32(bot->GetMaxHealth()); // GROUP_UPDATE_FLAG_MAX_HP + if (updateFlags & GROUP_UPDATE_FLAG_POWER_TYPE) + *data << uint8(powerType); + + *data << uint16(bot->GetPower(powerType)); // GROUP_UPDATE_FLAG_CUR_POWER + *data << uint16(bot->GetMaxPower(powerType)); // GROUP_UPDATE_FLAG_MAX_POWER + *data << uint16(bot->GetLevel()); // GROUP_UPDATE_FLAG_LEVEL + *data << uint16(bot->GetZoneId()); // GROUP_UPDATE_FLAG_ZONE + *data << uint16(bot->GetPositionX()); // GROUP_UPDATE_FLAG_POSITION + *data << uint16(bot->GetPositionY()); // GROUP_UPDATE_FLAG_POSITION + + uint64 auraMask = 0; + size_t maskPos = data->wpos(); + *data << uint64(auraMask); // placeholder + for (uint8 i = 0; i < MAX_AURAS_GROUP_UPDATE; ++i) + { + if (AuraApplication const* aurApp = bot->GetVisibleAura(i)) + { + auraMask |= uint64(1) << i; + *data << uint32(aurApp->GetBase()->GetId()); + *data << uint8(aurApp->GetFlags()); + } + } + + data->put(maskPos, auraMask); // GROUP_UPDATE_FLAG_AURAS + + if (updateFlags & GROUP_UPDATE_FLAG_PET_GUID) + *data << uint64(ASSERT_NOTNULL(pet)->GetGUID()); + + *data << std::string(pet ? pet->GetName() : ""); // GROUP_UPDATE_FLAG_PET_NAME + *data << uint16(pet ? pet->GetDisplayId() : 0); // GROUP_UPDATE_FLAG_PET_MODEL_ID + + if (updateFlags & GROUP_UPDATE_FLAG_PET_CUR_HP) + *data << uint32(pet->GetHealth()); + + if (updateFlags & GROUP_UPDATE_FLAG_PET_MAX_HP) + *data << uint32(pet->GetMaxHealth()); + + if (updateFlags & GROUP_UPDATE_FLAG_PET_POWER_TYPE) + *data << (uint8)pet->GetPowerType(); + + if (updateFlags & GROUP_UPDATE_FLAG_PET_CUR_POWER) + *data << uint16(pet->GetPower(pet->GetPowerType())); + + if (updateFlags & GROUP_UPDATE_FLAG_PET_MAX_POWER) + *data << uint16(pet->GetMaxPower(pet->GetPowerType())); + + uint64 petAuraMask = 0; + maskPos = data->wpos(); + *data << uint64(petAuraMask); // placeholder + if (pet) + { + for (uint8 i = 0; i < MAX_AURAS_GROUP_UPDATE; ++i) + { + if (AuraApplication const* aurApp = pet->GetVisibleAura(i)) + { + petAuraMask |= uint64(1) << i; + *data << uint32(aurApp->GetBase()->GetId()); + *data << uint8(aurApp->GetFlags()); + } + } + } + + data->put(maskPos, petAuraMask); // GROUP_UPDATE_FLAG_PET_AURAS + + if (updateFlags & GROUP_UPDATE_FLAG_VEHICLE_SEAT) + *data << uint32(bot->GetVehicle()->GetVehicleInfo()->SeatID[bot->m_movementInfo.transport.seat]); +} + +void BotMgr::BuildBotPartyMemberStatsChangedPacket(Creature const* bot, WorldPacket* data) +{ + uint32 mask = bot->GetBotAI()->GetGroupUpdateFlag(); + + if (mask == GROUP_UPDATE_FLAG_NONE) + return; + + if (mask & GROUP_UPDATE_FLAG_POWER_TYPE) // if update power type, update current/max power also + mask |= (GROUP_UPDATE_FLAG_CUR_POWER | GROUP_UPDATE_FLAG_MAX_POWER); + + if (mask & GROUP_UPDATE_FLAG_PET_POWER_TYPE) // same for pets + mask |= (GROUP_UPDATE_FLAG_PET_CUR_POWER | GROUP_UPDATE_FLAG_PET_MAX_POWER); + + uint32 byteCount = 0; + for (int i = 1; i < GROUP_UPDATE_FLAGS_COUNT; ++i) + if (mask & (1 << i)) + byteCount += GroupUpdateLength[i]; + + data->Initialize(SMSG_PARTY_MEMBER_STATS, size_t(8 + 4 + byteCount)); + *data << bot->GetPackGUID(); + *data << uint32(mask); + + if (mask & GROUP_UPDATE_FLAG_STATUS) + { + uint16 playerStatus = MEMBER_STATUS_ONLINE; + if (bot->IsPvP()) + playerStatus |= MEMBER_STATUS_PVP; + + if (!bot->IsAlive()) + playerStatus |= MEMBER_STATUS_DEAD; + + if (bot->IsFFAPvP()) + playerStatus |= MEMBER_STATUS_PVP_FFA; + + *data << uint16(playerStatus); + } + + if (mask & GROUP_UPDATE_FLAG_CUR_HP) + *data << uint32(bot->GetHealth()); + + if (mask & GROUP_UPDATE_FLAG_MAX_HP) + *data << uint32(bot->GetMaxHealth()); + + Powers powerType = bot->GetPowerType(); + if (mask & GROUP_UPDATE_FLAG_POWER_TYPE) + *data << uint8(powerType); + + if (mask & GROUP_UPDATE_FLAG_CUR_POWER) + *data << uint16(bot->GetPower(powerType)); + + if (mask & GROUP_UPDATE_FLAG_MAX_POWER) + *data << uint16(bot->GetMaxPower(powerType)); + + if (mask & GROUP_UPDATE_FLAG_LEVEL) + *data << uint16(bot->GetLevel()); + + if (mask & GROUP_UPDATE_FLAG_ZONE) + *data << uint16(bot->GetZoneId()); + + if (mask & GROUP_UPDATE_FLAG_POSITION) + { + *data << uint16(bot->GetPositionX()); + *data << uint16(bot->GetPositionY()); + } + + if (mask & GROUP_UPDATE_FLAG_AURAS) + { + uint64 auramask = GetBotAuraUpdateMaskForRaid(bot); + *data << uint64(auramask); + for (uint32 i = 0; i < MAX_AURAS_GROUP_UPDATE; ++i) + { + if (auramask & (uint64(1) << i)) + { + AuraApplication const* aurApp = bot->GetVisibleAura(i); + *data << uint32(aurApp ? aurApp->GetBase()->GetId() : 0); + *data << uint8(1); + } + } + } + + Creature const* pet = nullptr; //bot->GetBotAI()->GetBotsPet(); + if (mask & GROUP_UPDATE_FLAG_PET_GUID) + { + if (pet) + *data << (uint64) pet->GetGUID(); + else + *data << (uint64) 0; + } + + if (mask & GROUP_UPDATE_FLAG_PET_NAME) + { + if (pet) + *data << pet->GetName(); + else + *data << uint8(0); + } + + if (mask & GROUP_UPDATE_FLAG_PET_MODEL_ID) + { + if (pet) + *data << uint16(pet->GetDisplayId()); + else + *data << uint16(0); + } + + if (mask & GROUP_UPDATE_FLAG_PET_CUR_HP) + { + if (pet) + *data << uint32(pet->GetHealth()); + else + *data << uint32(0); + } + + if (mask & GROUP_UPDATE_FLAG_PET_MAX_HP) + { + if (pet) + *data << uint32(pet->GetMaxHealth()); + else + *data << uint32(0); + } + + if (mask & GROUP_UPDATE_FLAG_PET_POWER_TYPE) + { + if (pet) + *data << uint8(pet->GetPowerType()); + else + *data << uint8(0); + } + + if (mask & GROUP_UPDATE_FLAG_PET_CUR_POWER) + { + if (pet) + *data << uint16(pet->GetPower(pet->GetPowerType())); + else + *data << uint16(0); + } + + if (mask & GROUP_UPDATE_FLAG_PET_MAX_POWER) + { + if (pet) + *data << uint16(pet->GetMaxPower(pet->GetPowerType())); + else + *data << uint16(0); + } + + if (mask & GROUP_UPDATE_FLAG_PET_AURAS) + { + if (pet) + { + uint64 auramask = GetBotPetAuraUpdateMaskForRaid(pet); + *data << uint64(auramask); + for (uint32 i = 0; i < MAX_AURAS_GROUP_UPDATE; ++i) + { + if (auramask & (uint64(1) << i)) + { + AuraApplication const* aurApp = pet->GetVisibleAura(i); + *data << uint32(aurApp ? aurApp->GetBase()->GetId() : 0); + *data << uint8(aurApp ? aurApp->GetFlags() : 0); + } + } + } + else + *data << uint64(0); + } + + if (mask & GROUP_UPDATE_FLAG_VEHICLE_SEAT) + { + if (Vehicle* veh = bot->GetVehicle()) + *data << uint32(veh->GetVehicleInfo()->SeatID[bot->m_movementInfo.transport.seat]); + else + *data << uint32(0); + } +} + +//uint32 BotMgr::GetBotGroupUpdateFlag(Creature const* bot) +//{ +// bot->GetBotAI()->GetGroupUpdateFlags +//} +void BotMgr::SetBotGroupUpdateFlag(Creature const* bot, uint32 flag) +{ + bot->GetBotAI()->SetGroupUpdateFlag(flag); +} +uint64 BotMgr::GetBotAuraUpdateMaskForRaid(Creature const* bot) +{ + return bot->GetBotAI()->GetAuraUpdateMaskForRaid(); +} +void BotMgr::SetBotAuraUpdateMaskForRaid(Creature const* bot, uint8 slot) +{ + bot->GetBotAI()->SetAuraUpdateMaskForRaid(slot); +} +void BotMgr::ResetBotAuraUpdateMaskForRaid(Creature const* bot) +{ + bot->GetBotAI()->ResetAuraUpdateMaskForRaid(); +} +uint64 BotMgr::GetBotPetAuraUpdateMaskForRaid(Creature const* botpet) +{ + return botpet->GetBotPetAI()->GetAuraUpdateMaskForRaid(); +} +void BotMgr::SetBotPetAuraUpdateMaskForRaid(Creature const* botpet, uint8 slot) +{ + botpet->GetBotPetAI()->SetAuraUpdateMaskForRaid(slot); +} +void BotMgr::ResetBotPetAuraUpdateMaskForRaid(Creature const* botpet) +{ + botpet->GetBotPetAI()->ResetAuraUpdateMaskForRaid(); +} + +void BotMgr::PropagateEngageTimers() const +{ + uint32 delay_dps = GetEngageDelayDPS(); + uint32 delay_heal = GetEngageDelayHeal(); + + if (!delay_dps && !delay_heal) + return; + + for (BotMap::const_iterator itr = _bots.begin(); itr != _bots.end(); ++itr) + { + if (itr->second->GetBotAI()->IsTank()) + continue; + + bool is_heal = itr->second->GetBotAI()->HasRole(BOT_ROLE_HEAL); + bool is_dps= itr->second->GetBotAI()->HasRole(BOT_ROLE_DPS); + uint32 delay = (is_heal && is_dps) ? std::max(delay_dps, delay_heal) : is_heal ? delay_heal : is_dps ? delay_dps : 0; + + itr->second->GetBotAI()->ResetEngageTimer(delay); + } +} + +void BotMgr::TrackDamage(Unit const* u, uint32 damage) +{ + _dpstracker->TrackDamage(u, damage); +} + +uint32 BotMgr::GetDPSTaken(Unit const* u) const +{ + return _dpstracker->GetDPSTaken(u->GetGUID().GetRawValue()); +} + +int32 BotMgr::GetHPSTaken(Unit const* unit) const +{ + if (!HaveBot()) + return 0; + + std::list unitList; + Group const* gr = _owner->GetGroup(); + if (!gr) + { + if (_owner->HasUnitState(UNIT_STATE_CASTING)) + unitList.push_back(_owner); + for (BotMap::const_iterator itr = _bots.begin(); itr != _bots.end(); ++itr) + if (itr->second->GetTarget() == unit->GetGUID() && itr->second->HasUnitState(UNIT_STATE_CASTING)) + unitList.push_back(itr->second); + } + else + { + bool Bots = false; + for (GroupReference const* itr = gr->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* player = itr->GetSource(); + if (player == nullptr) continue; + if (_owner->GetMap() != player->FindMap()) continue; + if (!Bots) + Bots = true; + if (player->HasUnitState(UNIT_STATE_CASTING)) + unitList.push_back(player); + } + if (Bots) + { + for (GroupReference const* gitr = gr->GetFirstMember(); gitr != nullptr; gitr = gitr->next()) + { + if (gitr->GetSource() == nullptr) continue; + if (_owner->GetMap() != gitr->GetSource()->FindMap()) continue; + + if (gitr->GetSource()->HaveBot()) + { + BotMap const* map = gitr->GetSource()->GetBotMgr()->GetBotMap(); + for (BotMap::const_iterator itr = map->begin(); itr != map->end(); ++itr) + if (itr->second->GetTarget() == unit->GetGUID() && itr->second->HasUnitState(UNIT_STATE_CASTING)) + unitList.push_back(itr->second); + } + } + } + } + + int32 amount = 0; + + Unit* u; + Spell const* spell; + SpellInfo const* spellInfo; + for (std::list::const_iterator itr = unitList.begin(); itr != unitList.end(); ++itr) + { + u = *itr; + + for (uint8 i = CURRENT_FIRST_NON_MELEE_SPELL; i != CURRENT_AUTOREPEAT_SPELL; ++i) + { + spell = u->GetCurrentSpell(CurrentSpellTypes(i)); + if (!spell) + continue; + + ObjectGuid targetGuid = spell->m_targets.GetObjectTargetGUID(); + if (!targetGuid || !targetGuid.IsUnit()) + continue; + + if (targetGuid != unit->GetGUID()) + { + if (!gr || !gr->IsMember(unit->GetGUID())) + continue; + } + + spellInfo = spell->GetSpellInfo(); + + for (uint8 j = 0; j != MAX_SPELL_EFFECTS; ++j) + { + if (spellInfo->_effects[j].Effect != SPELL_EFFECT_HEAL) + continue; + + if (targetGuid != unit->GetGUID()) + { + if (spellInfo->_effects[j].TargetA.GetSelectionCategory() != TARGET_SELECT_CATEGORY_AREA) + continue; + + //Targets t = spellInfo->_effects[j].TargetA.GetTarget(); + //non-existing case + //if (t == TARGET_UNIT_CASTER_AREA_PARTY && !gr->SameSubGroup(u->GetGUID(), unit->GetGUID())) + // continue; + Targets t = spellInfo->_effects[j].TargetB.GetTarget(); + if (t == TARGET_UNIT_LASTTARGET_AREA_PARTY && + !(GetBot(unit->GetGUID()) && GetBot(targetGuid)) && + !gr->SameSubGroup(unit->GetGUID(), targetGuid)) + continue; + } + + int32 healing = u->SpellHealingBonusDone(const_cast(unit), spellInfo, spellInfo->_effects[0].CalcValue(u), HEAL, spellInfo->GetEffect(EFFECT_0), {}); + healing = unit->SpellHealingBonusTaken(u, spellInfo, healing, HEAL); + + if (i == CURRENT_CHANNELED_SPELL) + amount += int32(healing / (spellInfo->_effects[j].Amplitude * 0.001f)); + else + amount += int32(healing / (std::max(spell->GetTimer(), 1000) * 0.001f)); + + //TC_LOG_ERROR("entities.player", "BotMgr:pendingHeals: found {}'s {} on {} in {} ({}, total {})", + // u->GetName(), spellInfo->SpellName[0], target->GetName(), pheal->delay, healing, pheal->amount); + } + + break; + } + } + + //HoTs + Unit::AuraEffectList const& hots = unit->GetAuraEffectsByType(SPELL_AURA_PERIODIC_HEAL); + for (Unit::AuraEffectList::const_iterator itr = hots.begin(); itr != hots.end(); ++itr) + amount += int32((*itr)->GetAmount() / ((*itr)->GetAmplitude() * 0.001f)); + + //if (amount != 0) + // TC_LOG_ERROR("entities.player", "BotMgr:GetHPSTaken(): {} got {})", unit->GetName(), amount); + + return amount; +} + +void BotMgr::OnBotWandererKilled(Creature const* bot, Player* looter) +{ + bot->GetBotAI()->SpawnKillReward(looter); +} + +void BotMgr::OnBotWandererKilled(GameObject* go) +{ + if (go->GetEntry() == GO_BOT_MONEY_BAG && go->GetSpellId() > go->GetEntry()) + { + uint32 bot_id = go->GetSpellId() - GO_BOT_MONEY_BAG; + if (Creature const* bot = BotDataMgr::FindBot(bot_id)) + bot->GetBotAI()->FillKillReward(go); + } +} + +void BotMgr::OnBotKilled(Creature const* bot, Unit* attacker/* = nullptr*/) +{ + bot->GetBotAI()->OnDeath(attacker); +} + +void BotMgr::OnBotSpellInterrupt(Unit const* caster, CurrentSpellTypes spellType) +{ + if (spellType == CURRENT_AUTOREPEAT_SPELL) + { + WorldPackets::Combat::CancelAutoRepeat cancelAutoRepeat; + cancelAutoRepeat.Guid = caster->GetPackGUID(); + caster->SendMessageToSet(cancelAutoRepeat.Write(), true); + } +} + +void BotMgr::OnBotSpellGo(Unit const* caster, Spell const* spell, bool ok) +{ + if (caster->ToCreature()->GetBotAI()) + caster->ToCreature()->GetBotAI()->OnBotSpellGo(spell, ok); + else if (caster->ToCreature()->GetBotPetAI()) + caster->ToCreature()->GetBotPetAI()->OnBotPetSpellGo(spell, ok); +} + +void BotMgr::OnBotOwnerSpellGo(Unit const* caster, Spell const* spell, bool ok) +{ + BotMap const* bmap = caster->ToPlayer()->GetBotMgr()->GetBotMap(); + for (BotMap::const_iterator itr = bmap->begin(); itr != bmap->end(); ++itr) + { + if (Creature const* bot = itr->second) + { + if (!bot->IsInWorld() || !bot->IsAlive()) + continue; + + bot->GetBotAI()->OnBotOwnerSpellGo(spell, ok); + //if (Creature const* botpet = bot->GetBotsPet()) + // botpet->GetBotAI()->OnBotPetOwnerSpellGo(spell, ok); + } + } +} + +void BotMgr::OnBotChannelFinish(Unit const* caster, Spell const* spell) +{ + if (caster->ToCreature()->GetBotAI()) + caster->ToCreature()->GetBotAI()->OnBotChannelFinish(spell); + //else if (caster->ToCreature()->GetBotPetAI()) + // caster->ToCreature()->GetBotPetAI()->OnBotPetChannelFinish(spell); +} + +void BotMgr::OnVehicleSpellGo(Unit const* caster, Spell const* spell, bool ok) +{ + if (caster->GetCharmerGUID().IsPlayer()) + { + Unit const* owner = caster->GetCharmer(); + if (owner && owner->ToPlayer()->HaveBot()) + { + BotMap const* bmap = owner->ToPlayer()->GetBotMgr()->GetBotMap(); + for (BotMap::const_iterator itr = bmap->begin(); itr != bmap->end(); ++itr) + { + if (Creature const* bot = itr->second) + { + bot->GetBotAI()->OnBotOwnerSpellGo(spell, ok); + //if (Creature const* botpet = bot->GetBotsPet()) + // botpet->GetBotAI()->OnBotPetOwnerSpellGo(spell, ok); + } + } + } + } + else if (caster->GetCharmerGUID().IsCreature()) + { + Unit const* bot = caster->GetCharmer(); + if (bot->ToCreature()->GetBotAI()) + bot->ToCreature()->GetBotAI()->OnBotSpellGo(spell, ok); + } +} + +void BotMgr::OnVehicleAttackedBy(Unit* attacker, Unit const* victim) +{ + Unit const* owner = victim->GetCharmer(); + if (victim->GetCharmerGUID().IsPlayer()) + owner = victim->GetCharmer(); + else if (victim->GetCharmerGUID().IsCreature()) + if (Unit const* bot = victim->GetCharmer()) + owner = bot->ToCreature()->GetBotOwner(); + + if (owner && owner->GetTypeId() == TYPEID_PLAYER && owner->ToPlayer()->HaveBot()) + { + BotMap const* bmap = owner->ToPlayer()->GetBotMgr()->GetBotMap(); + for (BotMap::const_iterator itr = bmap->begin(); itr != bmap->end(); ++itr) + if (Creature const* bot = itr->second) + bot->GetBotAI()->OnOwnerVehicleDamagedBy(attacker); + } +} + +void BotMgr::OnBotDamageTaken(Unit* attacker, Unit* victim, uint32 damage, CleanDamage const* cleanDamage, DamageEffectType damagetype, SpellInfo const* spellInfo) +{ + victim->ToCreature()->GetBotAI()->OnBotDamageTaken(attacker, damage, cleanDamage , damagetype, spellInfo); +} + +void BotMgr::OnBotDamageDealt(Unit* attacker, Unit* victim, uint32 damage, CleanDamage const* cleanDamage, DamageEffectType damagetype, SpellInfo const* spellInfo) +{ + attacker->ToCreature()->GetBotAI()->OnBotDamageDealt(victim, damage, cleanDamage, damagetype, spellInfo); +} + +void BotMgr::OnBotDispelDealt(Unit* dispeller, Unit* dispelled, uint8 num) +{ + dispeller->ToCreature()->GetBotAI()->OnBotDispelDealt(dispelled, num); +} + +void BotMgr::OnBotEnterVehicle(Creature const* passenger, Vehicle const* vehicle) +{ + passenger->GetBotAI()->OnBotEnterVehicle(vehicle); +} + +void BotMgr::OnBotExitVehicle(Creature const* passenger, Vehicle const* vehicle) +{ + passenger->GetBotAI()->OnBotExitVehicle(vehicle); +} + +void BotMgr::OnBotOwnerEnterVehicle(Player const* passenger, Vehicle const* vehicle) +{ + BotMap const* bmap = passenger->GetBotMgr()->GetBotMap(); + for (BotMap::const_iterator itr = bmap->begin(); itr != bmap->end(); ++itr) + if (Creature const* bot = itr->second) + if (bot->IsInWorld() && bot->IsAlive()) + bot->GetBotAI()->OnBotOwnerEnterVehicle(vehicle); +} + +void BotMgr::OnBotOwnerExitVehicle(Player const* passenger, Vehicle const* vehicle) +{ + BotMap const* bmap = passenger->GetBotMgr()->GetBotMap(); + for (BotMap::const_iterator itr = bmap->begin(); itr != bmap->end(); ++itr) + if (Creature const* bot = itr->second) + if (bot->IsInWorld() && bot->IsAlive()) + bot->GetBotAI()->OnBotOwnerExitVehicle(vehicle); +} + +void BotMgr::OnBotPartyEngage(Player const* owner) +{ + Group const* gr = owner->GetGroup(); + if (gr) + { + std::vector affectedPlayers; + for (GroupReference const* itr = gr->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player const* player = itr->GetSource(); + if (!player || owner->GetMap() != player->FindMap() || + player->GetDistance(owner) > sWorld->GetMaxVisibleDistanceOnContinents() || + !player->HaveBot()) + continue; + + if (player->GetBotMgr()->IsPartyInCombat()) + return; + + affectedPlayers.push_back(player); + } + for (Player const* p : affectedPlayers) + p->GetBotMgr()->PropagateEngageTimers(); + } + else + owner->GetBotMgr()->PropagateEngageTimers(); +} + +void BotMgr::ApplyBotEffectMods(Unit const* caster, SpellInfo const* spellInfo, uint8 effIndex, float& value) +{ + caster->ToCreature()->GetBotAI()->ApplyBotEffectMods(spellInfo, effIndex, value); +} + +void BotMgr::ApplyBotThreatMods(Unit const* attacker, SpellInfo const* spellInfo, float& threat) +{ + attacker->ToCreature()->GetBotAI()->ApplyBotThreatMods(spellInfo, threat); +} + +void BotMgr::ApplyBotEffectValueMultiplierMods(Unit const* caster, SpellInfo const* spellInfo, SpellEffIndex effIndex, float& multiplier) +{ + caster->ToCreature()->GetBotAI()->ApplyBotEffectValueMultiplierMods(spellInfo, effIndex, multiplier); +} + +float BotMgr::GetBotDamageTakenMod(Creature const* bot, bool magic) +{ + return bot->GetBotAI()->GetBotDamageTakenMod(magic); +} + +int32 BotMgr::GetBotStat(Creature const* bot, BotStatMods stat) +{ + return bot->GetBotAI()->GetTotalBotStat(stat); +} + +float BotMgr::GetBotResilience(Creature const* botOrPet) +{ + if (botOrPet->IsNPCBot()) + return botOrPet->GetBotAI()->GetBotResilience(); + + return botOrPet->GetBotPetAI()->GetPetsOwner()->GetBotAI()->GetBotResilience(); +} + +float BotMgr::GetBotDamageModPhysical() +{ + return _mult_dmg_physical; +} +float BotMgr::GetBotDamageModSpell() +{ + return _mult_dmg_spell; +} +float BotMgr::GetBotHealingMod() +{ + return _mult_healing; +} +float BotMgr::GetBotHPMod() +{ + return _mult_hp; +} +float BotMgr::GetBotWandererDamageMod() +{ + return _mult_dmg_wanderer; +} +float BotMgr::GetBotWandererHealingMod() +{ + return _mult_healing_wanderer; +} +float BotMgr::GetBotWandererHPMod() +{ + return _mult_hp_wanderer; +} +float BotMgr::GetBotWandererSpeedMod() +{ + return _mult_speed_wanderer; +} +float BotMgr::GetBotWandererXPGainMod() +{ + return _mult_xpgain_wanderer; +} +PctBrackets BotMgr::GetBotWandererLevelBrackets() +{ + return _botwanderer_pct_level_brackets; +} +float BotMgr::GetBotDamageModByClass(uint8 botclass) +{ + switch (botclass) + { + case BOT_CLASS_WARRIOR: + return _mult_dmg_warrior; + case BOT_CLASS_PALADIN: + return _mult_dmg_paladin; + case BOT_CLASS_HUNTER: + return _mult_dmg_hunter; + case BOT_CLASS_ROGUE: + return _mult_dmg_rogue; + case BOT_CLASS_PRIEST: + return _mult_dmg_priest; + case BOT_CLASS_DEATH_KNIGHT: + return _mult_dmg_deathknight; + case BOT_CLASS_SHAMAN: + return _mult_dmg_shaman; + case BOT_CLASS_MAGE: + return _mult_dmg_mage; + case BOT_CLASS_WARLOCK: + return _mult_dmg_warlock; + case BOT_CLASS_DRUID: + return _mult_dmg_druid; + case BOT_CLASS_BM: + return _mult_dmg_blademaster; + case BOT_CLASS_SPHYNX: + return _mult_dmg_obsidiandestroyer; + case BOT_CLASS_ARCHMAGE: + return _mult_dmg_archmage; + case BOT_CLASS_DREADLORD: + return _mult_dmg_dreadlord; + case BOT_CLASS_SPELLBREAKER: + return _mult_dmg_spellbreaker; + case BOT_CLASS_DARK_RANGER: + return _mult_dmg_darkranger; + case BOT_CLASS_NECROMANCER: + return _mult_dmg_necromancer; + case BOT_CLASS_SEA_WITCH: + return _mult_dmg_seawitch; + case BOT_CLASS_CRYPT_LORD: + return _mult_dmg_cryptlord; + default: + return 1.0; + } +} + +float BotMgr::GetBotDamageModByLevel(uint8 botlevel) +{ + uint8 bracket = botlevel / 10; + if (bracket < _mult_dmg_levels.size()) + return _mult_dmg_levels[bracket]; + return 1.0f; +} + +std::vector BotMgr::GetAllGroupMembers(Group const* group) +{ + std::vector group_members; + if (group) + { + group_members.reserve(group->GetMembersCount()); + for (GroupReference const* ref = group->GetFirstMember(); ref != nullptr; ref = ref->next()) + { + if (Player* pl = ref->GetSource()) + group_members.push_back(pl); + } + for (GroupBotReference const* ref = group->GetFirstBotMember(); ref != nullptr; ref = ref->next()) + { + if (Creature* cr = ref->GetSource()) + group_members.push_back(cr); + } + } + + return group_members; +} +std::vector BotMgr::GetAllGroupMembers(Unit const* source) +{ + Group const* group = (source->IsNPCBot() && source->ToCreature()->GetBotAI()) ? source->ToCreature()->GetBotAI()->GetGroup() : + source->IsPlayer() ? source->ToPlayer()->GetGroup() : nullptr; + return GetAllGroupMembers(group); +} + +void BotMgr::InviteBotToBG(ObjectGuid botguid, GroupQueueInfo* ginfo, Battleground* bg) +{ + Creature const* bot = BotDataMgr::FindBot(botguid.GetEntry()); + ASSERT(bot); + + bg->IncreaseInvitedCount(ginfo->Team); + //TC_LOG_INFO("npcbots", "Battleground: invited NPCBot {} to BG instance {} bgtype {} '{}'", + // botguid.GetEntry(), bg->GetInstanceID(), bg->GetTypeID(), bg->GetName()); +} + +bool BotMgr::IsBotInAreaTriggerRadius(Creature const* bot, AreaTriggerEntry const* trigger) +{ + if (!trigger || !bot->IsInWorld() || bot->GetMap()->GetId() != trigger->ContinentID) + return false; + + if (trigger->Radius > 0.f) + { + // if we have radius check it + float dist = bot->GetDistance(trigger->Pos.X, trigger->Pos.Y, trigger->Pos.Z); + if (dist > trigger->Radius) + return false; + } + else + { + Position center(trigger->Pos.X, trigger->Pos.Y, trigger->Pos.Z, trigger->BoxYaw); + if (!bot->IsWithinBox(center, trigger->BoxLength / 2.f, trigger->BoxWidth / 2.f, trigger->BoxHeight / 2.f)) + return false; + } + + return true; +} + +BotMgr::delayed_teleport_mutex_type* BotMgr::_getTpLock() +{ + static BotMgr::delayed_teleport_mutex_type _lock; + return &_lock; +} +void BotMgr::AddDelayedTeleportCallback(delayed_teleport_callback_type&& callback) +{ + delayed_teleport_lock_type lock(*_getTpLock()); + delayed_bot_teleports.push_back(std::forward(callback)); +} +void BotMgr::HandleDelayedTeleports() +{ + for (auto& func : delayed_bot_teleports) + func(); + delayed_bot_teleports.clear(); +} + +#ifdef _MSC_VER +# pragma warning(pop) +#endif diff --git a/src/server/game/AI/NpcBots/botmgr.h b/src/server/game/AI/NpcBots/botmgr.h new file mode 100644 index 000000000..4a636a2da --- /dev/null +++ b/src/server/game/AI/NpcBots/botmgr.h @@ -0,0 +1,350 @@ +#ifndef _BOTMGR_H +#define _BOTMGR_H + +#include "botcommon.h" + +#include +#include + +class bot_ai; +class Battleground; +class Creature; +class GameObject; +class Group; +class Map; +class Player; +class Spell; +class SpellInfo; +class Unit; +class Vehicle; +class WorldLocation; +class WorldObject; +class WorldPacket; + +class DPSTracker; + +struct AreaTriggerEntry; +struct CleanDamage; +struct GroupQueueInfo; +struct Position; + +enum BattlegroundTypeId : uint32; +enum CurrentSpellTypes : uint8; +enum DamageEffectType : uint8; + +constexpr size_t TargetIconNamesCacheSize = 8u; // Group.h TARGETICONCOUNT +constexpr size_t BracketsCount = DEFAULT_MAX_LEVEL / 10 + 1; //0-9, 10-19, 20-29, 30-39, 40-49, 50-59, 60-69, 70-79, 80-83 + +enum BotAddResult +{ + BOT_ADD_DISABLED = 0x001, + BOT_ADD_ALREADY_HAVE = 0x002, + BOT_ADD_MAX_EXCEED = 0x004, + BOT_ADD_MAX_CLASS_EXCEED = 0x008, + BOT_ADD_CANT_AFFORD = 0x010, + BOT_ADD_INSTANCE_LIMIT = 0x020, + BOT_ADD_BUSY = 0x040, // unused + BOT_ADD_NOT_AVAILABLE = 0x080, + + BOT_ADD_SUCCESS = 0x100, + + BOT_ADD_FATAL = (BOT_ADD_DISABLED | BOT_ADD_CANT_AFFORD | BOT_ADD_MAX_EXCEED | BOT_ADD_MAX_CLASS_EXCEED) +}; + +enum BotRemoveType +{ + BOT_REMOVE_LOGOUT = 0, + BOT_REMOVE_DISMISS = 1, + BOT_REMOVE_UNSUMMON = 2, + BOT_REMOVE_UNBIND = 3, + BOT_REMOVE_BY_DEFAULT = BOT_REMOVE_LOGOUT +}; + +enum BotOwnershipExpireMode +{ + BOT_OWNERSHIP_EXPIRE_OFFLINE = 0, + BOT_OWNERSHIP_EXPIRE_HIRE = 1 +}; + +enum BotAttackRange +{ + BOT_ATTACK_RANGE_SHORT = 1, + BOT_ATTACK_RANGE_LONG = 2, + BOT_ATTACK_RANGE_EXACT = 3 +}; + +enum BotAttackAngle +{ + BOT_ATTACK_ANGLE_NORMAL = 1, + BOT_ATTACK_ANGLE_AVOID_FRONTAL_AOE = 2 +}; + +typedef std::unordered_map BotMap; +template +using BotBrackets = std::array; +typedef BotBrackets LvlBrackets; +typedef BotBrackets PctBrackets; + +class TC_GAME_API BotMgr +{ + public: + using delayed_teleport_callback_type = std::function; + using delayed_teleport_mutex_type = std::mutex; + using delayed_teleport_lock_type = std::unique_lock; + + BotMgr(Player* const master); + ~BotMgr(); + + Player* GetOwner() const { return _owner; } + + BotMap const* GetBotMap() const { return &_bots; } + BotMap* GetBotMap() { return &_bots; } + + static bool IsNpcBotModEnabled(); + static bool IsNpcBotLogEnabled(); + static bool IsNpcBotDungeonFinderEnabled(); + static bool DisplayEquipment(); + static bool ShowEquippedCloak(); + static bool ShowEquippedHelm(); + static bool SendEquipListItems(); + static bool IsGearBankEnabled(); + static bool IsTransmogEnabled(); + static bool MixArmorClasses(); + static bool MixWeaponClasses(); + static bool MixWeaponInventoryTypes(); + static bool TransmogUseEquipmentSlots(); + static bool IsClassEnabled(uint8 m_class); + static bool IsWanderingClassEnabled(uint8 m_class); + static bool HideBotSpawns(); + static bool IsEnrageOnDimissEnabled(); + static bool IsBotStatsLimitsEnabled(); + static bool IsPvPEnabled(); + static bool IsFoodInterruptedByMovement(); + static bool FilterRaces(); + static bool IsBotGenerationEnabledBGs(); + static bool IsBotLevelCappedByConfigBG(); + static bool IsBotLevelCappedByConfigBGFirstPlayer(); + static bool IsBotGenerationEnabledWorldMapId(uint32 mapId); + static bool IsBotHKEnabled(); + static bool IsBotHKMessageEnabled(); + static bool IsBotHKAchievementsEnabled(); + static uint8 GetMaxClassBots(); + static uint8 GetMaxAccountBots(); + static uint8 GetHealTargetIconFlags(); + static uint8 GetTankTargetIconFlags(); + static uint8 GetOffTankTargetIconFlags(); + static uint8 GetDPSTargetIconFlags(); + static uint8 GetRangedDPSTargetIconFlags(); + static uint8 GetNoDPSTargetIconFlags(); + static uint32 GetBaseUpdateDelay(); + static uint32 GetOwnershipExpireTime(); + static uint8 GetOwnershipExpireMode(); + static uint32 GetDesiredWanderingBotsCount(); + static uint32 GetBGTargetTeamPlayersCount(BattlegroundTypeId bgTypeId); + static float GetBotHKHonorRate(); + static float GetBotStatLimitDodge(); + static float GetBotStatLimitParry(); + static float GetBotStatLimitBlock(); + static float GetBotStatLimitCrit(); + static float GetBotDamageModPhysical(); + static float GetBotDamageModSpell(); + static float GetBotHealingMod(); + static float GetBotHPMod(); + static float GetBotWandererDamageMod(); + static float GetBotWandererHealingMod(); + static float GetBotWandererHPMod(); + static float GetBotWandererSpeedMod(); + static float GetBotWandererXPGainMod(); + static PctBrackets GetBotWandererLevelBrackets(); + static float GetBotDamageModByClass(uint8 botclass); + static float GetBotDamageModByLevel(uint8 botlevel); + + static void Initialize(); + static void ReloadConfig(); + static void LoadConfig(bool reload = false); + static void ResolveConfigConflicts(); + + //onEvent hooks + static void OnBotWandererKilled(Creature const* bot, Player* looter); + static void OnBotWandererKilled(GameObject* go); + static void OnBotKilled(Creature const* bot, Unit* attacker = nullptr); + static void OnBotSpellInterrupt(Unit const* caster, CurrentSpellTypes spellType); + static void OnBotSpellGo(Unit const* caster, Spell const* spell, bool ok = true); + static void OnBotOwnerSpellGo(Unit const* caster, Spell const* spell, bool ok = true); + static void OnBotChannelFinish(Unit const* caster, Spell const* spell); + static void OnVehicleSpellGo(Unit const* caster, Spell const* spell, bool ok = true); + static void OnVehicleAttackedBy(Unit* attacker, Unit const* victim); + static void OnBotDamageTaken(Unit* attacker, Unit* victim, uint32 damage, CleanDamage const* cleanDamage, DamageEffectType damagetype, SpellInfo const* spellInfo); + static void OnBotDamageDealt(Unit* attacker, Unit* victim, uint32 damage, CleanDamage const* cleanDamage, DamageEffectType damagetype, SpellInfo const* spellInfo); + static void OnBotDispelDealt(Unit* dispeller, Unit* dispelled, uint8 num); + static void OnBotEnterVehicle(Creature const* passenger, Vehicle const* vehicle); + static void OnBotExitVehicle(Creature const* passenger, Vehicle const* vehicle); + static void OnBotOwnerEnterVehicle(Player const* passenger, Vehicle const* vehicle); + static void OnBotOwnerExitVehicle(Player const* passenger, Vehicle const* vehicle); + static void OnBotPartyEngage(Player const* owner); + //mod hooks + static void ApplyBotEffectMods(Unit const* caster, SpellInfo const* spellInfo, uint8 effIndex, float& value); + static void ApplyBotThreatMods(Unit const* attacker, SpellInfo const* spellInfo, float& threat); + static void ApplyBotEffectValueMultiplierMods(Unit const* caster, SpellInfo const* spellInfo, SpellEffIndex effIndex, float& multiplier); + static float GetBotDamageTakenMod(Creature const* bot, bool magic); + static int32 GetBotStat(Creature const* bot, BotStatMods stat); + static float GetBotResilience(Creature const* botOrPet); + + void Update(uint32 diff); + + Creature* GetBot(ObjectGuid guid) const; + Creature* GetBotByName(std::string_view name) const; + std::list GetAllBotsByClass(uint8 botclass) const; + + bool HaveBot() const { return !_bots.empty(); } + uint8 GetNpcBotsCount() const; + uint8 GetNpcBotsCountByRole(uint32 roles) const; + uint8 GetNpcBotsCountByVehicleEntry(uint32 creEntry) const; + uint8 GetNpcBotSlot(Creature const* bot) const; + uint8 GetNpcBotSlotByRole(uint32 roles, Creature const* bot) const; + uint32 GetAllNpcBotsClassMask() const; + static uint8 GetMaxNpcBots(uint8 level); + static uint8 GetNpcBotXpReduction(); + static uint8 GetNpcBotXpReductionStartingNumber(); + static uint8 GetNpcBotMountLevel60(); + static uint8 GetNpcBotMountLevel100(); + static int32 GetBotInfoPacketsLimit(); + static bool LimitBots(Map const* map); + static bool CanBotParryWhileCasting(Creature const* bot); + static bool IsWanderingWorldBot(Creature const* bot); + static bool IsBotContestedPvP(Creature const* bot); + static void SetBotContestedPvP(Creature const* bot); + bool IsMapAllowedForBots(Map const* map) const; + bool RestrictBots(Creature const* bot, bool add) const; + bool IsPartyInCombat() const; + bool HasBotClass(uint8 botclass) const; + bool HasBotWithSpec(uint8 spec, bool alive = true) const; + bool HasBotPetType(uint32 petType) const; + bool IsBeingResurrected(WorldObject const* corpse) const; + + static uint32 GetNpcBotCost(uint8 level, uint8 botclass); + static std::string GetNpcBotCostStr(uint8 level, uint8 botclass); + static uint8 BotClassByClassName(std::string const& className); + static uint8 GetBotPlayerClass(uint8 bot_class); + static uint8 GetBotPlayerRace(uint8 bot_class, uint8 bot_race); + static uint8 GetBotPlayerClass(Creature const* bot); + static uint8 GetBotPlayerRace(Creature const* bot); + static uint8 GetBotEquipmentClass(uint8 bot_class); + + std::string GetTargetIconString(uint8 icon_idx) const; + + void OnTeleportFar(uint32 mapId, float x, float y, float z, float ori = 0.f); + void OnOwnerSetGameMaster(bool on); + void ReviveAllBots(); + void SendBotCommandState(uint32 state); + void SendBotCommandStateRemove(uint32 state); + void SendBotAwaitState(uint8 state); + void RecallAllBots(bool teleport = false); + void RecallBot(Creature* bot); + void KillAllBots(); + void KillBot(Creature* bot) const; + + void CleanupsBeforeBotDelete(ObjectGuid guid, uint8 removetype = BOT_REMOVE_LOGOUT); + static void CleanupsBeforeBotDelete(Creature* bot); + void RemoveAllBots(uint8 removetype = BOT_REMOVE_LOGOUT); + void RemoveBot(ObjectGuid guid, uint8 removetype = BOT_REMOVE_LOGOUT); + void UnbindBot(ObjectGuid guid); + [[nodiscard]] BotAddResult RebindBot(Creature* bot); + [[nodiscard]] BotAddResult AddBot(Creature* bot); + bool AddBotToGroup(Creature* bot); + void RemoveBotFromBGQueue(Creature const* bot); + bool RemoveBotFromGroup(Creature* bot); + bool RemoveAllBotsFromGroup(); + + static uint8 GetBotFollowDistDefault() { return 100; } + uint8 GetBotFollowDist() const { return _followdist; } + void SetBotFollowDist(uint8 dist) { _followdist = dist; } + + uint8 GetBotExactAttackRange() const { return _exactAttackRange; } + uint8 GetBotAttackRangeMode() const { return _attackRangeMode; } + void SetBotAttackRangeMode(uint8 mode, uint8 exactRange = 0) { _attackRangeMode = mode; _setBotExactAttackRange(exactRange); } + + uint8 GetBotAttackAngleMode() const { return _attackAngleMode; } + void SetBotAttackAngleMode(uint8 mode) { _attackAngleMode = mode; } + + bool GetBotAllowCombatPositioning() const { return _allowCombatPositioning; } + void SetBotAllowCombatPositioning(bool allow) { _allowCombatPositioning = allow; } + + uint32 GetEngageDelayDPS() const { return _npcBotEngageDelayDPS; } + uint32 GetEngageDelayHeal() const { return _npcBotEngageDelayHeal; } + void SetEngageDelayDPS(uint32 delay) { _npcBotEngageDelayDPS = delay; } + void SetEngageDelayHeal(uint32 delay) { _npcBotEngageDelayHeal = delay; } + void PropagateEngageTimers() const; + + void SetBotsHidden(bool hidden) { _botsHidden = hidden; } + + void SetBotsShouldUpdateStats(); + void UpdatePhaseForBots(); + void UpdatePvPForBots(); + + static void BuildBotPartyMemberStatsPacket(ObjectGuid bot_guid, WorldPacket* data); + static void BuildBotPartyMemberStatsChangedPacket(Creature const* bot, WorldPacket* data); + //static uint32 GetBotGroupUpdateFlag(Creature const* bot); + static void SetBotGroupUpdateFlag(Creature const* bot, uint32 flag); + static uint64 GetBotAuraUpdateMaskForRaid(Creature const* bot); + static void SetBotAuraUpdateMaskForRaid(Creature const* bot, uint8 slot); + static void ResetBotAuraUpdateMaskForRaid(Creature const* bot); + static uint64 GetBotPetAuraUpdateMaskForRaid(Creature const* botpet); + static void SetBotPetAuraUpdateMaskForRaid(Creature const* botpet, uint8 slot); + static void ResetBotPetAuraUpdateMaskForRaid(Creature const* botpet); + + void TrackDamage(Unit const* u, uint32 damage); + uint32 GetDPSTaken(Unit const* u) const; + int32 GetHPSTaken(Unit const* unit) const; + + static void ReviveBot(Creature* bot, WorldLocation* dest = nullptr) { _reviveBot(bot, dest); } + + //TELEPORT BETWEEN MAPS + //CONFIRMEND UNSAFE (charmer,owner) + static void TeleportBot(Creature* bot, Map* newMap, Position const* pos, bool quick = false, bool reset = false, bot_ai* detached_ai = nullptr); + + AoeSpotsVec const& GetAoeSpots() const { return _aoespots; } + AoeSpotsVec& GetAoeSpots() { return _aoespots; } + + void UpdateTargetIconName(uint8 id, std::string const& name); + void ResetTargetIconNames(); + + static std::vector GetAllGroupMembers(Group const* group); + static std::vector GetAllGroupMembers(Unit const* source); + static void InviteBotToBG(ObjectGuid botguid, GroupQueueInfo* ginfo, Battleground* bg); + + static bool IsBotInAreaTriggerRadius(Creature const* bot, AreaTriggerEntry const* trigger); + + static void AddDelayedTeleportCallback(delayed_teleport_callback_type&& callback); + static void HandleDelayedTeleports(); + + private: + static void _teleportBot(Creature* bot, Map* newMap, float x, float y, float z, float ori, bool quick, bool reset, bot_ai* detached_ai); + static void _reviveBot(Creature* bot, WorldLocation* dest = nullptr); + void _setBotExactAttackRange(uint8 exactRange) { _exactAttackRange = exactRange; } + static delayed_teleport_mutex_type* _getTpLock(); + + Player* const _owner; + BotMap _bots; + std::list _removeList; + DPSTracker* const _dpstracker; + + uint8 _followdist; + uint8 _exactAttackRange; + uint8 _attackRangeMode; + uint8 _attackAngleMode; + bool _allowCombatPositioning; + uint32 _npcBotEngageDelayDPS; + uint32 _npcBotEngageDelayHeal; + + bool _botsHidden; + bool _quickrecall; + + AoeSpotsVec _aoespots; + + std::array _targetIconNamesCache; +}; + +void AddNpcBotScripts(); + +#endif diff --git a/src/server/game/AI/NpcBots/botspell.cpp b/src/server/game/AI/NpcBots/botspell.cpp new file mode 100644 index 000000000..b8c081348 --- /dev/null +++ b/src/server/game/AI/NpcBots/botspell.cpp @@ -0,0 +1,2095 @@ +#include "botspell.h" +#include "DBCStores.h" +#include "Log.h" +#include "SpellInfo.h" +#include "SpellMgr.h" +#include "Timer.h" + +#include + +typedef std::unordered_map SpellInfoOverridesMap; +typedef std::unordered_map SpellProcOverridesMap; +static SpellInfoOverridesMap botSpellInfoOverrides; +static SpellProcOverridesMap botSpellProcOverrides; + +void GenerateBotCustomSpellProcs() +{ + botSpellProcOverrides.clear(); + + bool isTriggerAura[TOTAL_AURAS]; + bool isAlwaysTriggeredAura[TOTAL_AURAS]; + uint32 spellTypeMask[TOTAL_AURAS]; + for (uint16 i = 0; i < TOTAL_AURAS; ++i) + { + isTriggerAura[i] = false; + isAlwaysTriggeredAura[i] = false; + spellTypeMask[i] = PROC_SPELL_TYPE_MASK_ALL; + } + + isTriggerAura[SPELL_AURA_DUMMY] = true; // Most dummy auras should require scripting, but there are some exceptions (ie 12311) + isTriggerAura[SPELL_AURA_MOD_CONFUSE] = true; // "Any direct damaging attack will revive targets" + isTriggerAura[SPELL_AURA_MOD_THREAT] = true; // Only one spell: 28762 part of Mage T3 8p bonus + isTriggerAura[SPELL_AURA_MOD_STUN] = true; // Aura does not have charges but needs to be removed on trigger + isTriggerAura[SPELL_AURA_MOD_DAMAGE_DONE] = true; + isTriggerAura[SPELL_AURA_MOD_DAMAGE_TAKEN] = true; + isTriggerAura[SPELL_AURA_MOD_RESISTANCE] = true; + isTriggerAura[SPELL_AURA_MOD_STEALTH] = true; + isTriggerAura[SPELL_AURA_MOD_FEAR] = true; // Aura does not have charges but needs to be removed on trigger + isTriggerAura[SPELL_AURA_MOD_ROOT] = true; + isTriggerAura[SPELL_AURA_TRANSFORM] = true; + isTriggerAura[SPELL_AURA_REFLECT_SPELLS] = true; + isTriggerAura[SPELL_AURA_DAMAGE_IMMUNITY] = true; + isTriggerAura[SPELL_AURA_PROC_TRIGGER_SPELL] = true; + isTriggerAura[SPELL_AURA_PROC_TRIGGER_DAMAGE] = true; + isTriggerAura[SPELL_AURA_MOD_CASTING_SPEED_NOT_STACK] = true; + isTriggerAura[SPELL_AURA_MOD_POWER_COST_SCHOOL_PCT] = true; + isTriggerAura[SPELL_AURA_MOD_POWER_COST_SCHOOL] = true; + isTriggerAura[SPELL_AURA_REFLECT_SPELLS_SCHOOL] = true; + isTriggerAura[SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN] = true; + isTriggerAura[SPELL_AURA_MOD_ATTACK_POWER] = true; + isTriggerAura[SPELL_AURA_ADD_CASTER_HIT_TRIGGER] = true; + isTriggerAura[SPELL_AURA_OVERRIDE_CLASS_SCRIPTS] = true; + isTriggerAura[SPELL_AURA_MOD_MELEE_HASTE] = true; + isTriggerAura[SPELL_AURA_MOD_ATTACKER_MELEE_HIT_CHANCE] = true; + isTriggerAura[SPELL_AURA_RAID_PROC_FROM_CHARGE] = true; + isTriggerAura[SPELL_AURA_RAID_PROC_FROM_CHARGE_WITH_VALUE] = true; + isTriggerAura[SPELL_AURA_PROC_TRIGGER_SPELL_WITH_VALUE] = true; + isTriggerAura[SPELL_AURA_MOD_SPELL_CRIT_CHANCE] = true; + isTriggerAura[SPELL_AURA_ADD_FLAT_MODIFIER] = true; + isTriggerAura[SPELL_AURA_ADD_PCT_MODIFIER] = true; + isTriggerAura[SPELL_AURA_ABILITY_IGNORE_AURASTATE] = true; + isTriggerAura[SPELL_AURA_MOD_INVISIBILITY] = true; + isTriggerAura[SPELL_AURA_FORCE_REACTION] = true; + isTriggerAura[SPELL_AURA_MOD_TAUNT] = true; + isTriggerAura[SPELL_AURA_MOD_DETAUNT] = true; + isTriggerAura[SPELL_AURA_MOD_DAMAGE_PERCENT_DONE] = true; + isTriggerAura[SPELL_AURA_MOD_ATTACK_POWER_PCT] = true; + isTriggerAura[SPELL_AURA_MOD_HIT_CHANCE] = true; + isTriggerAura[SPELL_AURA_MOD_WEAPON_CRIT_PERCENT] = true; + isTriggerAura[SPELL_AURA_MOD_BLOCK_PERCENT] = true; + + isAlwaysTriggeredAura[SPELL_AURA_OVERRIDE_CLASS_SCRIPTS] = true; + isAlwaysTriggeredAura[SPELL_AURA_MOD_STEALTH] = true; + isAlwaysTriggeredAura[SPELL_AURA_MOD_CONFUSE] = true; + isAlwaysTriggeredAura[SPELL_AURA_MOD_FEAR] = true; + isAlwaysTriggeredAura[SPELL_AURA_MOD_ROOT] = true; + isAlwaysTriggeredAura[SPELL_AURA_MOD_STUN] = true; + isAlwaysTriggeredAura[SPELL_AURA_TRANSFORM] = true; + isAlwaysTriggeredAura[SPELL_AURA_MOD_INVISIBILITY] = true; + + spellTypeMask[SPELL_AURA_MOD_STEALTH] = PROC_SPELL_TYPE_DAMAGE | PROC_SPELL_TYPE_NO_DMG_HEAL; + spellTypeMask[SPELL_AURA_MOD_CONFUSE] = PROC_SPELL_TYPE_DAMAGE; + spellTypeMask[SPELL_AURA_MOD_FEAR] = PROC_SPELL_TYPE_DAMAGE; + spellTypeMask[SPELL_AURA_MOD_ROOT] = PROC_SPELL_TYPE_DAMAGE; + spellTypeMask[SPELL_AURA_MOD_STUN] = PROC_SPELL_TYPE_DAMAGE; + spellTypeMask[SPELL_AURA_TRANSFORM] = PROC_SPELL_TYPE_DAMAGE; + spellTypeMask[SPELL_AURA_MOD_INVISIBILITY] = PROC_SPELL_TYPE_DAMAGE; + + for (auto const& p : botSpellInfoOverrides) + { + SpellInfo const& spellInfo = p.second; + + if (!spellInfo.ProcFlags) + continue; + + bool addTriggerFlag = false; + uint32 procSpellTypeMask = PROC_SPELL_TYPE_NONE; + uint32 nonProcMask = 0; + for (SpellEffectInfo const& spellEffectInfo : spellInfo.GetEffects()) + { + if (!spellEffectInfo.IsEffect()) + continue; + + uint32 auraName = spellEffectInfo.ApplyAuraName; + if (!auraName) + continue; + + if (!isTriggerAura[auraName]) + { + // explicitly disable non proccing auras to avoid losing charges on self proc + nonProcMask |= 1 << spellEffectInfo.EffectIndex; + continue; + } + + procSpellTypeMask |= spellTypeMask[auraName]; + if (isAlwaysTriggeredAura[auraName]) + addTriggerFlag = true; + + if (!addTriggerFlag && (spellInfo.ProcFlags & TAKEN_HIT_PROC_FLAG_MASK) != 0) + { + switch (auraName) + { + case SPELL_AURA_PROC_TRIGGER_SPELL: + case SPELL_AURA_PROC_TRIGGER_DAMAGE: + addTriggerFlag = true; + break; + default: + break; + } + } + } + + if (!procSpellTypeMask) + { + for (SpellEffectInfo const& spellEffectInfo : spellInfo.GetEffects()) + { + if (spellEffectInfo.IsAura()) + { + TC_LOG_ERROR("scripts", "Bot spell {} has ProcFlags {}, but it's of non-proc aura type, needs a correction", spellInfo.Id, spellInfo.ProcFlags); + break; + } + } + continue; + } + + SpellProcEntry procEntry; + procEntry.SchoolMask = 0; + procEntry.ProcFlags = spellInfo.ProcFlags; + procEntry.SpellFamilyName = 0; + for (SpellEffectInfo const& spellEffectInfo : spellInfo.GetEffects()) + if (spellEffectInfo.IsEffect() && isTriggerAura[spellEffectInfo.ApplyAuraName]) + procEntry.SpellFamilyMask |= spellEffectInfo.SpellClassMask; + + if (procEntry.SpellFamilyMask) + procEntry.SpellFamilyName = spellInfo.SpellFamilyName; + + procEntry.SpellTypeMask = procSpellTypeMask; + procEntry.SpellPhaseMask = PROC_SPELL_PHASE_HIT; + procEntry.HitMask = PROC_HIT_NONE; + + for (SpellEffectInfo const& spellEffectInfo : spellInfo.GetEffects()) + { + if (!spellEffectInfo.IsAura()) + continue; + + switch (spellEffectInfo.ApplyAuraName) + { + case SPELL_AURA_REFLECT_SPELLS: + case SPELL_AURA_REFLECT_SPELLS_SCHOOL: + procEntry.HitMask = PROC_HIT_REFLECT; + break; + case SPELL_AURA_MOD_WEAPON_CRIT_PERCENT: + procEntry.HitMask = PROC_HIT_CRITICAL; + break; + case SPELL_AURA_MOD_BLOCK_PERCENT: + procEntry.HitMask = PROC_HIT_BLOCK; + break; + case SPELL_AURA_MOD_HIT_CHANCE: + if (spellEffectInfo.CalcValue() <= -100) + procEntry.HitMask = PROC_HIT_MISS; + break; + default: + continue; + } + break; + } + + procEntry.AttributesMask = 0; + procEntry.DisableEffectsMask = nonProcMask; + if (spellInfo.ProcFlags & PROC_FLAG_KILL) + procEntry.AttributesMask |= PROC_ATTR_REQ_EXP_OR_HONOR; + if (addTriggerFlag) + procEntry.AttributesMask |= PROC_ATTR_TRIGGERED_CAN_PROC; + + procEntry.ProcsPerMinute = 0; + procEntry.Chance = spellInfo.ProcChance; + procEntry.Cooldown = Milliseconds::zero(); + procEntry.Charges = spellInfo.ProcCharges; + + botSpellProcOverrides[spellInfo.Id] = std::move(procEntry); + } + + TC_LOG_INFO("server.loading", ">> Bot spell proc overrides generated for {} spells", uint32(botSpellProcOverrides.size())); + +} + +SpellInfo const* GetBotSpellInfoOverride(uint32 spellId) +{ + decltype(botSpellInfoOverrides)::const_iterator ci = botSpellInfoOverrides.find(spellId); + return ci != botSpellInfoOverrides.cend() ? &ci->second : nullptr; +} + +SpellInfo const* AssertBotSpellInfoOverride(uint32 spellId) +{ + decltype(botSpellInfoOverrides)::const_iterator ci = botSpellInfoOverrides.find(spellId); + ASSERT(ci != botSpellInfoOverrides.cend(), "AssertBotSpellInfoOverride failed for spell Id %u!", spellId); + return &ci->second; +} + +SpellProcEntry const* GetBotSpellProceEntryOverride(uint32 spellId) +{ + decltype(botSpellProcOverrides)::const_iterator ci = botSpellProcOverrides.find(spellId); + return ci != botSpellProcOverrides.cend() ? &ci->second : nullptr; +} + +void GenerateBotCustomSpells() +{ + botSpellInfoOverrides.clear(); + + uint32 spellId, triggerSpellId; + SpellInfo* sinfo; + + //COMMON + //1) SPELL_TELEPORT_LOCAL + spellId = SPELL_TELEPORT_LOCAL; //7794 + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + + sinfo->InterruptFlags = SPELL_INTERRUPT_FLAG_ABORT_ON_DMG; + sinfo->CastTimeEntry = sSpellCastTimesStore.LookupEntry(6); //5000ms + //sinfo->CastTimeEntry = sSpellCastTimesStore.LookupEntry(4); //1000ms + sinfo->RangeEntry = sSpellRangeStore.LookupEntry(1); //self + sinfo->ExplicitTargetMask = TARGET_FLAG_DEST_LOCATION; + sinfo->Attributes |= SPELL_ATTR0_UNAFFECTED_BY_INVULNERABILITY | SPELL_ATTR0_CASTABLE_WHILE_MOUNTED; + sinfo->AttributesEx2 |= SPELL_ATTR2_CAN_TARGET_NOT_IN_LOS; + + sinfo->_effects[0].Effect = SPELL_EFFECT_TELEPORT_UNITS; + sinfo->_effects[0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_CASTER); + sinfo->_effects[0].TargetB = SpellImplicitTargetInfo(TARGET_DEST_DEST); + sinfo->_effects[0].BasePoints = 1; + + // SPELL_NULLIFY_POISON + spellId = SPELL_NULLIFY_POISON; //550 + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + + sinfo->PreventionType = SPELL_PREVENTION_TYPE_NONE; + sinfo->SpellLevel = 0; + sinfo->MaxLevel = 0; + sinfo->RecoveryTime = 0; + sinfo->StartRecoveryCategory = 0; + sinfo->StartRecoveryTime = 0; + sinfo->ManaCost = 0; + sinfo->ManaCostPercentage = 0; + sinfo->ManaCostPerlevel = 0; + sinfo->CastTimeEntry = sSpellCastTimesStore.LookupEntry(1); //0 + sinfo->DurationEntry = sSpellDurationStore.LookupEntry(21); //-1 + sinfo->RangeEntry = sSpellRangeStore.LookupEntry(1); //self + sinfo->ExplicitTargetMask = TARGET_FLAG_UNIT; + sinfo->Attributes &= ~(SPELL_ATTR0_NOT_SHAPESHIFT); + sinfo->Attributes |= SPELL_ATTR0_PASSIVE | SPELL_ATTR0_HIDDEN_CLIENTSIDE | SPELL_ATTR0_HIDE_IN_COMBAT_LOG; + + sinfo->_effects[0].Effect = SPELL_EFFECT_APPLY_AURA; + sinfo->_effects[0].ApplyAuraName = SPELL_AURA_MOD_AURA_DURATION_BY_DISPEL; + sinfo->_effects[0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_CASTER); + sinfo->_effects[0].TargetB = SpellImplicitTargetInfo(0); + sinfo->_effects[0].BasePoints = -200; + sinfo->_effects[0].MiscValue = DISPEL_POISON; + sinfo->_effects[0].RealPointsPerLevel = 0.0f; + sinfo->_effects[0].BonusMultiplier = 0.0f; + sinfo->_effects[0].DamageMultiplier = 0.0f; + // END SPELL_NULLIFY_POISON + + //BLADEMASTER + //2) SPELL_COMBAT_SPECIAL_2H_ATTACK + spellId = SPELL_COMBAT_SPECIAL_2H_ATTACK; //44079 + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + + sinfo->RangeEntry = sSpellRangeStore.LookupEntry(6); //6 - 100 yds + sinfo->Attributes &= ~(SPELL_ATTR0_CANT_USED_IN_COMBAT); + sinfo->AttributesEx2 |= SPELL_ATTR2_CAN_TARGET_DEAD; + //2) END SPELL_COMBAT_SPECIAL_2H_ATTACK + + //3) WINDWALK + //3.1) TRANSPARENCY + spellId = SPELL_TRANSPARENCY_50; //44816 + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + triggerSpellId = spellId; + + sinfo->Attributes |= (SPELL_ATTR0_NOT_SHAPESHIFT | SPELL_ATTR0_CASTABLE_WHILE_SITTING); + sinfo->AttributesEx |= (SPELL_ATTR1_NOT_BREAK_STEALTH); + sinfo->AuraInterruptFlags = + AURA_INTERRUPT_FLAG_SPELL_ATTACK | AURA_INTERRUPT_FLAG_MELEE_ATTACK | + AURA_INTERRUPT_FLAG_NOT_ABOVEWATER | AURA_INTERRUPT_FLAG_MOUNT; //0x00003C07;vanish + sinfo->CasterAuraStateNot = 0; + //3.1) END TRANSPARENCY + + spellId = SPELL_NETHERWALK; //31599 + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + + sinfo->SpellLevel = 0; + sinfo->MaxLevel = 0; + sinfo->RecoveryTime = 5000; + sinfo->PowerType = POWER_MANA; + sinfo->ManaCost = 75 * 5; + sinfo->ManaCostPercentage = 0; + sinfo->ManaCostPerlevel = 0; + sinfo->Attributes &= ~(SPELL_ATTR0_UNK11); + sinfo->Attributes |= (SPELL_ATTR0_NOT_SHAPESHIFT | SPELL_ATTR0_CASTABLE_WHILE_SITTING | SPELL_ATTR0_UNAFFECTED_BY_INVULNERABILITY); + sinfo->AttributesEx &= ~SPELL_ATTR1_DONT_REFRESH_DURATION_ON_RECAST; + sinfo->AttributesEx |= (SPELL_ATTR1_NOT_BREAK_STEALTH | SPELL_ATTR1_NO_THREAT); + sinfo->AttributesEx2 |= SPELL_ATTR2_UNK1; + sinfo->AuraInterruptFlags = + AURA_INTERRUPT_FLAG_SPELL_ATTACK | AURA_INTERRUPT_FLAG_MELEE_ATTACK | + AURA_INTERRUPT_FLAG_NOT_ABOVEWATER | AURA_INTERRUPT_FLAG_MOUNT; //0x00003C07;vanish + sinfo->CasterAuraStateNot = 0; + + sinfo->_effects[0].Effect = SPELL_EFFECT_APPLY_AURA; + sinfo->_effects[0].BasePoints = 100; + sinfo->_effects[0].RealPointsPerLevel = 2.5f; + sinfo->_effects[0].ValueMultiplier = 1.0f; + sinfo->_effects[0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_CASTER); + sinfo->_effects[0].ApplyAuraName = SPELL_AURA_MOD_INVISIBILITY; + sinfo->_effects[0].Amplitude = 0; + sinfo->_effects[0].TriggerSpell = 0; + sinfo->_effects[0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_0_YARDS); + + sinfo->_effects[1].Effect = SPELL_EFFECT_APPLY_AURA; + sinfo->_effects[1].BasePoints = 10; + sinfo->_effects[1].RealPointsPerLevel = 0.5f; + sinfo->_effects[1].ValueMultiplier = 1.0f; + sinfo->_effects[1].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_CASTER); + sinfo->_effects[1].TargetB = SpellImplicitTargetInfo(0); + sinfo->_effects[1].ApplyAuraName = SPELL_AURA_MOD_INCREASE_SPEED; + sinfo->_effects[1].Amplitude = 0; + sinfo->_effects[1].TriggerSpell = 0; + sinfo->_effects[1].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_0_YARDS); //14 + + sinfo->_effects[2].Effect = SPELL_EFFECT_TRIGGER_SPELL; + sinfo->_effects[2].BasePoints = 0; + sinfo->_effects[2].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_CASTER); + sinfo->_effects[2].TargetB = SpellImplicitTargetInfo(0); + sinfo->_effects[2].ApplyAuraName = SPELL_AURA_NONE; + sinfo->_effects[2].Amplitude = 0; + sinfo->_effects[2].TriggerSpell = triggerSpellId; + sinfo->_effects[2].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_0_YARDS); //14 + //3) END WINDWALK + + //4) MIRROR IMAGE (BLADEMASTER) + spellId = SPELL_MIRROR_IMAGE_BM; //69936 + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + + sinfo->RangeEntry = sSpellRangeStore.LookupEntry(1); //1 - self only //6 - 100 yds + sinfo->DurationEntry = sSpellDurationStore.LookupEntry(566); //566 - 0 sec //3 - 60 sec //1 - 10 sec //32 - 6 seconds + sinfo->RecoveryTime = 8000; + sinfo->PowerType = POWER_MANA; + sinfo->ManaCost = 125 * 5; + sinfo->ManaCostPercentage = 0; + sinfo->ManaCostPerlevel = 0; + sinfo->Attributes |= (SPELL_ATTR0_NOT_SHAPESHIFT/* | SPELL_ATTR0_CASTABLE_WHILE_SITTING | SPELL_ATTR0_UNAFFECTED_BY_INVULNERABILITY*/); + sinfo->AttributesEx2 &= ~(SPELL_ATTR2_CAN_TARGET_NOT_IN_LOS); + //sinfo->AttributesEx3 |= SPELL_ATTR3_DONT_DISPLAY_RANGE; + + sinfo->_effects[0].Effect = SPELL_EFFECT_DUMMY; + sinfo->_effects[0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_CASTER); + sinfo->_effects[0].MiscValue = 0; + sinfo->_effects[0].MiscValueB = 0; + sinfo->_effects[0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_0_YARDS); + //4) END MIRROR IMAGE (BLADEMASTER) + + //SPHYNX + //5) SHADOW BLAST (SPLASH ATTACK) + //TODO: balance + spellId = SPELL_SHADOW_BLAST; //38085 + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + + sinfo->SpellFamilyName = SPELLFAMILY_WARLOCK; + sinfo->SpellLevel = 60; + sinfo->MaxLevel = 83; + sinfo->ManaCost = BASE_MANA_SPHYNX / 16; + sinfo->ManaCostPercentage = 0; + sinfo->ManaCostPerlevel = 0; + sinfo->CastTimeEntry = nullptr; + sinfo->RangeEntry = sSpellRangeStore.LookupEntry(4); //30 yds + sinfo->SchoolMask = SPELL_SCHOOL_MASK_SHADOW | SPELL_SCHOOL_MASK_ARCANE; + sinfo->ExplicitTargetMask = TARGET_FLAG_UNIT | TARGET_FLAG_DEST_LOCATION; + //sinfo->MaxAffectedTargets = 1000; + //sinfo->Attributes |= SPELL_ATTR0_HIDE_IN_COMBAT_LOG | SPELL_ATTR0_HIDDEN_CLIENTSIDE | SPELL_ATTR0_DONT_AFFECT_SHEATH_STATE; + sinfo->Attributes &= ~(SPELL_ATTR0_LEVEL_DAMAGE_CALCULATION); + //sinfo->AttributesEx2 |= SPELL_ATTR2_CAN_TARGET_NOT_IN_LOS; + sinfo->AttributesEx3 |= SPELL_ATTR3_IGNORE_HIT_RESULT; + + sinfo->_effects[0].BasePoints = 300; + sinfo->_effects[0].DieSides = 0; + sinfo->_effects[0].BonusMultiplier = 0.f; + sinfo->_effects[0].DamageMultiplier = 0.75f; + sinfo->_effects[0].RealPointsPerLevel = 50.f; + //sinfo->_effects[0].ValueMultiplier = 1.f; + + sinfo->_effects[1].Effect = SPELL_EFFECT_SCHOOL_DAMAGE; + sinfo->_effects[1].BasePoints = 50; + sinfo->_effects[1].BonusMultiplier = 1.0f; + sinfo->_effects[1].DamageMultiplier = 0.5f; + sinfo->_effects[1].DieSides = /*17*/25; + sinfo->_effects[1].RealPointsPerLevel = 30.f; + //sinfo->_effects[1].ValueMultiplier = 1.f; + sinfo->_effects[1].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_TARGET_ENEMY); + sinfo->_effects[1].TargetB = SpellImplicitTargetInfo(TARGET_UNIT_DEST_AREA_ENEMY); + sinfo->_effects[1].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_12_YARDS); + //5) END SHADOW BLAST (SPLASH ATTACK) + + //6) SHADOW BOLT (BASE ATTACK) + spellId = SPELL_SHADOW_BOLT1; //16408 + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + + sinfo->PreventionType = SPELL_PREVENTION_TYPE_NONE; + sinfo->SpellLevel = 60; + sinfo->MaxLevel = 83; + sinfo->ManaCost = 0; + sinfo->ManaCostPercentage = 0; + sinfo->ManaCostPerlevel = 0; + sinfo->CastTimeEntry = nullptr; + sinfo->RangeEntry = sSpellRangeStore.LookupEntry(4); //30 yds + sinfo->SchoolMask = SPELL_SCHOOL_MASK_SHADOW | SPELL_SCHOOL_MASK_ARCANE; + //sinfo->Attributes |= SPELL_ATTR0_HIDE_IN_COMBAT_LOG | SPELL_ATTR0_HIDDEN_CLIENTSIDE | SPELL_ATTR0_DONT_AFFECT_SHEATH_STATE; + //sinfo->AttributesEx3 |= SPELL_ATTR3_DONT_DISPLAY_RANGE; + + sinfo->_effects[0].BasePoints = 200; + sinfo->_effects[0].DieSides = /*12*/25; + sinfo->_effects[0].BonusMultiplier = 1.15f; + sinfo->_effects[0].DamageMultiplier = 1.f; + sinfo->_effects[0].RealPointsPerLevel = 10.f; + //sinfo->_effects[0].ValueMultiplier = 1.f; + //6) END SHADOW BOLT (BASE ATTACK) + + //7) ATTACK ANIMATION + spellId = SPELL_ATTACK_MELEE_RANDOM; //42902 + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + + sinfo->Attributes &= ~(SPELL_ATTR0_CANT_USED_IN_COMBAT); + //7) END ATTACK ANIMATION + + //8) SPLASH ANIMATION + spellId = SHADOWFURY_VISUAL; //48582 + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + + sinfo->SpellLevel = 0; + sinfo->MaxLevel = 0; + sinfo->ManaCost = 0; + sinfo->ManaCostPercentage = 0; + sinfo->ManaCostPerlevel = 0; + sinfo->RangeEntry = sSpellRangeStore.LookupEntry(6); //100 yds + sinfo->ExplicitTargetMask = TARGET_FLAG_DEST_LOCATION; + sinfo->MaxAffectedTargets = 1; + sinfo->Stances = 0; + sinfo->Speed = 0.f; + sinfo->Attributes |= SPELL_ATTR0_CASTABLE_WHILE_DEAD | SPELL_ATTR0_UNAFFECTED_BY_INVULNERABILITY; + sinfo->AttributesEx |= SPELL_ATTR1_UNAFFECTED_BY_SCHOOL_IMMUNE | SPELL_ATTR1_NO_THREAT; + sinfo->AttributesEx2 |= SPELL_ATTR2_CAN_TARGET_DEAD | SPELL_ATTR2_CAN_TARGET_NOT_IN_LOS; + sinfo->AttributesEx3 |= SPELL_ATTR3_IGNORE_HIT_RESULT; + sinfo->AttributesEx5 |= SPELL_ATTR5_USABLE_WHILE_STUNNED | SPELL_ATTR5_USABLE_WHILE_CONFUSED | SPELL_ATTR5_USABLE_WHILE_FEARED; + + sinfo->_effects[0].Effect = SPELL_EFFECT_DUMMY; + sinfo->_effects[0].BasePoints = 1; + sinfo->_effects[0].RealPointsPerLevel = 0.f; + sinfo->_effects[0].ValueMultiplier = 0.f; + sinfo->_effects[0].RealPointsPerLevel = 0.f; + sinfo->_effects[0].DamageMultiplier = 0.f; + sinfo->_effects[0].TargetB = SpellImplicitTargetInfo(0); + sinfo->_effects[0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_0_YARDS); + sinfo->_effects[0].TargetA = SpellImplicitTargetInfo(TARGET_DEST_DEST); + //8) END SPLASH ANIMATION + + //9) DEVOUR MAGIC + spellId = SPELL_DEVOUR_MAGIC; //17012 + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + + sinfo->InterruptFlags = 0xF; + sinfo->SpellLevel = 0; + sinfo->MaxLevel = 0; + sinfo->MaxTargetLevel = 0; + sinfo->RecoveryTime = 7000; + sinfo->PowerType = POWER_MANA; + sinfo->ManaCost = 0; + sinfo->ManaCostPercentage = 0; + sinfo->ManaCostPerlevel = 0; + sinfo->CastTimeEntry = sSpellCastTimesStore.LookupEntry(4); //1000ms + sinfo->RangeEntry = sSpellRangeStore.LookupEntry(5); //40 yds + sinfo->SchoolMask = SPELL_SCHOOL_MASK_SHADOW | SPELL_SCHOOL_MASK_ARCANE; + sinfo->DmgClass = SPELL_DAMAGE_CLASS_MAGIC; + sinfo->ExplicitTargetMask = TARGET_FLAG_DEST_LOCATION; + //sinfo->MaxAffectedTargets = 100; + sinfo->Attributes |= SPELL_ATTR0_UNAFFECTED_BY_INVULNERABILITY; + sinfo->AttributesEx |= SPELL_ATTR1_NO_THREAT; + //sinfo->Attributes &= ~(SPELL_ATTR0_HIDE_IN_COMBAT_LOG); + //sinfo->AttributesEx3 |= SPELL_ATTR3_DONT_DISPLAY_RANGE; + + sinfo->_effects[0].Effect = SPELL_EFFECT_DISPEL; + sinfo->_effects[0].BasePoints = 2; + sinfo->_effects[0].MiscValue = DISPEL_MAGIC; + sinfo->_effects[0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_DEST_AREA_ALLY); + sinfo->_effects[0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_20_YARDS); + + sinfo->_effects[1].Effect = SPELL_EFFECT_DISPEL; + sinfo->_effects[1].BasePoints = 2; + sinfo->_effects[1].MiscValue = DISPEL_CURSE; + sinfo->_effects[1].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_DEST_AREA_ALLY); + sinfo->_effects[1].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_20_YARDS); + + sinfo->_effects[2].Effect = SPELL_EFFECT_DISPEL; + sinfo->_effects[2].BasePoints = 2; + sinfo->_effects[2].MiscValue = DISPEL_MAGIC; + sinfo->_effects[2].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_DEST_AREA_ENEMY); + sinfo->_effects[2].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_20_YARDS); + //9) END DEVOUR MAGIC + + //10) DRAIN MANA + spellId = SPELL_DRAIN_MANA; //25755 + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + + sinfo->SpellLevel = 0; + sinfo->MaxLevel = 0; + sinfo->MaxTargetLevel = 0; + sinfo->RecoveryTime = 0;//60000; + //sinfo->PowerType = POWER_MANA; + //sinfo->ManaCost = 0; + //sinfo->ManaCostPercentage = 0; + //sinfo->ManaCostPerlevel = 0; + sinfo->Speed = 0.f; + sinfo->CastTimeEntry = sSpellCastTimesStore.LookupEntry(4); //1000ms + sinfo->RangeEntry = sSpellRangeStore.LookupEntry(5); //40 yds + sinfo->SchoolMask = SPELL_SCHOOL_MASK_SHADOW | SPELL_SCHOOL_MASK_ARCANE; + //sinfo->DmgClass = SPELL_DAMAGE_CLASS_MAGIC; + sinfo->ExplicitTargetMask = TARGET_FLAG_UNIT; + sinfo->Attributes |= SPELL_ATTR0_ABILITY | SPELL_ATTR0_DONT_AFFECT_SHEATH_STATE; + sinfo->AttributesEx3 |= SPELL_ATTR3_IGNORE_HIT_RESULT | SPELL_ATTR3_NO_DONE_BONUS; + + //sinfo->_effects[0].Effect = SPELL_EFFECT_POWER_DRAIN; + sinfo->_effects[0].BasePoints = 999999; + sinfo->_effects[0].RealPointsPerLevel = 0.f; + sinfo->_effects[0].ValueMultiplier = 1.f; + sinfo->_effects[0].RealPointsPerLevel = 0.f; + sinfo->_effects[0].DamageMultiplier = 1.f; + sinfo->_effects[0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_TARGET_ANY); + + sinfo->_effects[1].Effect = SPELL_EFFECT_NONE; + //10) END DRAIN MANA + + //11) REPLENISH MANA + spellId = SPELL_REPLENISH_MANA; //33394 + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + + sinfo->SpellFamilyName = SPELLFAMILY_WARLOCK; + sinfo->SpellLevel = 0; + sinfo->RecoveryTime = 3000; + sinfo->CategoryRecoveryTime = 0; + sinfo->CategoryEntry = nullptr; + sinfo->PowerType = POWER_MANA; + sinfo->CastTimeEntry = nullptr;//sSpellCastTimesStore.LookupEntry(2); //250ms + sinfo->SchoolMask = SPELL_SCHOOL_MASK_SHADOW | SPELL_SCHOOL_MASK_ARCANE; + sinfo->ExplicitTargetMask = TARGET_FLAG_UNIT; + sinfo->MaxAffectedTargets = 100; + sinfo->Attributes |= SPELL_ATTR0_ABILITY | SPELL_ATTR0_DONT_AFFECT_SHEATH_STATE | SPELL_ATTR0_IS_REPLENISHMENT | SPELL_ATTR0_HIDE_IN_COMBAT_LOG; + sinfo->AttributesEx |= SPELL_ATTR1_DRAIN_ALL_POWER/* | SPELL_ATTR1_CANT_TARGET_SELF*/; + sinfo->AttributesEx2 |= SPELL_ATTR2_CAN_TARGET_NOT_IN_LOS; + sinfo->AttributesEx3 |= SPELL_ATTR3_NO_DONE_BONUS; + sinfo->AttributesEx4 |= SPELL_ATTR4_NOT_CHECK_SELFCAST_POWER; + sinfo->AttributesEx5 |= SPELL_ATTR5_DONT_TURN_DURING_CAST; + sinfo->AttributesEx6 |= SPELL_ATTR6_CAN_TARGET_POSSESSED_FRIENDS; + + sinfo->_effects[0].Effect = SPELL_EFFECT_TRIGGER_SPELL; + sinfo->_effects[0].BasePoints = 3; + sinfo->_effects[0].DieSides = 0; + sinfo->_effects[0].RealPointsPerLevel = 0.f; + sinfo->_effects[0].ValueMultiplier = 0.f; + sinfo->_effects[0].RealPointsPerLevel = 0.f; + sinfo->_effects[0].DamageMultiplier = 0.f; + sinfo->_effects[0].TriggerSpell = SPELL_TRIGGERED_ENERGIZE; + sinfo->_effects[0].TargetA = SpellImplicitTargetInfo(TARGET_SRC_CASTER); + sinfo->_effects[0].TargetB = SpellImplicitTargetInfo(TARGET_UNIT_SRC_AREA_ALLY); + sinfo->_effects[0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_25_YARDS); + //11) END REPLENISH MANA + + //12) REPLENISH HEALTH + spellId = SPELL_REPLENISH_HEALTH; //34756 + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + + sinfo->SpellFamilyName = SPELLFAMILY_WARLOCK; + sinfo->SpellLevel = 0; + sinfo->RecoveryTime = 3000; + sinfo->CategoryEntry = nullptr; + sinfo->PowerType = POWER_MANA; + sinfo->CastTimeEntry = nullptr;//sSpellCastTimesStore.LookupEntry(2); //250ms + sinfo->SchoolMask = SPELL_SCHOOL_MASK_SHADOW | SPELL_SCHOOL_MASK_ARCANE; + sinfo->ExplicitTargetMask = TARGET_FLAG_UNIT; + sinfo->MaxAffectedTargets = 100; + sinfo->Attributes |= SPELL_ATTR0_ABILITY | SPELL_ATTR0_DONT_AFFECT_SHEATH_STATE | SPELL_ATTR0_IS_REPLENISHMENT | SPELL_ATTR0_HIDE_IN_COMBAT_LOG; + sinfo->AttributesEx |= SPELL_ATTR1_DRAIN_ALL_POWER/* | SPELL_ATTR1_CANT_TARGET_SELF*/; + sinfo->AttributesEx &= ~(SPELL_ATTR1_CANT_TARGET_SELF); + sinfo->AttributesEx2 |= SPELL_ATTR2_CAN_TARGET_NOT_IN_LOS; + sinfo->AttributesEx3 |= SPELL_ATTR3_NO_DONE_BONUS; + sinfo->AttributesEx4 |= SPELL_ATTR4_NOT_CHECK_SELFCAST_POWER; + sinfo->AttributesEx5 |= SPELL_ATTR5_DONT_TURN_DURING_CAST; + sinfo->AttributesEx6 |= SPELL_ATTR6_CAN_TARGET_POSSESSED_FRIENDS; + + sinfo->_effects[0].Effect = SPELL_EFFECT_TRIGGER_SPELL; + sinfo->_effects[0].BasePoints = 3; + sinfo->_effects[0].DieSides = 0; + sinfo->_effects[0].RealPointsPerLevel = 0.f; + sinfo->_effects[0].ValueMultiplier = 0.f; + sinfo->_effects[0].RealPointsPerLevel = 0.f; + sinfo->_effects[0].DamageMultiplier = 0.f; + sinfo->_effects[0].TriggerSpell = SPELL_TRIGGERED_HEAL; + sinfo->_effects[0].TargetA = SpellImplicitTargetInfo(TARGET_SRC_CASTER); + sinfo->_effects[0].TargetB = SpellImplicitTargetInfo(TARGET_UNIT_SRC_AREA_ALLY); + sinfo->_effects[0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_25_YARDS); + //12) END REPLENISH HEALTH + + //ARCHMAGE + //13) BRILLIANCE AURA + spellId = SPELL_BRILLIANCE_AURA; //1234 + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + + sinfo->SpellLevel = 0; + sinfo->MaxLevel = 0; + sinfo->MaxTargetLevel = 0; + sinfo->RangeEntry = sSpellRangeStore.LookupEntry(1); //0 yds + sinfo->ExplicitTargetMask = TARGET_FLAG_UNIT; + sinfo->Attributes |= SPELL_ATTR0_ABILITY | SPELL_ATTR0_PASSIVE; + sinfo->AttributesEx4 |= SPELL_ATTR4_DONT_REMOVE_IN_ARENA; + sinfo->AttributesEx7 |= SPELL_ATTR7_CONSOLIDATED_RAID_BUFF; + + sinfo->_effects[0].Effect = SPELL_EFFECT_APPLY_AREA_AURA_RAID; + sinfo->_effects[0].ApplyAuraName = SPELL_AURA_MOD_POWER_REGEN_PERCENT; + sinfo->_effects[0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_CASTER); + sinfo->_effects[0].BasePoints = 100; + sinfo->_effects[0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_40_YARDS); + + sinfo->_effects[1].Effect = SPELL_EFFECT_APPLY_AREA_AURA_RAID; + sinfo->_effects[1].ApplyAuraName = SPELL_AURA_MOD_INCREASE_ENERGY_PERCENT; + sinfo->_effects[1].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_CASTER); + sinfo->_effects[1].BasePoints = 10; + sinfo->_effects[1].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_40_YARDS); + + //for stacking rule + /* + sinfo->_effects[2].Effect = SPELL_EFFECT_APPLY_AURA; + sinfo->_effects[2].ApplyAuraName = SPELL_AURA_DUMMY; + sinfo->_effects[2].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_CASTER); + sinfo->_effects[2].BasePoints = 1; + sinfo->_effects[2].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_0_YARDS); + */ + //13) END BRILLIANCE AURA + + //14) FIREBALL (MAIN_ATTACK) + //TODO: balance + spellId = SPELL_FIREBALL; //9488 + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + + sinfo->SpellLevel = 20; + sinfo->BaseLevel = 20; + sinfo->MaxLevel = 81; + sinfo->ManaCost = 0; + sinfo->ManaCostPercentage = 0; + sinfo->ManaCostPerlevel = 0; + sinfo->CastTimeEntry = nullptr; + sinfo->RangeEntry = sSpellRangeStore.LookupEntry(4); //30 yds + sinfo->SchoolMask = SPELL_SCHOOL_MASK_FIRE | SPELL_SCHOOL_MASK_ARCANE; + //sinfo->ExplicitTargetMask = TARGET_FLAG_UNIT; + //sinfo->MaxAffectedTargets = 1000; + sinfo->Attributes |= SPELL_ATTR0_DONT_AFFECT_SHEATH_STATE | SPELL_ATTR0_ABILITY | SPELL_ATTR0_CASTABLE_WHILE_MOUNTED; + sinfo->Attributes &= ~(SPELL_ATTR0_LEVEL_DAMAGE_CALCULATION); + sinfo->AttributesEx3 |= SPELL_ATTR3_IGNORE_HIT_RESULT; + + sinfo->_effects[0].BasePoints = 15; + sinfo->_effects[0].DieSides = 9; + sinfo->_effects[0].BonusMultiplier = 0.5f; + sinfo->_effects[0].DamageMultiplier = 1.f; + sinfo->_effects[0].RealPointsPerLevel = 15.f; + sinfo->_effects[0].ValueMultiplier = 1.f; + //14) END FIREBALL (MAIN ATTACK) + + //15) BLIZZARD + //TODO: balance + spellId = SPELL_BLIZZARD; //15783 + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + + sinfo->SpellFamilyName = SPELLFAMILY_MAGE; + sinfo->SpellLevel = 20; + sinfo->BaseLevel = 20; + sinfo->MaxLevel = 0; + sinfo->ManaCost = 75 * 5; + sinfo->ManaCostPercentage = 0; + sinfo->ManaCostPerlevel = 0; + sinfo->CastTimeEntry = nullptr; + sinfo->RecoveryTime = 6000; + sinfo->RangeEntry = sSpellRangeStore.LookupEntry(4); //30 yds + sinfo->SchoolMask = SPELL_SCHOOL_MASK_FROST | SPELL_SCHOOL_MASK_ARCANE; + ///sinfo->ExplicitTargetMask = TARGET_FLAG_DEST_LOCATION; + //sinfo->MaxAffectedTargets = 1000; + sinfo->Attributes |= SPELL_ATTR0_DONT_AFFECT_SHEATH_STATE | SPELL_ATTR0_ABILITY | SPELL_ATTR0_CASTABLE_WHILE_MOUNTED; + sinfo->AttributesEx2 |= SPELL_ATTR2_CAN_TARGET_NOT_IN_LOS | SPELL_ATTR2_UNK22; + sinfo->AttributesEx3 |= SPELL_ATTR3_IGNORE_HIT_RESULT; + sinfo->AttributesEx5 |= SPELL_ATTR5_HASTE_AFFECT_DURATION; + + sinfo->_effects[0].BasePoints = 26; + sinfo->_effects[0].DieSides = 0; + sinfo->_effects[0].BonusMultiplier = 1.f; + sinfo->_effects[0].DamageMultiplier = 1.f; + sinfo->_effects[0].RealPointsPerLevel = 15.f; + sinfo->_effects[0].ValueMultiplier = 1.f; + sinfo->_effects[0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_13_YARDS); + sinfo->_effects[0].Amplitude = 1000; + //15) END BLIZZARD + + //16) SUMMON WATER ELEMENTAL (dummy spell) + spellId = SPELL_SUMMON_WATER_ELEMENTAL; //35593 + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + + sinfo->SpellFamilyName = SPELLFAMILY_MAGE; + sinfo->SpellLevel = 20; + sinfo->BaseLevel = 20; + sinfo->MaxLevel = 0; + sinfo->RecoveryTime = 20000; + sinfo->PowerType = POWER_MANA; + sinfo->ManaCost = 125 * 5; + sinfo->ManaCostPercentage = 0; + sinfo->ManaCostPerlevel = 0; + sinfo->CastTimeEntry = sSpellCastTimesStore.LookupEntry(3); //500ms + sinfo->SchoolMask = SPELL_SCHOOL_MASK_FROST | SPELL_SCHOOL_MASK_ARCANE; + sinfo->ExplicitTargetMask = TARGET_FLAG_UNIT; + + sinfo->_effects[0].Effect = SPELL_EFFECT_DUMMY; + //sinfo->_effects[0].BasePoints = 1; + sinfo->_effects[0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_CASTER); + sinfo->_effects[0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_0_YARDS); + //16) END SUMMON WATER ELEMENTAL + + //17) WATERBOLT (MAIN_ATTACK) + //TODO: balance, we only have 1 of 3 possible elementals so boost damage + spellId = SPELL_WATERBOLT; //72898 + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + + sinfo->SpellFamilyName = SPELLFAMILY_GENERIC; + sinfo->SpellLevel = 20; + sinfo->BaseLevel = 20; + sinfo->MaxTargetLevel = 0; + sinfo->ManaCost = 0; + sinfo->ManaCostPercentage = 0; + sinfo->ManaCostPerlevel = 0; + sinfo->CastTimeEntry = sSpellCastTimesStore.LookupEntry(5); //2000ms + sinfo->RangeEntry = sSpellRangeStore.LookupEntry(4); //30 yds + sinfo->SchoolMask = SPELL_SCHOOL_MASK_FROST | SPELL_SCHOOL_MASK_ARCANE; + sinfo->AttributesEx3 |= SPELL_ATTR3_IGNORE_HIT_RESULT; + + sinfo->_effects[0].BasePoints = 25; + sinfo->_effects[0].DieSides = 20; + sinfo->_effects[0].BonusMultiplier = 1.f; + sinfo->_effects[0].DamageMultiplier = 1.f; + sinfo->_effects[0].RealPointsPerLevel = 25.f; + sinfo->_effects[0].ValueMultiplier = 1.f; + //17) END WATERBOLT (MAIN ATTACK) + + //DREADLORD + //18) VAMPIRIC AURA + spellId = SPELL_VAMPIRIC_AURA; //20810 + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + + sinfo->ProcFlags = PROC_FLAG_DONE_MELEE_AUTO_ATTACK | PROC_FLAG_DONE_SPELL_MELEE_DMG_CLASS; + sinfo->SchoolMask = SPELL_SCHOOL_MASK_SHADOW | SPELL_SCHOOL_MASK_ARCANE; + sinfo->PreventionType = SPELL_PREVENTION_TYPE_NONE; + sinfo->DmgClass = SPELL_DAMAGE_CLASS_NONE; + sinfo->SpellLevel = 0; + sinfo->BaseLevel = 0; + sinfo->MaxTargetLevel = 0; + sinfo->RangeEntry = sSpellRangeStore.LookupEntry(1); //0 yds + sinfo->ExplicitTargetMask = TARGET_FLAG_UNIT; + sinfo->Attributes |= SPELL_ATTR0_ABILITY | SPELL_ATTR0_PASSIVE; + sinfo->AttributesEx3 |= SPELL_ATTR3_CAN_PROC_WITH_TRIGGERED; + sinfo->AttributesEx4 |= SPELL_ATTR4_DONT_REMOVE_IN_ARENA; + sinfo->AttributesEx7 |= SPELL_ATTR7_CONSOLIDATED_RAID_BUFF; + + sinfo->_effects[0].Effect = SPELL_EFFECT_APPLY_AREA_AURA_RAID; + sinfo->_effects[0].ApplyAuraName = SPELL_AURA_MOD_CRIT_DAMAGE_BONUS; + sinfo->_effects[0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_CASTER); + sinfo->_effects[0].BasePoints = 5; + sinfo->_effects[0].MiscValue = SPELL_SCHOOL_MASK_NORMAL; + sinfo->_effects[0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_40_YARDS); + + sinfo->_effects[1].Effect = SPELL_EFFECT_APPLY_AREA_AURA_RAID; + sinfo->_effects[1].ApplyAuraName = SPELL_AURA_PROC_TRIGGER_SPELL; + sinfo->_effects[1].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_CASTER); + sinfo->_effects[1].BasePoints = 1; + sinfo->_effects[1].TriggerSpell = SPELL_TRIGGERED_HEAL; + sinfo->_effects[1].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_40_YARDS); + + //for stacking rule + /* + sinfo->_effects[2].Effect = SPELL_EFFECT_APPLY_AURA; + sinfo->_effects[2].ApplyAuraName = SPELL_AURA_DUMMY; + sinfo->_effects[2].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_CASTER); + sinfo->_effects[2].BasePoints = 1; + sinfo->_effects[2].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_0_YARDS); + */ + //18) END VAMPIRIC AURA + + //19) VAMPIRIC HEAL + spellId = SPELL_TRIGGERED_HEAL; //25155 + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + + sinfo->PreventionType = SPELL_PREVENTION_TYPE_NONE; + sinfo->DmgClass = SPELL_DAMAGE_CLASS_NONE; + sinfo->SchoolMask = SPELL_SCHOOL_MASK_SHADOW | SPELL_SCHOOL_MASK_ARCANE; + sinfo->Attributes &= ~(SPELL_ATTR0_NOT_SHAPESHIFT); + sinfo->AttributesEx |= SPELL_ATTR1_CANT_BE_REFLECTED | SPELL_ATTR1_CANT_BE_REDIRECTED | SPELL_ATTR1_NO_THREAT; + sinfo->AttributesEx2 |= SPELL_ATTR2_CANT_CRIT; + sinfo->AttributesEx3 |= SPELL_ATTR3_IGNORE_HIT_RESULT | SPELL_ATTR3_DISABLE_PROC | SPELL_ATTR3_CAN_PROC_WITH_TRIGGERED | SPELL_ATTR3_NO_DONE_BONUS; + + sinfo->_effects[0].BasePoints = 1; + + sinfo->_effects[1].Effect = SPELL_EFFECT_NONE; + //19) END VAMPIRIC HEAL + + //20) SLEEP + spellId = SPELL_SLEEP; //20663 + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + + sinfo->SpellFamilyName = SPELLFAMILY_WARLOCK; + sinfo->SchoolMask = SPELL_SCHOOL_MASK_SHADOW | SPELL_SCHOOL_MASK_ARCANE; + sinfo->InterruptFlags = 0xF; + sinfo->SpellLevel = 0; + sinfo->BaseLevel = 0; + sinfo->MaxTargetLevel = 0; + sinfo->Dispel = DISPEL_MAGIC; + sinfo->Mechanic = MECHANIC_SLEEP; + sinfo->RangeEntry = sSpellRangeStore.LookupEntry(4); //30 yds + sinfo->CastTimeEntry = sSpellCastTimesStore.LookupEntry(3); //500ms + sinfo->RecoveryTime = 6000; + //sinfo->StartRecoveryCategory = 133; + //sinfo->StartRecoveryTime = 1000; + sinfo->DurationEntry = sSpellDurationStore.LookupEntry(3); //60000ms + sinfo->ManaCost = 50 * 5; + sinfo->ManaCostPercentage = 0; + sinfo->ManaCostPerlevel = 0; + sinfo->AuraInterruptFlags = AURA_INTERRUPT_FLAG_DIRECT_DAMAGE; + sinfo->ExplicitTargetMask = TARGET_FLAG_UNIT; + sinfo->Attributes &= ~(SPELL_ATTR0_NOT_SHAPESHIFT | SPELL_ATTR0_HEARTBEAT_RESIST_CHECK); + sinfo->AttributesEx |= SPELL_ATTR1_CANT_BE_REDIRECTED | SPELL_ATTR1_CANT_BE_REFLECTED; + sinfo->AttributesEx3 |= SPELL_ATTR3_IGNORE_HIT_RESULT; + + //sinfo->_effects[0].Effect = SPELL_EFFECT_APPLY_AURA; + //sinfo->_effects[0].ApplyAuraName = SPELL_AURA_MOD_STUN; + //sinfo->_effects[0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_TARGET_ENEMY); + //sinfo->_effects[0].BasePoints = 1; + + sinfo->_effects[1].Effect = SPELL_EFFECT_APPLY_AURA; + sinfo->_effects[1].ApplyAuraName = SPELL_AURA_MOD_RESISTANCE_PCT; + sinfo->_effects[1].MiscValue = SPELL_SCHOOL_MASK_NORMAL; + sinfo->_effects[1].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_TARGET_ENEMY); + sinfo->_effects[1].BasePoints = -100; + //20) END SLEEP + + //21) CARRION SWARM + //TODO: balance + spellId = SPELL_CARRION_SWARM; //34240 + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + + sinfo->SpellFamilyName = SPELLFAMILY_WARLOCK; + sinfo->DmgClass = SPELL_DAMAGE_CLASS_MAGIC; + sinfo->SchoolMask = SPELL_SCHOOL_MASK_SHADOW | SPELL_SCHOOL_MASK_ARCANE; + sinfo->PreventionType = SPELL_PREVENTION_TYPE_NONE; + sinfo->SpellLevel = 40; + sinfo->BaseLevel = 40; + sinfo->MaxTargetLevel = 0; + sinfo->RangeEntry = sSpellRangeStore.LookupEntry(4); //30 yds + sinfo->RecoveryTime = 10000; + sinfo->StartRecoveryCategory = 133; + sinfo->StartRecoveryTime = 1500; + sinfo->ManaCost = 110 * 5; + //sinfo->MaxAffectedTargets = 1000; + //sinfo->ExplicitTargetMask = TARGET_FLAG_UNIT; + sinfo->Attributes &= ~(SPELL_ATTR0_UNK11); + sinfo->AttributesEx |= SPELL_ATTR1_CANT_BE_REDIRECTED | SPELL_ATTR1_CANT_BE_REFLECTED; + sinfo->AttributesEx2 |= SPELL_ATTR2_CANT_CRIT/* | SPELL_ATTR2_CAN_TARGET_NOT_IN_LOS*/; + sinfo->AttributesEx3 |= SPELL_ATTR3_IGNORE_HIT_RESULT; + + //sinfo->_effects[0].Effect = SPELL_EFFECT_SCHOOL_DAMAGE; + sinfo->_effects[0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_CONE_ENEMY_104); + sinfo->_effects[0].BasePoints = 425; + sinfo->_effects[0].DieSides = 150; + sinfo->_effects[0].BonusMultiplier = 2.f; + sinfo->_effects[0].DamageMultiplier = 1.f; + sinfo->_effects[0].RealPointsPerLevel = 37.5f; //2000 avg at 80 + sinfo->_effects[0].ValueMultiplier = 1.f; + sinfo->_effects[0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_40_YARDS); + //21) END CARRION SWARM + + //22) INFERNO (dummy summon) + spellId = SPELL_INFERNO; //12740 + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + + sinfo->SpellFamilyName = SPELLFAMILY_WARLOCK; + sinfo->SpellLevel = 60; + sinfo->BaseLevel = 60; + sinfo->RangeEntry = sSpellRangeStore.LookupEntry(4); //30 yds + sinfo->CastTimeEntry = sSpellCastTimesStore.LookupEntry(3); //500ms + sinfo->RecoveryTime = 180000; + sinfo->StartRecoveryCategory = 133; + sinfo->StartRecoveryTime = 1500; + sinfo->ManaCost = 175 * 5; + sinfo->ExplicitTargetMask = TARGET_FLAG_DEST_LOCATION; + sinfo->Attributes &= ~(SPELL_ATTR0_ABILITY); + sinfo->AttributesEx |= /*SPELL_ATTR1_UNAFFECTED_BY_SCHOOL_IMMUNE | */SPELL_ATTR1_NO_THREAT; + //sinfo->AttributesEx3 |= SPELL_ATTR3_IGNORE_HIT_RESULT; + + sinfo->_effects[0].Effect = SPELL_EFFECT_DUMMY; + sinfo->_effects[0].TargetA = SpellImplicitTargetInfo(TARGET_DEST_DEST); + sinfo->_effects[0].BasePoints = 1; + //22) END INFERNO + + //23) INFERNO VISUAL (dummy summon) + spellId = SPELL_INFERNO_METEOR_VISUAL; //5739 + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + + sinfo->ExplicitTargetMask = TARGET_FLAG_DEST_LOCATION; + + //sinfo->_effects[0].Effect = SPELL_EFFECT_DUMMY; + sinfo->_effects[0].TargetA = SpellImplicitTargetInfo(TARGET_DEST_DEST); + //23) END INFERNO VISUAL + + //SPELL BREAKER + //24) STEAL MAGIC + spellId = SPELL_STEAL_MAGIC; //30036 + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + + sinfo->SpellFamilyName = SPELLFAMILY_PALADIN; + sinfo->RangeEntry = sSpellRangeStore.LookupEntry(34); //25 yds + sinfo->RecoveryTime = 2000; + sinfo->ManaCost = 75 * 5; + sinfo->ManaCostPercentage = 0; + sinfo->ManaCostPerlevel = 0; + sinfo->ExplicitTargetMask = TARGET_FLAG_UNIT; + sinfo->AttributesEx |= SPELL_ATTR1_CANT_BE_REDIRECTED | SPELL_ATTR1_CANT_BE_REFLECTED; + sinfo->AttributesEx3 |= SPELL_ATTR3_IGNORE_HIT_RESULT; + sinfo->AttributesEx6 |= SPELL_ATTR6_CAN_TARGET_POSSESSED_FRIENDS; + + sinfo->_effects[0].Effect = SPELL_EFFECT_DUMMY; + sinfo->_effects[0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_TARGET_ANY); + + sinfo->_effects[1].Effect = SPELL_EFFECT_NONE; + //24) END STEAL MAGIC + + //24.1) STEAL MAGIC VISUAL + spellId = SPELL_STEAL_MAGIC_VISUAL; //11084 + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + + sinfo->SpellLevel = 1; + sinfo->BaseLevel = 1; + sinfo->RangeEntry = sSpellRangeStore.LookupEntry(6); //100 yds + sinfo->RecoveryTime = 0; + sinfo->ExplicitTargetMask = TARGET_FLAG_UNIT; + sinfo->AttributesEx |= SPELL_ATTR1_CANT_BE_REDIRECTED | SPELL_ATTR1_CANT_BE_REFLECTED; + sinfo->AttributesEx3 |= SPELL_ATTR3_IGNORE_HIT_RESULT; + + sinfo->_effects[0].Effect = SPELL_EFFECT_DUMMY; + sinfo->_effects[0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_TARGET_ANY); + sinfo->_effects[0].BasePoints = 1; + sinfo->_effects[0].DieSides = 0; + sinfo->_effects[0].RealPointsPerLevel = 0.f; + sinfo->_effects[0].BonusMultiplier = 0.f; + //24.1) END STEAL MAGIC VISUAL + + //25) FEEDBACK + spellId = SPELL_FEEDBACK; //32897 + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + + sinfo->SpellFamilyName = SPELLFAMILY_PALADIN; + sinfo->SchoolMask = SPELL_SCHOOL_MASK_ARCANE; + sinfo->PreventionType = SPELL_PREVENTION_TYPE_NONE; + sinfo->SpellLevel = 0; + sinfo->BaseLevel = 0; + sinfo->RangeEntry = sSpellRangeStore.LookupEntry(13); //50000 yds + sinfo->ManaCost = 0; + sinfo->ManaCostPercentage = 0; + sinfo->ManaCostPerlevel = 0; + sinfo->ExplicitTargetMask = TARGET_FLAG_UNIT; + sinfo->Attributes &= ~(SPELL_ATTR0_NOT_SHAPESHIFT); + sinfo->AttributesEx |= SPELL_ATTR1_CANT_BE_REDIRECTED | SPELL_ATTR1_CANT_BE_REFLECTED | SPELL_ATTR1_UNAFFECTED_BY_SCHOOL_IMMUNE; + sinfo->AttributesEx2 |= SPELL_ATTR2_CANT_CRIT; + sinfo->AttributesEx3 |= SPELL_ATTR3_IGNORE_HIT_RESULT; + + sinfo->_effects[0].Effect = SPELL_EFFECT_POWER_BURN; + sinfo->_effects[0].DieSides = 0; + sinfo->_effects[0].ValueMultiplier = 1.f; + //25) END FEEDBACK + + // DARK RANGER + //26) BLACK ARROW + //TODO: balance + spellId = SPELL_BLACK_ARROW; //20733 + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + + sinfo->SpellFamilyName = SPELLFAMILY_WARLOCK; + //sinfo->SpellFamilyFlags[0] = 0x0; + sinfo->SpellFamilyFlags[1] = 0x4; //custom, not present in db + //sinfo->SpellFamilyFlags[2] = 0x0; + sinfo->DmgClass = SPELL_DAMAGE_CLASS_RANGED; + sinfo->SchoolMask = SPELL_SCHOOL_MASK_SHADOW | SPELL_SCHOOL_MASK_ARCANE; + sinfo->PreventionType = SPELL_PREVENTION_TYPE_PACIFY; + sinfo->Dispel = DISPEL_NONE; + sinfo->Mechanic = MECHANIC_NONE; + sinfo->SpellLevel = 40; + sinfo->BaseLevel = 40; + sinfo->MaxTargetLevel = 0; + sinfo->CastTimeEntry = sSpellCastTimesStore.LookupEntry(3); //500ms + sinfo->RangeEntry = sSpellRangeStore.LookupEntry(4); //5-30 yds + sinfo->DurationEntry = sSpellDurationStore.LookupEntry(85); //18 sec + sinfo->RecoveryTime = 3000; + sinfo->StartRecoveryCategory = 133; + sinfo->StartRecoveryTime = 1500; + sinfo->ManaCost = 6 * 5 * 2; //need to increase cost since ability is not autocast, has cd and deals more damage + sinfo->MaxAffectedTargets = 1; + sinfo->AuraInterruptFlags = AURA_INTERRUPT_FLAG_CHANGE_MAP; + //sinfo->ExplicitTargetMask = TARGET_FLAG_UNIT; + sinfo->Attributes |= SPELL_ATTR0_IMPOSSIBLE_DODGE_PARRY_BLOCK | SPELL_ATTR0_NEGATIVE_1; + sinfo->AttributesEx |= SPELL_ATTR1_CANT_BE_REDIRECTED | SPELL_ATTR1_CANT_BE_REFLECTED; + sinfo->AttributesEx2 |= SPELL_ATTR2_NOT_RESET_AUTO_ACTIONS/* | SPELL_ATTR2_CANT_CRIT*/; + sinfo->AttributesEx4 |= SPELL_ATTR4_IGNORE_RESISTANCES; + + sinfo->_effects[1].Effect = SPELL_EFFECT_WEAPON_PERCENT_DAMAGE; + sinfo->_effects[1].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_TARGET_ENEMY); + sinfo->_effects[1].ApplyAuraName = SPELL_AURA_NONE; + sinfo->_effects[1].BasePoints = 150; + sinfo->_effects[1].DieSides = 0; + sinfo->_effects[1].BonusMultiplier = 1.f; + sinfo->_effects[1].DamageMultiplier = 1.f; + sinfo->_effects[1].RealPointsPerLevel = 0.f; + sinfo->_effects[1].ValueMultiplier = 1.f; + sinfo->_effects[1].Amplitude = 0; + sinfo->_effects[1].RadiusEntry = nullptr; + + //sinfo->_effects[0].Effect = SPELL_EFFECT_APPLY_AURA; + //sinfo->_effects[0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_TARGET_ENEMY); + //sinfo->_effects[0].ApplyAuraName = SPELL_AURA_PERIODIC_DAMAGE; + sinfo->_effects[0].BasePoints = 100; + //sinfo->_effects[0].DieSides = 0; + sinfo->_effects[0].BonusMultiplier = 1.5f; + sinfo->_effects[0].DamageMultiplier = 1.f; + sinfo->_effects[0].RealPointsPerLevel = 10.f; + //sinfo->_effects[0].ValueMultiplier = 1.f; + //sinfo->_effects[0].RadiusEntry = nullptr; + sinfo->_effects[0].Amplitude = 2000; + //26) END BLACK ARROW + + //27) DRAIN LIFE + //TODO: balance + spellId = SPELL_DRAIN_LIFE; //17238 + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + + sinfo->SpellFamilyName = SPELLFAMILY_WARLOCK; + sinfo->DmgClass = SPELL_DAMAGE_CLASS_MAGIC; + sinfo->SchoolMask = SPELL_SCHOOL_MASK_SHADOW; + sinfo->PreventionType = SPELL_PREVENTION_TYPE_SILENCE; + sinfo->Dispel = DISPEL_NONE; + sinfo->Mechanic = MECHANIC_NONE; + sinfo->SpellLevel = 40; + sinfo->BaseLevel = 40; + sinfo->MaxTargetLevel = 0; + sinfo->CastTimeEntry = nullptr; + sinfo->RangeEntry = sSpellRangeStore.LookupEntry(4); //30 yds + //sinfo->DurationEntry = sSpellDurationStore.LookupEntry(85); //18 sec + sinfo->RecoveryTime = 5000; + sinfo->StartRecoveryCategory = 133; + sinfo->StartRecoveryTime = 1500; + sinfo->ManaCost = 75 * 5; + sinfo->MaxAffectedTargets = 1; + sinfo->AuraInterruptFlags = 0x0; + sinfo->ExplicitTargetMask = TARGET_FLAG_UNIT; + sinfo->Attributes |= SPELL_ATTR0_IMPOSSIBLE_DODGE_PARRY_BLOCK; + sinfo->AttributesEx |= SPELL_ATTR1_CANT_BE_REDIRECTED | SPELL_ATTR1_CANT_BE_REFLECTED; + sinfo->AttributesEx3 |= SPELL_ATTR3_NO_INITIAL_AGGRO | SPELL_ATTR3_IGNORE_HIT_RESULT; + sinfo->AttributesEx4 |= SPELL_ATTR4_IGNORE_RESISTANCES; + sinfo->AttributesEx5 |= SPELL_ATTR5_START_PERIODIC_AT_APPLY; + + sinfo->_effects[0].Effect = SPELL_EFFECT_APPLY_AURA; + sinfo->_effects[0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_TARGET_ENEMY); + sinfo->_effects[0].ApplyAuraName = SPELL_AURA_PERIODIC_LEECH; + sinfo->_effects[0].BasePoints = 45; + sinfo->_effects[0].DieSides = 0; + sinfo->_effects[0].BonusMultiplier = 1.f; + sinfo->_effects[0].DamageMultiplier = 1.f; + sinfo->_effects[0].RealPointsPerLevel = 6.f; + sinfo->_effects[0].ValueMultiplier = 2.f; + sinfo->_effects[0].RadiusEntry = nullptr; + sinfo->_effects[0].Amplitude = 1000; + //27) END DRAIN LIFE + + //28) SILENCE + //TODO: balance + spellId = SPELL_SILENCE; //29943 + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + + sinfo->SpellFamilyName = SPELLFAMILY_WARLOCK; + sinfo->DmgClass = SPELL_DAMAGE_CLASS_NONE; + sinfo->SchoolMask = SPELL_SCHOOL_MASK_SHADOW | SPELL_SCHOOL_MASK_ARCANE; + sinfo->PreventionType = SPELL_PREVENTION_TYPE_SILENCE; + sinfo->Dispel = DISPEL_MAGIC; + sinfo->Mechanic = MECHANIC_SILENCE; + sinfo->SpellLevel = 60; + sinfo->BaseLevel = 60; + sinfo->MaxTargetLevel = 0; + sinfo->CastTimeEntry = sSpellCastTimesStore.LookupEntry(2); //250ms + sinfo->RangeEntry = sSpellRangeStore.LookupEntry(4); //30 yds + //sinfo->DurationEntry = sSpellDurationStore.LookupEntry(85); //18 sec + sinfo->RecoveryTime = 15000; + sinfo->StartRecoveryCategory = 133; + sinfo->StartRecoveryTime = 1500; + sinfo->ManaCost = 75 * 5; + sinfo->MaxAffectedTargets = 5; + sinfo->AuraInterruptFlags = 0x0; + sinfo->ExplicitTargetMask = TARGET_FLAG_UNIT | TARGET_FLAG_DEST_LOCATION; + sinfo->AttributesEx |= SPELL_ATTR1_CANT_BE_REFLECTED | SPELL_ATTR1_CANT_BE_REDIRECTED; + + sinfo->_effects[0].Effect = SPELL_EFFECT_APPLY_AURA; + sinfo->_effects[0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_TARGET_ENEMY); + sinfo->_effects[0].TargetB = SpellImplicitTargetInfo(TARGET_UNIT_DEST_AREA_ENEMY); + sinfo->_effects[0].ApplyAuraName = SPELL_AURA_MOD_SILENCE; + sinfo->_effects[0].BasePoints = 1; + sinfo->_effects[0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_15_YARDS); + //28) END SILENCE + + // NECROMANCER + //29) SHADOW BOLT (MAIN_ATTACK) + //TODO: balance + spellId = SPELL_SHADOW_BOLT2; //17509 + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + + sinfo->SpellFamilyName = SPELLFAMILY_WARLOCK; + sinfo->SpellLevel = 20; + sinfo->BaseLevel = 20; + sinfo->MaxLevel = 82; + sinfo->ManaCost = 0; + sinfo->ManaCostPercentage = 0; + sinfo->ManaCostPerlevel = 0; + sinfo->CastTimeEntry = nullptr; + sinfo->RangeEntry = sSpellRangeStore.LookupEntry(4); //30 yds + sinfo->SchoolMask = SPELL_SCHOOL_MASK_SHADOW | SPELL_SCHOOL_MASK_ARCANE; + sinfo->Attributes |= SPELL_ATTR0_DONT_AFFECT_SHEATH_STATE; + + sinfo->_effects[0].BasePoints = 15; + sinfo->_effects[0].DieSides = 9; + sinfo->_effects[0].BonusMultiplier = 0.75f; + sinfo->_effects[0].DamageMultiplier = 1.f; + sinfo->_effects[0].RealPointsPerLevel = 8.f; + sinfo->_effects[0].ValueMultiplier = 1.f; + //29) END SHADOW BOLT (MAIN_ATTACK) + + //30) RAISE DEAD + spellId = SPELL_RAISE_DEAD; //34011 + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + + sinfo->SpellFamilyName = SPELLFAMILY_WARLOCK; + sinfo->SchoolMask = SPELL_SCHOOL_MASK_SHADOW; + sinfo->InterruptFlags = 0xF; + sinfo->SpellLevel = 20; + sinfo->BaseLevel = 20; + sinfo->MaxTargetLevel = 0; + sinfo->RangeEntry = sSpellRangeStore.LookupEntry(34); //25 yds + sinfo->CastTimeEntry = sSpellCastTimesStore.LookupEntry(3); //500ms + sinfo->RecoveryTime = 8000; + sinfo->StartRecoveryCategory = 133; + sinfo->StartRecoveryTime = 1500; + sinfo->ManaCost = 50 * 5; + sinfo->ManaCostPercentage = 0; + sinfo->ManaCostPerlevel = 0; + sinfo->ExplicitTargetMask = TARGET_FLAG_CORPSE_ENEMY; + sinfo->Attributes |= SPELL_ATTR0_ABILITY | SPELL_ATTR0_DONT_AFFECT_SHEATH_STATE; + sinfo->AttributesEx3 |= SPELL_ATTR3_IGNORE_HIT_RESULT; + + sinfo->_effects[0].Effect = SPELL_EFFECT_DUMMY; + sinfo->_effects[0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_TARGET_ANY); + sinfo->_effects[0].TargetB = SpellImplicitTargetInfo(0); + sinfo->_effects[1].Effect = SPELL_EFFECT_NONE; + sinfo->_effects[2].Effect = SPELL_EFFECT_NONE; + //30) END RAISE DEAD + + //31) UNHOLY FRENZY + spellId = SPELL_UNHOLY_FRENZY; //52499 + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + + sinfo->SpellFamilyName = SPELLFAMILY_WARLOCK; + sinfo->DmgClass = SPELL_DAMAGE_CLASS_NONE; + sinfo->SchoolMask = SPELL_SCHOOL_MASK_SHADOW; + sinfo->SpellLevel = 30; + sinfo->BaseLevel = 30; + sinfo->MaxTargetLevel = 0; + sinfo->DurationEntry = sSpellDurationStore.LookupEntry(22); //566 - 0 sec //3 - 60 sec //1 - 10 sec //32 - 6 sec //22 - 45 sec + sinfo->RecoveryTime = 2000; //original 1000 + sinfo->CategoryEntry = nullptr; + sinfo->StartRecoveryCategory = 133; + sinfo->StartRecoveryTime = 1500; + sinfo->ManaCost = 50 * 5; + sinfo->ManaCostPercentage = 0; + sinfo->ManaCostPerlevel = 0; + sinfo->ExplicitTargetMask = TARGET_FLAG_UNIT; + sinfo->Attributes |= SPELL_ATTR0_ABILITY | SPELL_ATTR0_DONT_AFFECT_SHEATH_STATE; + sinfo->AttributesEx |= SPELL_ATTR1_CANT_BE_REFLECTED | SPELL_ATTR1_CANT_BE_REDIRECTED; + sinfo->AttributesEx2 |= SPELL_ATTR2_CANT_CRIT; + sinfo->AttributesEx3 |= SPELL_ATTR3_IGNORE_HIT_RESULT | SPELL_ATTR3_NO_DONE_BONUS; + sinfo->AttributesEx4 |= SPELL_ATTR4_IGNORE_RESISTANCES; + + sinfo->_effects[0].ApplyAuraName = SPELL_AURA_MOD_ATTACKSPEED; + sinfo->_effects[0].BasePoints = 75; + sinfo->_effects[1].Amplitude = 3000; + sinfo->_effects[1].BasePoints = 1; + //31) END UNHOLY FRENZY + + //32) CRIPPLE + spellId = SPELL_CRIPPLE; //50379 + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + + sinfo->SpellFamilyName = SPELLFAMILY_WARLOCK; + sinfo->DmgClass = SPELL_DAMAGE_CLASS_NONE; + sinfo->Dispel = DISPEL_CURSE; //TODO: check if works + sinfo->SpellLevel = 50; + sinfo->BaseLevel = 50; + sinfo->MaxLevel = 0; + sinfo->MaxTargetLevel = 0; + sinfo->CastTimeEntry = sSpellCastTimesStore.LookupEntry(0); //0ms + sinfo->DurationEntry = sSpellDurationStore.LookupEntry(3); //60 sec + sinfo->RecoveryTime = 10000; + sinfo->CategoryEntry = nullptr; + sinfo->StartRecoveryCategory = 133; + sinfo->StartRecoveryTime = 1500; + sinfo->ManaCost = 175 * 5; + sinfo->ExplicitTargetMask = TARGET_FLAG_UNIT; + sinfo->AttributesEx |= SPELL_ATTR1_CANT_BE_REFLECTED | SPELL_ATTR1_CANT_BE_REDIRECTED; + sinfo->AttributesEx3 |= SPELL_ATTR3_IGNORE_HIT_RESULT; + //32) END CRIPPLE + + //33) CORPSE EXPLOSION + spellId = SPELL_CORPSE_EXPLOSION; //61614 + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + + sinfo->SpellFamilyName = SPELLFAMILY_WARLOCK; + sinfo->DmgClass = SPELL_DAMAGE_CLASS_NONE; + sinfo->SchoolMask = SPELL_SCHOOL_MASK_SHADOW; + sinfo->TargetCreatureType = 0x0000037F; + sinfo->InterruptFlags = 0xF; + sinfo->SpellLevel = 40; + sinfo->BaseLevel = 40; + sinfo->MaxTargetLevel = 0; + sinfo->DurationEntry = sSpellDurationStore.LookupEntry(21); //-1 + sinfo->CastTimeEntry = sSpellCastTimesStore.LookupEntry(110); //750ms + sinfo->RangeEntry = sSpellRangeStore.LookupEntry(3); //20 yds + sinfo->RecoveryTime = 1500; + sinfo->StartRecoveryCategory = 133; + sinfo->StartRecoveryTime = 1500; + sinfo->ManaCost = 100 * 5; + sinfo->ExplicitTargetMask = TARGET_FLAG_CORPSE_ENEMY; + sinfo->Attributes |= SPELL_ATTR0_ABILITY | SPELL_ATTR0_DONT_AFFECT_SHEATH_STATE; + sinfo->AttributesEx2 |= SPELL_ATTR2_CANT_CRIT; + sinfo->AttributesEx3 |= SPELL_ATTR3_IGNORE_HIT_RESULT | SPELL_ATTR3_NO_DONE_BONUS; + + sinfo->_effects[0].Effect = SPELL_EFFECT_APPLY_AURA; + sinfo->_effects[0].ApplyAuraName = SPELL_AURA_DUMMY; + sinfo->_effects[0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_TARGET_ANY); + sinfo->_effects[0].TargetB = SpellImplicitTargetInfo(0); + sinfo->_effects[0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_10_YARDS); + sinfo->_effects[0].SpellClassMask[0] = 0; + sinfo->_effects[0].BasePoints = 1; + sinfo->_effects[1].Effect = SPELL_EFFECT_NONE; + //33) END CORPSE EXPLOSION + + //SEA WITCH + //35) FORKED LIGHTNING + spellId = SPELL_FORKED_LIGHTNING; //63541 + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + + sinfo->SpellFamilyName = SPELLFAMILY_MAGE; + sinfo->SchoolMask = SPELL_SCHOOL_MASK_NATURE | SPELL_SCHOOL_MASK_ARCANE; + sinfo->PreventionType = SPELL_PREVENTION_TYPE_SILENCE; + sinfo->InterruptFlags = 0x9; + sinfo->SpellLevel = 4; + sinfo->BaseLevel = 4; + sinfo->CastTimeEntry = sSpellCastTimesStore.LookupEntry(110); //750ms + sinfo->RangeEntry = sSpellRangeStore.LookupEntry(4); //30 yds + sinfo->RecoveryTime = 11000; + sinfo->StartRecoveryCategory = 133; + sinfo->StartRecoveryTime = 1500; + sinfo->ManaCost = 110 * 5; + sinfo->MaxAffectedTargets = 2; + sinfo->Speed = 1000.f; + sinfo->AttributesEx |= SPELL_ATTR1_NO_THREAT; + sinfo->AttributesEx3 |= SPELL_ATTR3_IGNORE_HIT_RESULT; + sinfo->AttributesEx5 |= SPELL_ATTR5_DONT_TURN_DURING_CAST; + //sinfo->AttributesEx6 |= SPELL_ATTR6_CAN_TARGET_INVISIBLE; + + sinfo->_effects[0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_CONE_ENEMY_24); + //sinfo->_effects[0].TargetB = SpellImplicitTargetInfo(TARGET_UNIT_CONE_ENEMY_24); + sinfo->_effects[0].RadiusEntry = nullptr;//sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_50_YARDS); + sinfo->_effects[0].BasePoints = 1; + sinfo->_effects[0].DieSides = 49; + sinfo->_effects[0].BonusMultiplier = 0.0f; + sinfo->_effects[0].DamageMultiplier = 1.f; + sinfo->_effects[0].RealPointsPerLevel = 15.f; + sinfo->_effects[0].ValueMultiplier = 1.f; + //35) END FORKED LIGHTNING + + //36) FORKED LIGHTNING EFFECT + spellId = SPELL_FORKED_LIGHTNING_EFFECT; //50900 + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + + sinfo->SpellFamilyName = SPELLFAMILY_MAGE; + sinfo->SchoolMask = SPELL_SCHOOL_MASK_NATURE | SPELL_SCHOOL_MASK_ARCANE; + sinfo->Dispel = DISPEL_MAGIC; + sinfo->Mechanic = MECHANIC_STUN; + sinfo->DurationEntry = sSpellDurationStore.LookupEntry(39); //2000ms + sinfo->CastTimeEntry = sSpellCastTimesStore.LookupEntry(1); //instant + sinfo->RangeEntry = sSpellRangeStore.LookupEntry(6); //100 yds + sinfo->ManaCost = 0; + sinfo->AttributesEx3 |= SPELL_ATTR3_IGNORE_HIT_RESULT; + sinfo->AttributesEx5 |= SPELL_ATTR5_DONT_TURN_DURING_CAST; + sinfo->AttributesEx6 |= SPELL_ATTR6_CAN_TARGET_INVISIBLE; + + sinfo->_effects[0].Effect = SPELL_EFFECT_APPLY_AURA; + sinfo->_effects[0].ApplyAuraName = SPELL_AURA_MOD_STUN; + sinfo->_effects[0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_TARGET_ENEMY); + sinfo->_effects[0].RadiusEntry = nullptr; + //36) END FORKED LIGHTNING EFFECT + + //37) FROST ARROW + spellId = SPELL_FROST_ARROW; //38942 + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + + sinfo->SpellFamilyName = SPELLFAMILY_MAGE; + //sinfo->SpellFamilyFlags[0] = 0x0; + sinfo->SpellFamilyFlags[1] = 0x4; //custom, not present in db + //sinfo->SpellFamilyFlags[2] = 0x0; + sinfo->DmgClass = SPELL_DAMAGE_CLASS_RANGED; + sinfo->SchoolMask = SPELL_SCHOOL_MASK_FROST | SPELL_SCHOOL_MASK_ARCANE; + sinfo->PreventionType = SPELL_PREVENTION_TYPE_PACIFY; + sinfo->Dispel = DISPEL_NONE; + sinfo->Mechanic = MECHANIC_NONE; + sinfo->SpellLevel = 4; + sinfo->BaseLevel = 4; + sinfo->MaxTargetLevel = 0; + sinfo->CastTimeEntry = sSpellCastTimesStore.LookupEntry(110); //750ms + sinfo->RangeEntry = sSpellRangeStore.LookupEntry(35); //0-35 yds + sinfo->DurationEntry = nullptr; + sinfo->RecoveryTime = 0; + sinfo->StartRecoveryCategory = 133; + sinfo->StartRecoveryTime = 750; + sinfo->PowerType = POWER_MANA; + sinfo->ManaCost = 10 * 5; + sinfo->MaxAffectedTargets = 1; + sinfo->AuraInterruptFlags = AURA_INTERRUPT_FLAG_CHANGE_MAP; + sinfo->ExplicitTargetMask = TARGET_FLAG_UNIT; + sinfo->Attributes |= SPELL_ATTR0_IMPOSSIBLE_DODGE_PARRY_BLOCK | SPELL_ATTR0_DONT_AFFECT_SHEATH_STATE; + sinfo->Attributes &= ~(SPELL_ATTR0_REQ_AMMO); + sinfo->AttributesEx |= SPELL_ATTR1_CANT_BE_REDIRECTED | SPELL_ATTR1_CANT_BE_REFLECTED; + sinfo->AttributesEx2 |= SPELL_ATTR2_NOT_RESET_AUTO_ACTIONS/* | SPELL_ATTR2_CANT_CRIT*/; + sinfo->AttributesEx4 |= SPELL_ATTR4_IGNORE_RESISTANCES; + + sinfo->_effects[0].Effect = SPELL_EFFECT_WEAPON_DAMAGE; + sinfo->_effects[0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_TARGET_ENEMY); + sinfo->_effects[0].BasePoints = 10; + sinfo->_effects[0].DieSides = 0; + sinfo->_effects[0].BonusMultiplier = 0.5f; + sinfo->_effects[0].DamageMultiplier = 1.f; + sinfo->_effects[0].RealPointsPerLevel = 2.f; + sinfo->_effects[0].ValueMultiplier = 1.f; + sinfo->_effects[0].RadiusEntry = nullptr; + sinfo->_effects[1].Effect = SPELL_EFFECT_NONE; + //37) END FROST ARROW + + //38) FROST ARROW EFFECT + spellId = SPELL_FROST_ARROW_EFFECT; //56095 + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + + sinfo->SpellFamilyName = SPELLFAMILY_GENERIC; + //sinfo->SpellFamilyFlags[0] = 0x0; + sinfo->SpellFamilyFlags[1] = 0x4; //custom, not present in db + //sinfo->SpellFamilyFlags[2] = 0x0; + sinfo->SchoolMask = SPELL_SCHOOL_MASK_FROST | SPELL_SCHOOL_MASK_ARCANE; + sinfo->Dispel = DISPEL_MAGIC; + sinfo->Mechanic = MECHANIC_SNARE; + sinfo->Attributes &= ~(SPELL_ATTR0_CAST_TRACK_TARGET); + sinfo->AttributesEx |= SPELL_ATTR1_CANT_BE_REDIRECTED | SPELL_ATTR1_CANT_BE_REFLECTED; + sinfo->AttributesEx4 |= SPELL_ATTR4_IGNORE_RESISTANCES; + + sinfo->_effects[0].Effect = SPELL_EFFECT_APPLY_AURA; + sinfo->_effects[0].ApplyAuraName = SPELL_AURA_MOD_SPEED_SLOW_ALL; + sinfo->_effects[0].Mechanic = MECHANIC_SLOW_ATTACK; + sinfo->_effects[0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_CASTER); + sinfo->_effects[0].BasePoints = -30; + sinfo->_effects[0].DieSides = 0; + sinfo->_effects[0].BonusMultiplier = 1.f; + sinfo->_effects[0].DamageMultiplier = 1.f; + sinfo->_effects[0].RealPointsPerLevel = 0.f; + sinfo->_effects[0].ValueMultiplier = 1.f; + sinfo->_effects[0].RadiusEntry = nullptr; + sinfo->_effects[1].Effect = SPELL_EFFECT_APPLY_AURA; + sinfo->_effects[1].ApplyAuraName = SPELL_AURA_MOD_DECREASE_SPEED; + sinfo->_effects[1].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_CASTER); + sinfo->_effects[1].BasePoints = -30; + sinfo->_effects[1].DieSides = 0; + sinfo->_effects[1].BonusMultiplier = 1.f; + sinfo->_effects[1].DamageMultiplier = 1.f; + sinfo->_effects[1].RealPointsPerLevel = 0.f; + sinfo->_effects[1].ValueMultiplier = 1.f; + sinfo->_effects[1].RadiusEntry = nullptr; + //38) END FROST ARROW EFFECT + + //39) MANA SHIELD + spellId = SPELL_MANA_SHIELD; //35064 + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + + sinfo->Dispel = DISPEL_NONE; + sinfo->PreventionType = SPELL_PREVENTION_TYPE_NONE; + sinfo->SpellLevel = 0; + sinfo->BaseLevel = 0; + sinfo->MaxTargetLevel = 0; + sinfo->DurationEntry = sSpellDurationStore.LookupEntry(21); //-1 + sinfo->RecoveryTime = 10000; + sinfo->StartRecoveryCategory = 133; + sinfo->StartRecoveryTime = 1500; + sinfo->Attributes |= SPELL_ATTR0_ABILITY | SPELL_ATTR0_DONT_AFFECT_SHEATH_STATE | SPELL_ATTR0_DISABLED_WHILE_ACTIVE; + sinfo->AttributesEx3 |= SPELL_ATTR3_IGNORE_HIT_RESULT | SPELL_ATTR3_NO_DONE_BONUS; + sinfo->AttributesEx4 |= SPELL_ATTR4_NOT_STEALABLE; + + sinfo->_effects[0].BasePoints = 1000000000; + sinfo->_effects[0].ValueMultiplier = 10.f; + //39) END MANA SHIELD + + //40) TORNADO + spellId = SPELL_TORNADO; //34695 + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + + sinfo->SpellFamilyName = SPELLFAMILY_MAGE; + sinfo->SchoolMask = SPELL_SCHOOL_MASK_NATURE | SPELL_SCHOOL_MASK_ARCANE; + sinfo->InterruptFlags = 0x9; + sinfo->SpellLevel = 60; + sinfo->BaseLevel = 60; + sinfo->MaxTargetLevel = 0; + sinfo->DurationEntry = nullptr; + sinfo->CastTimeEntry = sSpellCastTimesStore.LookupEntry(15); //4000ms + //sinfo->RangeEntry = sSpellRangeStore.LookupEntry(5); //40 yds + sinfo->RecoveryTime = 120000; + sinfo->StartRecoveryCategory = 133; + sinfo->StartRecoveryTime = 1500; + sinfo->ManaCost = 250 * 5; + sinfo->ExplicitTargetMask = TARGET_FLAG_DEST_LOCATION; + sinfo->Attributes |= SPELL_ATTR0_ABILITY | SPELL_ATTR0_DONT_AFFECT_SHEATH_STATE | SPELL_ATTR0_OUTDOORS_ONLY; + sinfo->AttributesEx2 |= SPELL_ATTR2_CANT_CRIT | SPELL_ATTR2_CAN_TARGET_NOT_IN_LOS; + sinfo->AttributesEx3 |= SPELL_ATTR3_IGNORE_HIT_RESULT | SPELL_ATTR3_NO_DONE_BONUS; + sinfo->AttributesEx3 &= ~(SPELL_ATTR3_ONLY_TARGET_PLAYERS); + sinfo->AttributesEx4 = 0; + + sinfo->_effects[0].Effect = SPELL_EFFECT_DUMMY; + sinfo->_effects[0].TargetA = SpellImplicitTargetInfo(TARGET_DEST_DEST); + sinfo->_effects[0].TargetB = SpellImplicitTargetInfo(0); + sinfo->_effects[0].RadiusEntry = nullptr; + sinfo->_effects[0].BasePoints = 1; + sinfo->_effects[0].TriggerSpell = 0; + sinfo->_effects[0].Amplitude = 0; + sinfo->_effects[1].Effect = SPELL_EFFECT_NONE; + //40) END TORNADO + + //41) TORNADO EFFECT + spellId = SPELL_TORNADO_EFFECT; //21990 + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + + sinfo->SpellFamilyName = SPELLFAMILY_MAGE; + //sinfo->SpellFamilyFlags[0] = 0x0; + sinfo->SpellFamilyFlags[1] = 0x4; //custom, not present in db + //sinfo->SpellFamilyFlags[2] = 0x0; + sinfo->SchoolMask = SPELL_SCHOOL_MASK_NATURE | SPELL_SCHOOL_MASK_ARCANE; + sinfo->Dispel = DISPEL_MAGIC; + sinfo->Mechanic = MECHANIC_NONE; //MECHANIC_KNOCKOUT + sinfo->InterruptFlags = 0x0; + sinfo->SpellLevel = 60; + sinfo->BaseLevel = 60; + sinfo->MaxTargetLevel = 0; + sinfo->DurationEntry = sSpellDurationStore.LookupEntry(29); //12000ms + sinfo->CastTimeEntry = sSpellCastTimesStore.LookupEntry(1); //0ms + sinfo->RangeEntry = sSpellRangeStore.LookupEntry(2); //5 yds + sinfo->RecoveryTime = 3000; + //sinfo->StartRecoveryCategory = 133; + //sinfo->StartRecoveryTime = 1500; + //sinfo->ManaCost = 250 * 5; + sinfo->MaxAffectedTargets = 1; + sinfo->ExplicitTargetMask = TARGET_FLAG_UNIT; + sinfo->Attributes |= SPELL_ATTR0_ABILITY | SPELL_ATTR0_DONT_AFFECT_SHEATH_STATE | SPELL_ATTR0_OUTDOORS_ONLY; + sinfo->Attributes &= ~(SPELL_ATTR0_HEARTBEAT_RESIST_CHECK); + sinfo->AttributesEx2 |= SPELL_ATTR2_CANT_CRIT | SPELL_ATTR2_CAN_TARGET_NOT_IN_LOS; + sinfo->AttributesEx3 |= SPELL_ATTR3_IGNORE_HIT_RESULT | SPELL_ATTR3_NO_DONE_BONUS; + sinfo->AttributesEx3 &= ~(SPELL_ATTR3_ONLY_TARGET_PLAYERS); + sinfo->AttributesEx4 = 0; + sinfo->AttributesEx5 = 0; + + //sinfo->_effects[0].Effect = SPELL_EFFECT_DUMMY; + //sinfo->_effects[0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_TARGET_ENEMY); + //sinfo->_effects[0].TargetB = SpellImplicitTargetInfo(0); + //sinfo->_effects[0].RadiusEntry = nullptr; + //sinfo->_effects[0].BasePoints = 1; + //sinfo->_effects[0].TriggerSpell = 0; + //sinfo->_effects[0].Amplitude = 0; + sinfo->_effects[1].Effect = SPELL_EFFECT_APPLY_AURA; + sinfo->_effects[1].ApplyAuraName = SPELL_AURA_MOD_RESISTANCE_PCT; + sinfo->_effects[1].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_TARGET_ENEMY); + sinfo->_effects[1].TargetB = SpellImplicitTargetInfo(0); + sinfo->_effects[1].BasePoints = -100; + sinfo->_effects[1].MiscValue = SPELL_SCHOOL_MASK_ALL; + sinfo->_effects[2].Effect = SPELL_EFFECT_APPLY_AURA; + sinfo->_effects[2].ApplyAuraName = SPELL_AURA_PERIODIC_DAMAGE; + sinfo->_effects[2].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_TARGET_ENEMY); + sinfo->_effects[2].TargetB = SpellImplicitTargetInfo(0); + sinfo->_effects[2].BasePoints = 212; + sinfo->_effects[2].DieSides = 183; + sinfo->_effects[2].RealPointsPerLevel = 35.f; + sinfo->_effects[2].BonusMultiplier = 0.25f; + sinfo->_effects[2].Amplitude = 1500; + //41) END TORNADO EFFECT + + //42) TORNADO EFFECT2 + spellId = SPELL_TORNADO_EFFECT2; //34683 + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + + sinfo->SpellFamilyName = SPELLFAMILY_MAGE; + //sinfo->SpellFamilyFlags[0] = 0x0; + //sinfo->SpellFamilyFlags[1] = 0x4; //custom, not present in db + //sinfo->SpellFamilyFlags[2] = 0x0; + sinfo->SchoolMask = SPELL_SCHOOL_MASK_NATURE | SPELL_SCHOOL_MASK_ARCANE; + //sinfo->Dispel = DISPEL_MAGIC; + //sinfo->Mechanic = MECHANIC_DISORIENTED; + sinfo->ProcFlags = 0; + sinfo->InterruptFlags = 0x0; + sinfo->SpellLevel = 60; + sinfo->BaseLevel = 60; + sinfo->MaxTargetLevel = 0; + sinfo->DurationEntry = nullptr; + //sinfo->CastTimeEntry = sSpellCastTimesStore.LookupEntry(1); //0ms + sinfo->RangeEntry = sSpellRangeStore.LookupEntry(7); //10 yds + sinfo->RecoveryTime = 4500; + //sinfo->StartRecoveryCategory = 133; + //sinfo->StartRecoveryTime = 1500; + //sinfo->ManaCost = 250 * 5; + sinfo->MaxAffectedTargets = 1; + sinfo->ExplicitTargetMask = TARGET_FLAG_UNIT; + sinfo->Attributes |= SPELL_ATTR0_DONT_AFFECT_SHEATH_STATE | SPELL_ATTR0_OUTDOORS_ONLY; + sinfo->Attributes &= ~(SPELL_ATTR0_UNK11); + sinfo->AttributesEx2 |= SPELL_ATTR2_CAN_TARGET_NOT_IN_LOS; + sinfo->AttributesEx3 |= SPELL_ATTR3_IGNORE_HIT_RESULT; + sinfo->AttributesEx4 = 0; + sinfo->AttributesEx5 = 0; + + sinfo->_effects[0].Effect = SPELL_EFFECT_SCHOOL_DAMAGE; + sinfo->_effects[0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_TARGET_ENEMY); + sinfo->_effects[0].TargetB = SpellImplicitTargetInfo(0); + //sinfo->_effects[0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_10_YARDS); + sinfo->_effects[0].BasePoints = 541; + sinfo->_effects[0].DieSides = 215; + sinfo->_effects[0].RealPointsPerLevel = 40.f; + sinfo->_effects[0].BonusMultiplier = 0.5f; + //42) END TORNADO EFFECT2 + + //43) TORNADO EFFECT3 + spellId = SPELL_TORNADO_EFFECT3; //39261 + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + + sinfo->SpellFamilyName = SPELLFAMILY_MAGE; + //sinfo->SpellFamilyFlags[0] = 0x0; + //sinfo->SpellFamilyFlags[1] = 0x4; //custom, not present in db + //sinfo->SpellFamilyFlags[2] = 0x0; + sinfo->SchoolMask = SPELL_SCHOOL_MASK_NATURE | SPELL_SCHOOL_MASK_ARCANE; + //sinfo->Dispel = DISPEL_NONE; + //sinfo->Mechanic = MECHANIC_DISORIENTED; + //sinfo->ProcFlags = 0; + //sinfo->InterruptFlags = 0x0; + sinfo->SpellLevel = 0; + sinfo->BaseLevel = 0; + //sinfo->MaxTargetLevel = 0; + sinfo->DurationEntry = sSpellDurationStore.LookupEntry(21); //-1 + //sinfo->CastTimeEntry = sSpellCastTimesStore.LookupEntry(1); //0ms + //sinfo->RangeEntry = sSpellRangeStore.LookupEntry(1); //self + //sinfo->RecoveryTime = 4500; + //sinfo->StartRecoveryCategory = 133; + //sinfo->StartRecoveryTime = 1500; + //sinfo->ManaCost = 250 * 5; + //sinfo->MaxAffectedTargets = 1; + //sinfo->ExplicitTargetMask = TARGET_FLAG_UNIT; + sinfo->Attributes |= SPELL_ATTR0_DONT_AFFECT_SHEATH_STATE | SPELL_ATTR0_OUTDOORS_ONLY; + sinfo->AttributesEx |= SPELL_ATTR1_NO_THREAT; + sinfo->AttributesEx2 |= SPELL_ATTR2_CAN_TARGET_NOT_IN_LOS; + sinfo->AttributesEx3 |= SPELL_ATTR3_IGNORE_HIT_RESULT; + sinfo->AttributesEx4 = 0; + sinfo->AttributesEx5 = 0; + + sinfo->_effects[0].Effect = SPELL_EFFECT_APPLY_AREA_AURA_ENEMY; + //sinfo->_effects[0].ApplyAuraName = SPELL_AURA_MOD_DECREASE_SPEED; + //sinfo->_effects[0].TargetA = SpellImplicitTargetInfo(TARGET_SRC_CASTER); + //sinfo->_effects[0].TargetB = SpellImplicitTargetInfo(TARGET_UNIT_SRC_AREA_ENEMY); + sinfo->_effects[0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_5_YARDS); + //sinfo->_effects[0].BasePoints = -50; + sinfo->_effects[1].Effect = SPELL_EFFECT_APPLY_AREA_AURA_ENEMY; + sinfo->_effects[1].ApplyAuraName = SPELL_AURA_MOD_DECREASE_SPEED; + sinfo->_effects[1].TargetA = SpellImplicitTargetInfo(TARGET_SRC_CASTER); + sinfo->_effects[1].TargetB = SpellImplicitTargetInfo(TARGET_UNIT_SRC_AREA_ENEMY); + sinfo->_effects[1].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_5_YARDS); + sinfo->_effects[1].BasePoints = -60; + //sinfo->AttributesCu &= ~(SPELL_ATTR0_CU_NEGATIVE_EFF1); + //43) END TORNADO EFFECT3 + + //44) SHOOT + spellId = SPELL_SHOOT_BOW; //41188 + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + + sinfo->SpellFamilyName = SPELLFAMILY_MAGE; + sinfo->DmgClass = SPELL_DAMAGE_CLASS_RANGED; + sinfo->PreventionType = SPELL_PREVENTION_TYPE_PACIFY; + sinfo->SpellLevel = 1; + sinfo->BaseLevel = 1; + sinfo->CategoryEntry = sSpellCategoryStore.LookupEntry(76); + sinfo->CastTimeEntry = sSpellCastTimesStore.LookupEntry(110); //750ms + sinfo->RangeEntry = sSpellRangeStore.LookupEntry(35); //0-35 yds + sinfo->StartRecoveryCategory = 133; + sinfo->StartRecoveryTime = 750; + sinfo->ExplicitTargetMask = TARGET_FLAG_UNIT; + sinfo->Attributes |= SPELL_ATTR0_IMPOSSIBLE_DODGE_PARRY_BLOCK/* | SPELL_ATTR0_DONT_AFFECT_SHEATH_STATE*/; + sinfo->Attributes &= ~(SPELL_ATTR0_REQ_AMMO/* | SPELL_ATTR0_ABILITY*/ | SPELL_ATTR0_CAST_TRACK_TARGET | SPELL_ATTR0_LEVEL_DAMAGE_CALCULATION | SPELL_ATTR0_UNAFFECTED_BY_INVULNERABILITY); + sinfo->AttributesEx |= SPELL_ATTR1_CANT_BE_REDIRECTED | SPELL_ATTR1_CANT_BE_REFLECTED; + sinfo->AttributesEx &= ~(SPELL_ATTR1_CHANNEL_TRACK_TARGET | SPELL_ATTR1_NO_THREAT); + sinfo->AttributesEx2 |= SPELL_ATTR2_NOT_RESET_AUTO_ACTIONS; + sinfo->AttributesEx2 &= ~(SPELL_ATTR2_CAN_TARGET_NOT_IN_LOS); + sinfo->AttributesEx3 |= SPELL_ATTR3_UNK15; + + sinfo->_effects[0].Effect = SPELL_EFFECT_WEAPON_PERCENT_DAMAGE; + sinfo->_effects[0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_TARGET_ENEMY); + sinfo->_effects[0].TargetB = SpellImplicitTargetInfo(0); + sinfo->_effects[0].BasePoints = 100; + sinfo->_effects[0].DieSides = 0; + sinfo->_effects[0].BonusMultiplier = 1.f; + //44) END SHOOT + + //CRYPT LORD + //45) IMPALE + spellId = SPELL_IMPALE; //53458 + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + + sinfo->SpellFamilyName = SPELLFAMILY_WARRIOR; + sinfo->PreventionType = SPELL_PREVENTION_TYPE_PACIFY; + sinfo->SpellLevel = 20; + sinfo->BaseLevel = 20; + sinfo->CategoryEntry = nullptr; + sinfo->RecoveryTime = 9000; + sinfo->CategoryRecoveryTime = 0; + sinfo->StartRecoveryCategory = 133; + sinfo->StartRecoveryTime = 1500; + sinfo->PowerType = POWER_MANA; + sinfo->ManaCost = 100 * 5; + sinfo->MaxAffectedTargets = 0; + sinfo->InterruptFlags = 0x1; + sinfo->ChannelInterruptFlags = 0x0; + sinfo->CastTimeEntry = sSpellCastTimesStore.LookupEntry(1); //0 + sinfo->RangeEntry = sSpellRangeStore.LookupEntry(5); //40 yds + sinfo->DurationEntry = sSpellDurationStore.LookupEntry(592); //400ms + sinfo->ExplicitTargetMask = TARGET_FLAG_UNIT; + sinfo->Attributes |= SPELL_ATTR0_HIDDEN_CLIENTSIDE | SPELL_ATTR0_ABILITY; + sinfo->AttributesEx |= SPELL_ATTR1_CHANNELED_2 | SPELL_ATTR1_CHANNEL_TRACK_TARGET | SPELL_ATTR1_NO_THREAT; + sinfo->AttributesEx2 |= SPELL_ATTR2_CAN_TARGET_NOT_IN_LOS; + sinfo->AttributesEx3 |= SPELL_ATTR3_NO_INITIAL_AGGRO; + + sinfo->_effects[0].Effect = SPELL_EFFECT_APPLY_AURA; + sinfo->_effects[0].ApplyAuraName = SPELL_AURA_DUMMY; + sinfo->_effects[0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_CONE_ENEMY_24); + sinfo->_effects[0].TargetB = SpellImplicitTargetInfo(0); + sinfo->_effects[0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_40_YARDS); + sinfo->_effects[0].MiscValue = 0; + sinfo->_effects[0].MiscValueB = 0; + sinfo->_effects[0].BasePoints = 1; + sinfo->_effects[0].Amplitude = 0; + sinfo->_effects[0].RealPointsPerLevel = 0.0f; + sinfo->_effects[0].DieSides = 0; + sinfo->_effects[0].DamageMultiplier = 0.0f; + sinfo->_effects[0].BonusMultiplier = 0.0f; + + sinfo->_effects[1].Effect = SPELL_EFFECT_APPLY_AURA; + sinfo->_effects[1].ApplyAuraName = SPELL_AURA_DUMMY; + sinfo->_effects[1].TargetA = SpellImplicitTargetInfo(TARGET_SRC_CASTER); + sinfo->_effects[1].TargetB = SpellImplicitTargetInfo(TARGET_UNIT_SRC_AREA_ENEMY); + sinfo->_effects[1].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_8_YARDS); + sinfo->_effects[1].MiscValue = 0; + sinfo->_effects[1].MiscValueB = 0; + sinfo->_effects[1].BasePoints = 1; + sinfo->_effects[1].Amplitude = 0; + sinfo->_effects[1].RealPointsPerLevel = 0.0f; + sinfo->_effects[1].DieSides = 0; + sinfo->_effects[1].DamageMultiplier = 0.0f; + sinfo->_effects[1].BonusMultiplier = 0.0f; + //45) END IMPALE + + //46) IMPALE DAMAGE + spellId = SPELL_IMPALE_DAMAGE; //53454 + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + + sinfo->SpellFamilyName = SPELLFAMILY_WARRIOR; + sinfo->PreventionType = SPELL_PREVENTION_TYPE_NONE; + sinfo->SpellLevel = 20; + sinfo->BaseLevel = 20; + sinfo->CategoryEntry = nullptr; + sinfo->RecoveryTime = 0; + sinfo->CategoryRecoveryTime = 0; + sinfo->StartRecoveryCategory = 0; + sinfo->StartRecoveryTime = 0; + sinfo->PowerType = POWER_MANA; + sinfo->ManaCost = 0; + sinfo->MaxAffectedTargets = 1; + sinfo->ChannelInterruptFlags = 0; + sinfo->CastTimeEntry = sSpellCastTimesStore.LookupEntry(1); //0 + sinfo->RangeEntry = sSpellRangeStore.LookupEntry(36); //45 yds + sinfo->DurationEntry = sSpellDurationStore.LookupEntry(32); //6000ms + sinfo->ExplicitTargetMask = TARGET_FLAG_UNIT_ENEMY; + sinfo->Attributes |= SPELL_ATTR0_ABILITY | SPELL_ATTR0_DONT_AFFECT_SHEATH_STATE | SPELL_ATTR0_CASTABLE_WHILE_DEAD | SPELL_ATTR0_CASTABLE_WHILE_SITTING; + sinfo->AttributesEx |= SPELL_ATTR1_CANT_BE_REFLECTED | SPELL_ATTR1_CANT_BE_REDIRECTED; + sinfo->AttributesEx2 |= SPELL_ATTR2_CAN_TARGET_NOT_IN_LOS; + sinfo->AttributesEx3 |= SPELL_ATTR3_IGNORE_HIT_RESULT; + sinfo->AttributesEx5 |= SPELL_ATTR5_USABLE_WHILE_STUNNED; + sinfo->AttributesEx6 |= SPELL_ATTR6_CASTABLE_WHILE_ON_VEHICLE | SPELL_ATTR6_CAN_TARGET_INVISIBLE; + + sinfo->_effects[0].Effect = SPELL_EFFECT_SCHOOL_DAMAGE; + sinfo->_effects[0].ApplyAuraName = SPELL_AURA_NONE; + sinfo->_effects[0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_TARGET_ENEMY); + sinfo->_effects[0].TargetB = SpellImplicitTargetInfo(0); + sinfo->_effects[0].RadiusEntry = nullptr; + sinfo->_effects[0].MiscValue = 0; + sinfo->_effects[0].MiscValueB = 0; + sinfo->_effects[0].BasePoints = 150; + sinfo->_effects[0].Amplitude = 0; + sinfo->_effects[0].RealPointsPerLevel = 35.0f; + sinfo->_effects[0].DieSides = 200; + sinfo->_effects[0].DamageMultiplier = 0.0f; + sinfo->_effects[0].BonusMultiplier = 0.0f; + + sinfo->_effects[1].Effect = SPELL_EFFECT_KNOCK_BACK; + sinfo->_effects[1].ApplyAuraName = SPELL_AURA_NONE; + sinfo->_effects[1].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_TARGET_ENEMY); + sinfo->_effects[1].TargetB = SpellImplicitTargetInfo(0); + sinfo->_effects[1].RadiusEntry = nullptr; + sinfo->_effects[1].Mechanic = MECHANIC_KNOCKOUT; + sinfo->_effects[1].MiscValue = 5; + sinfo->_effects[1].MiscValueB = 0; + sinfo->_effects[1].BasePoints = 180; + sinfo->_effects[1].Amplitude = 0; + sinfo->_effects[1].RealPointsPerLevel = 0.0; + sinfo->_effects[1].DieSides = 0; + sinfo->_effects[1].DamageMultiplier = 0.0f; + sinfo->_effects[1].BonusMultiplier = 0.0f; + + sinfo->_effects[2].Effect = SPELL_EFFECT_APPLY_AURA; + sinfo->_effects[2].ApplyAuraName = SPELL_AURA_MOD_STUN; + //sinfo->_effects[2].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_TARGET_ENEMY); + //sinfo->_effects[2].TargetB = SpellImplicitTargetInfo(0); + sinfo->_effects[2].TargetA = sinfo->_effects[0].TargetA; + sinfo->_effects[2].TargetB = sinfo->_effects[0].TargetB; + sinfo->_effects[2].RadiusEntry = nullptr; + sinfo->_effects[2].Mechanic = MECHANIC_NONE; + sinfo->_effects[2].MiscValue = 0; + sinfo->_effects[2].MiscValueB = 0; + sinfo->_effects[2].BasePoints = 1; + sinfo->_effects[2].Amplitude = 0; + sinfo->_effects[2].RealPointsPerLevel = 0.0; + sinfo->_effects[2].DieSides = 0; + sinfo->_effects[2].DamageMultiplier = 0.0f; + sinfo->_effects[2].BonusMultiplier = 0.0f; + //46) END IMPALE DAMAGE + + //47) IMPALE VISUAL + spellId = SPELL_IMPALE_VISUAL; //53454 + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + + sinfo->SpellFamilyName = SPELLFAMILY_WARRIOR; + sinfo->PreventionType = SPELL_PREVENTION_TYPE_NONE; + sinfo->SpellLevel = 20; + sinfo->BaseLevel = 20; + sinfo->CategoryEntry = nullptr; + sinfo->RecoveryTime = 0; + sinfo->CategoryRecoveryTime = 0; + sinfo->StartRecoveryCategory = 0; + sinfo->StartRecoveryTime = 0; + sinfo->PowerType = POWER_MANA; + sinfo->ManaCost = 0; + sinfo->MaxAffectedTargets = 1; + sinfo->ChannelInterruptFlags = 0; + sinfo->CastTimeEntry = sSpellCastTimesStore.LookupEntry(1); //0 + sinfo->RangeEntry = sSpellRangeStore.LookupEntry(36); //45 yds + sinfo->ExplicitTargetMask = TARGET_FLAG_DEST_LOCATION; + sinfo->Attributes |= SPELL_ATTR0_ABILITY | SPELL_ATTR0_DONT_AFFECT_SHEATH_STATE | SPELL_ATTR0_CASTABLE_WHILE_DEAD | SPELL_ATTR0_CASTABLE_WHILE_SITTING; + sinfo->AttributesEx |= SPELL_ATTR1_CANT_BE_REFLECTED | SPELL_ATTR1_CANT_BE_REDIRECTED | SPELL_ATTR1_NO_THREAT; + sinfo->AttributesEx2 |= SPELL_ATTR2_CAN_TARGET_NOT_IN_LOS; + sinfo->AttributesEx3 |= SPELL_ATTR3_IGNORE_HIT_RESULT | SPELL_ATTR3_NO_INITIAL_AGGRO; + sinfo->AttributesEx5 |= SPELL_ATTR5_USABLE_WHILE_STUNNED; + sinfo->AttributesEx6 |= SPELL_ATTR6_CASTABLE_WHILE_ON_VEHICLE | SPELL_ATTR6_CAN_TARGET_INVISIBLE; + + sinfo->_effects[0].Effect = SPELL_EFFECT_DUMMY; + sinfo->_effects[0].ApplyAuraName = SPELL_AURA_NONE; + sinfo->_effects[0].TargetA = SpellImplicitTargetInfo(TARGET_DEST_DEST); + sinfo->_effects[0].TargetB = SpellImplicitTargetInfo(0); + sinfo->_effects[0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_0_5_YARDS); + sinfo->_effects[0].MiscValue = 0; + sinfo->_effects[0].MiscValueB = 0; + sinfo->_effects[0].BasePoints = 1; + sinfo->_effects[0].Amplitude = 0; + sinfo->_effects[0].RealPointsPerLevel = 0.0f; + sinfo->_effects[0].DieSides = 0; + sinfo->_effects[0].DamageMultiplier = 0.0f; + sinfo->_effects[0].BonusMultiplier = 0.0f; + + sinfo->_effects[1].Effect = SPELL_EFFECT_NONE; + sinfo->_effects[1].ApplyAuraName = SPELL_AURA_NONE; + sinfo->_effects[1].TargetA = SpellImplicitTargetInfo(0); + sinfo->_effects[1].TargetB = SpellImplicitTargetInfo(0); + sinfo->_effects[1].RadiusEntry = nullptr; + sinfo->_effects[1].MiscValueB = 0; + sinfo->_effects[1].BasePoints = 0; + sinfo->_effects[1].Amplitude = 0; + sinfo->_effects[1].RealPointsPerLevel = 0.0; + sinfo->_effects[1].DieSides = 0; + sinfo->_effects[1].DamageMultiplier = 0.0f; + sinfo->_effects[1].BonusMultiplier = 0.0f; + //47) END IMPALE VISUAL + + //48) CARRION BEETLES + spellId = SPELL_CARRION_BEETLES; //53520 + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + + sinfo->SpellFamilyName = SPELLFAMILY_WARRIOR; + sinfo->PreventionType = SPELL_PREVENTION_TYPE_PACIFY; + sinfo->SpellLevel = 10; + sinfo->BaseLevel = 10; + sinfo->RecoveryTime = 6000; + sinfo->StartRecoveryCategory = 133; + sinfo->StartRecoveryTime = 1500; + sinfo->PowerType = POWER_MANA; + sinfo->ManaCost = 50 * 5; + sinfo->MaxAffectedTargets = 1; + sinfo->InterruptFlags = 0x1; + sinfo->ChannelInterruptFlags = 0x100C; + sinfo->RangeEntry = sSpellRangeStore.LookupEntry(4); //30 yds + sinfo->DurationEntry = sSpellDurationStore.LookupEntry(327); //500ms // (36); // 1000ms // (327); //500ms + sinfo->ExplicitTargetMask = TARGET_FLAG_CORPSE_ENEMY; + sinfo->Attributes |= SPELL_ATTR0_HIDDEN_CLIENTSIDE | SPELL_ATTR0_ABILITY; + sinfo->AttributesEx |= SPELL_ATTR1_CHANNEL_TRACK_TARGET; + sinfo->AttributesEx2 |= SPELL_ATTR2_CAN_TARGET_DEAD; + + sinfo->_effects[0].Effect = SPELL_EFFECT_DUMMY; + sinfo->_effects[0].ApplyAuraName = SPELL_AURA_NONE; + sinfo->_effects[0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_TARGET_ANY); + sinfo->_effects[0].TargetB = SpellImplicitTargetInfo(0); + sinfo->_effects[0].BasePoints = 1; + sinfo->_effects[0].Amplitude = 500; + sinfo->_effects[0].RealPointsPerLevel = 0.0f; + sinfo->_effects[0].DieSides = 0; + sinfo->_effects[0].DamageMultiplier = 0.0f; + sinfo->_effects[0].BonusMultiplier = 0.0f; + //48) END CARRION BEETLES + + //49) LOCUST SWARM + spellId = SPELL_LOCUST_SWARM; //28785 + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + + sinfo->SpellFamilyName = SPELLFAMILY_WARRIOR; + sinfo->PreventionType = SPELL_PREVENTION_TYPE_PACIFY; + sinfo->SpellLevel = 40; + sinfo->BaseLevel = 40; + sinfo->CategoryEntry = nullptr; + sinfo->RecoveryTime = 180000; + sinfo->CategoryRecoveryTime = 0; + sinfo->StartRecoveryCategory = 133; + sinfo->StartRecoveryTime = 1500; + sinfo->PowerType = POWER_MANA; + sinfo->ManaCost = 50 * 5; + sinfo->MaxAffectedTargets = 0; + sinfo->StackAmount = 0; + sinfo->InterruptFlags = 0x1; + sinfo->ChannelInterruptFlags = 0x100C; + sinfo->CastTimeEntry = sSpellCastTimesStore.LookupEntry(1); //0 + sinfo->RangeEntry = sSpellRangeStore.LookupEntry(4); //30 yds + sinfo->DurationEntry = sSpellDurationStore.LookupEntry(35); //4000ms + sinfo->ExplicitTargetMask = TARGET_FLAG_UNIT; + sinfo->Attributes |= SPELL_ATTR0_HIDDEN_CLIENTSIDE | SPELL_ATTR0_HIDE_IN_COMBAT_LOG; + sinfo->AttributesEx |= SPELL_ATTR1_CHANNELED_2 | SPELL_ATTR1_DONT_DISPLAY_IN_AURA_BAR | SPELL_ATTR1_NO_THREAT; + sinfo->AttributesEx3 |= SPELL_ATTR3_NO_INITIAL_AGGRO; + + sinfo->_effects[0].Effect = SPELL_EFFECT_APPLY_AURA; + sinfo->_effects[0].ApplyAuraName = SPELL_AURA_DUMMY; + sinfo->_effects[0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_CASTER); + sinfo->_effects[0].TargetB = SpellImplicitTargetInfo(0); + sinfo->_effects[0].TriggerSpell = 0; + sinfo->_effects[0].RadiusEntry = nullptr; + sinfo->_effects[0].MiscValue = 0; + sinfo->_effects[0].MiscValueB = 0; + sinfo->_effects[0].BasePoints = 1; + sinfo->_effects[0].Amplitude = 0; + sinfo->_effects[0].RealPointsPerLevel = 0.0f; + sinfo->_effects[0].DieSides = 0; + sinfo->_effects[0].DamageMultiplier = 0.0f; + sinfo->_effects[0].BonusMultiplier = 0.0f; + + for (uint8 i = EFFECT_1; i < MAX_SPELL_EFFECTS; ++i) + { + sinfo->_effects[i].Effect = SPELL_EFFECT_NONE; + sinfo->_effects[i].ApplyAuraName = SPELL_AURA_NONE; + sinfo->_effects[i].TargetA = SpellImplicitTargetInfo(0); + sinfo->_effects[i].TargetB = SpellImplicitTargetInfo(0); + sinfo->_effects[i].RadiusEntry = nullptr; + sinfo->_effects[i].MiscValue = 0; + sinfo->_effects[i].MiscValueB = 0; + sinfo->_effects[i].BasePoints = 0; + sinfo->_effects[i].Amplitude = 0; + sinfo->_effects[i].RealPointsPerLevel = 0.0f; + sinfo->_effects[i].DieSides = 0; + sinfo->_effects[i].DamageMultiplier = 0.0f; + sinfo->_effects[i].BonusMultiplier = 0.0f; + } + //49) END LOCUST SWARM + + //50) SOUL BITE + spellId = SPELL_SOUL_BITE; //11016 + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + + sinfo->SpellFamilyName = SPELLFAMILY_WARRIOR; + sinfo->PreventionType = SPELL_PREVENTION_TYPE_PACIFY; + sinfo->SchoolMask = SPELL_SCHOOL_MASK_SHADOW | SPELL_SCHOOL_MASK_ARCANE; + sinfo->SpellLevel = 40; + sinfo->BaseLevel = 40; + sinfo->CategoryEntry = nullptr; + sinfo->RecoveryTime = 0; + sinfo->CategoryRecoveryTime = 0; + sinfo->StartRecoveryCategory = 0; + sinfo->StartRecoveryTime = 0; + sinfo->PowerType = POWER_MANA; + sinfo->ManaCost = 0; + sinfo->MaxAffectedTargets = 0; + sinfo->StackAmount = 10; + sinfo->ChannelInterruptFlags = 0; + sinfo->CastTimeEntry = sSpellCastTimesStore.LookupEntry(1); //0 + sinfo->RangeEntry = sSpellRangeStore.LookupEntry(11); //15 yds + sinfo->DurationEntry = sSpellDurationStore.LookupEntry(568); // 1250ms // (36); //1000ms + sinfo->ExplicitTargetMask = TARGET_FLAG_UNIT; + sinfo->Attributes |= SPELL_ATTR0_HIDE_IN_COMBAT_LOG; + sinfo->AttributesEx |= SPELL_ATTR1_CANT_BE_REFLECTED | SPELL_ATTR1_CANT_BE_REDIRECTED; + sinfo->AttributesEx2 |= SPELL_ATTR2_CANT_CRIT; + sinfo->AttributesEx3 |= SPELL_ATTR3_NO_DONE_BONUS | SPELL_ATTR3_IGNORE_HIT_RESULT; + sinfo->AttributesEx4 |= SPELL_ATTR4_FIXED_DAMAGE; + sinfo->AttributesCu &= ~(SPELL_ATTR0_CU_CAN_CRIT); + + sinfo->_effects[0].Effect = SPELL_EFFECT_HEALTH_LEECH; + sinfo->_effects[0].ApplyAuraName = SPELL_AURA_NONE; + sinfo->_effects[0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_TARGET_ENEMY); + sinfo->_effects[0].TargetB = SpellImplicitTargetInfo(0); + sinfo->_effects[0].RadiusEntry = nullptr; + sinfo->_effects[0].MiscValue = 0; + sinfo->_effects[0].MiscValueB = 0; + sinfo->_effects[0].BasePoints = 10; + sinfo->_effects[0].Amplitude = 0; + sinfo->_effects[0].RealPointsPerLevel = 0.0f; + sinfo->_effects[0].DieSides = 25; + sinfo->_effects[0].DamageMultiplier = 0.0f; + sinfo->_effects[0].ValueMultiplier = 0.0f; + sinfo->_effects[0].BonusMultiplier = 0.0f; + + sinfo->_effects[1].Effect = SPELL_EFFECT_APPLY_AURA; + sinfo->_effects[1].ApplyAuraName = SPELL_AURA_MOD_PACIFY_SILENCE; + sinfo->_effects[1].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_TARGET_ENEMY); + sinfo->_effects[1].TargetB = SpellImplicitTargetInfo(0); + sinfo->_effects[1].RadiusEntry = nullptr; + sinfo->_effects[1].MiscValue = 0; + sinfo->_effects[1].MiscValueB = 0; + sinfo->_effects[1].BasePoints = 1; + sinfo->_effects[1].Amplitude = 0; + sinfo->_effects[1].RealPointsPerLevel = 0.0f; + sinfo->_effects[1].DieSides = 0; + sinfo->_effects[1].DamageMultiplier = 0.0f; + sinfo->_effects[1].ValueMultiplier = 0.0f; + sinfo->_effects[1].BonusMultiplier = 0.0f; + + sinfo->_effects[2].Effect = SPELL_EFFECT_APPLY_AURA; + sinfo->_effects[2].ApplyAuraName = SPELL_AURA_MOD_DECREASE_SPEED; + sinfo->_effects[2].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_TARGET_ENEMY); + sinfo->_effects[2].TargetB = SpellImplicitTargetInfo(0); + sinfo->_effects[2].Mechanic = MECHANIC_SNARE; + sinfo->_effects[2].RadiusEntry = nullptr; + sinfo->_effects[2].MiscValue = 0; + sinfo->_effects[2].MiscValueB = 0; + sinfo->_effects[2].BasePoints = -3; + sinfo->_effects[2].Amplitude = 0; + sinfo->_effects[2].RealPointsPerLevel = 0.0f; + sinfo->_effects[2].DieSides = 0; + sinfo->_effects[2].DamageMultiplier = 0.0f; + sinfo->_effects[2].ValueMultiplier = 0.0f; + sinfo->_effects[2].BonusMultiplier = 0.0f; + //50) END SOUL BITE + + //51) ENERGIZE VISUAL + spellId = SPELL_ENERGIZE_VISUAL; //59198 + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + + sinfo->SchoolMask = SPELL_SCHOOL_MASK_SHADOW; + sinfo->SpellLevel = 1; + sinfo->BaseLevel = 1; + + sinfo->_effects[0].Effect = SPELL_EFFECT_DUMMY; + sinfo->_effects[0].BasePoints = 0; + sinfo->_effects[0].DieSides = 0; + //51) END ENERGIZE VISUAL + + //XX) FIXES + spellId = 48155; // Mind Flay (Rank 8) + botSpellInfoOverrides.insert({ spellId, *sSpellMgr->GetSpellInfo(spellId) }); + sinfo = &botSpellInfoOverrides.at(spellId); + sinfo->InterruptFlags &= SPELL_INTERRUPT_FLAG_MOVEMENT; + + for (auto& p : botSpellInfoOverrides) + { + for (auto& eff : p.second._effects) + { + eff.OverrideSpellInfo(&p.second); + } + } + + TC_LOG_INFO("server.loading", ">> Bot spellInfo overrides generated for {} spells", uint32(botSpellInfoOverrides.size())); + + GenerateBotCustomSpellProcs(); +} diff --git a/src/server/game/AI/NpcBots/botspell.h b/src/server/game/AI/NpcBots/botspell.h new file mode 100644 index 000000000..80ebc0647 --- /dev/null +++ b/src/server/game/AI/NpcBots/botspell.h @@ -0,0 +1,317 @@ +#ifndef _BOTSPELL_H +#define _BOTSPELL_H + +#include "Define.h" + +/* +NpcBot System by Trickerer (onlysuffering@gmail.com) +Original patch from: LordPsyan https://bitbucket.org/lordpsyan/trinitycore-patches/src/3b8b9072280e/Individual/11185-BOTS-NPCBots.patch +*/ + +class SpellInfo; +struct SpellProcEntry; + +enum BotSpells : uint32 +{ +//COMMON SPELLS + PVPTRINKET = 42292,//PvP Trinket no CD + BERSERK = 46587,//68378,//900%/150% + MODEL_TRANSITION = 24753,//"Trick" cannot cast or attack + SUMMONING_DISORIENTATION = 32752, + ACTIVATE_SPEC = 63645,//Activate Primary Spec + WANDERER_HEARTHSTONE = 54318,//"Hearthsone" no CD + SUMMONING_STONE_EFFECT = 59782,//Cast time 5s + Channeled 2m + SHOOT_WAND = 5019, +///Portals + PORTAL_STORMWIND = 10059, + PORTAL_IRONFORGE = 11416, + PORTAL_DARNASSUS = 11419, + PORTAL_EXODAR = 32266, + PORTAL_SHATTRATH_A = 33691, + PORTAL_THERAMORE = 49360, + PORTAL_ORGRIMMAR = 11417, + PORTAL_UNDERCITY = 11418, + PORTAL_THUNDERBLUFF = 11420, + PORTAL_SILVERMOON = 32267, + PORTAL_SHATTRATH_H = 35717, + PORTAL_STONARD = 49361, + PORTAL_DALARAN = 53142, +///Passives + DAMAGE_REDUCTION = 68066,//Vigilance, Blessing of Sanctuary, etc. +///Passives for Pets + DAMAGEDONE_PASSIVE = 30147,//for custom value, Tamed Pet Passive (DND) physical at 0, magic at 1 + DAMAGETAKEN_PASSIVE = 35697,//for custom value, Pet Passive (DND), single effect (aura 87 at 0) + SPELLDAMAGE_PASSIVE = 43922,//for custom value, Increase Spell Dam 473, single effect (aura 13 at 0) + SPELLPENETRATION_PASSIVE = 25975,//for custom value, Spell Penetration 10, single effect (aura 123 at 0) + SPELLHASTE_PASSIVE = 44400,//for custom value, Netherwind Presence rank 1, single effect (aura 65 at 0) + CRITBONUS_PASSIVE = 35695,//for custom value, Pet Passive (DND), spell at 0, physical at 1 +///Racials + RACIAL_EVERY_MAN_FOR_HIMSELF = 59752,//pvp trinket effect, instant, 2 min cd + RACIAL_BLOOD_FURY_WARLOCK = 33702,//effect varies, 15 sec, 2 min cd + RACIAL_BLOOD_FURY_SHAMAN = 33697, + RACIAL_BLOOD_FURY_OTHERS = 20572, + RACIAL_STONEFORM = 20594,//dispell disease, poison, bleed, instant, 2 min cd + //RACIAL_FIND_TREASURE = 2481, + RACIAL_SHADOWMELD = 58984,//stealth, -threat, instant, 2 min cd + RACIAL_WILL_OF_THE_FORSAKEN = 7744,//dispel charm/fear/sleep, instant, 2 min cd, 45 sec category cd + RACIAL_WARSTOMP = 20549,//2sec stun, casttime 500, 8yd, 2 min cd + RACIAL_ESCAPE_ARTIST = 20589,//dispel snare/root, instant, 1 min 45 sec cd + RACIAL_BERSERKING = 26297,//haste all 20%, isntant, 3 min cd + RACIAL_ARCANE_TORRENT_DEATHKNIGHT = 50613,//2sec AoE silence + energize, instant, 2 min cd + RACIAL_ARCANE_TORRENT_ROGUE = 25046, + RACIAL_ARCANE_TORRENT_OTHERS = 28730, + RACIAL_GIFT_OF_NAARU_WARRIOR = 28880,//Hot over 15 sec, instant, 2 min cd + RACIAL_GIFT_OF_NAARU_PALADIN = 59542, + RACIAL_GIFT_OF_NAARU_HUNTER = 59543, + RACIAL_GIFT_OF_NAARU_PRIEST = 59544, + RACIAL_GIFT_OF_NAARU_DEATHKNIGHT = 59545, + RACIAL_GIFT_OF_NAARU_SHAMAN = 59547, + RACIAL_GIFT_OF_NAARU_MAGE = 59548, +//ADVANCED + //SPELL_SUMMON_FELBLAZE_PREVISUAL = 46350,//green splash impact head/torso + //HONORLESS_TARGET = 2479, + COSMETIC_TELEPORT_EFFECT = 52096,//visual instant cast omni + COSMETIC_RESURRECTION = 58854,//visual instant cast self (castable while dead, hidden) + SUMMON_DEMON_VISUAL = 6657,//SUMMON_SERPENT_MESSENGER + CALL_PET_VISUAL = 30416,//QUEST_WOOD_CLEANSE_EFFECT + SPELL_VERTEX_COLOR_BLACK = 39662,//black color model full + SPELL_VERTEX_COLOR_GREY = 43355,//grey color model full + SPELL_BLACK_HOLE_VISUAL_2 = 46235,//blackened+smoke trail med +////CUSTOM SPELLS - UNUSED IN CODE AND DB + //common + //modify + SPELL_TRIGGERED_HEAL = 25155,//hidden + SPELL_TELEPORT_LOCAL = 7794,//Teleport, no log + SPELL_NULLIFY_POISON = 550,//To convert into passive for bots + //unmodify + SPELL_ATTACK_MELEE_1H = 42880, + SPELL_TRIGGERED_ENERGIZE = 60628,//hidden + SPELL_BRIEF_STUN = 41421,//1sec stun +//BLADEMASTER + //SPELLS + //unmodify + //SPELL_DEATH_GRIP_JUMP = 49575, +/**/SPELL_CRITICAL_STRIKE = 1132, + SPELL_BURNING_BLADE_BLADEMASTER = 32281,//horde flag visual + SPELL_STUN_FREEZE_ANIM = 59123,//stun forever, full stop + //modify + SPELL_TRANSPARENCY_50 = 44816, +/**/SPELL_NETHERWALK = 31599, +/**/SPELL_MIRROR_IMAGE_BM = 69936,//blank spell + SPELL_COMBAT_SPECIAL_2H_ATTACK = 44079,//animation only +//SPHYNX + //modify + SPELL_SHADOW_BOLT1 = 16408, + SPELL_SHADOW_BLAST = 38085, + //SPELL_SHADOW_BLAST_SPLASH = 38205, + SPELL_ATTACK_MELEE_RANDOM = 42902, + SHADOWFURY_VISUAL = 47444,//59912, + SPELL_DEVOUR_MAGIC = 17012,//used by Spellmaw but no matter (this spell does not work as intended) + SPELL_DRAIN_MANA = 25755, + SPELL_REPLENISH_MANA = 33394,//5406, + SPELL_REPLENISH_HEALTH = 34756,//regenerating aura + //unmodify + SPELL_DEVOUR_MAGIC_CASTER_IMPACT = 50527, + SPELL_DEVOUR_MAGIC_BEAM = 54393, +//ARCHMAGE + //modify + SPELL_BRILLIANCE_AURA = 1234, + SPELL_FIREBALL = 9488, + SPELL_BLIZZARD = 15783, + SPELL_SUMMON_WATER_ELEMENTAL = 35593, + SPELL_WATERBOLT = 72898, + //unmodify +//DREADLORD + //modify + SPELL_VAMPIRIC_AURA = 20810, + SPELL_SLEEP = 20663, + SPELL_CARRION_SWARM = 34240, + SPELL_INFERNO = 12740, //summon infernal servant + SPELL_INFERNO_METEOR_VISUAL = 5739, //meteor strike infernal + //unmodify + SPELL_INFERNO_EFFECT = 22703, //stun, damage (warlock spell) + //SPELL_INFERNO_IMPACT_EXPLOSION = 00000, //visual +//SPELLBREAKER + //modify + SPELL_STEAL_MAGIC = 30036, //used by Ethereal Spellfilcher + SPELL_FEEDBACK = 32897, + SPELL_STEAL_MAGIC_VISUAL = 11084, // "Shock" + //unmodify +//DARK RANGER + //modify + SPELL_BLACK_ARROW = 20733, //supposed to be used by Dark Ranger Clea + SPELL_DRAIN_LIFE = 17238, //used by Maleki the Palid, supposed to be by Shadow Adept (31145) + SPELL_SILENCE = 29943, + //SPELL_CHARM = 11111, // + //unmodify +//NECROMANCER + //modify + SPELL_SHADOW_BOLT2 = 17509, + SPELL_RAISE_DEAD = 34011, + SPELL_UNHOLY_FRENZY = 52499, + SPELL_CRIPPLE = 50379, + SPELL_CORPSE_EXPLOSION = 61614, + //SPELL_BONE_SHIELD = 0,//27688, //NIY //NO VIABLE SPELLS + //for Attract faction reaction must be adjusted at Object.cpp::GetFactionReactionTo(L2831) + //SPELL_BLOOD_CURSE = 29933, //NIY for Attract //NO VIABLE SPELLS + //unmodify + CORPSE_EXPLOSION_VISUAL = 60081, //explosion +//NAGA SEA WITCH + //modify + SPELL_FORKED_LIGHTNING = 63541, + SPELL_FORKED_LIGHTNING_EFFECT = 50900, // "Lightning Shock" + SPELL_FROST_ARROW = 38942, + SPELL_FROST_ARROW_EFFECT = 56095, + SPELL_MANA_SHIELD = 35064, + SPELL_TORNADO = 34695, + SPELL_TORNADO_EFFECT = 21990, // stun, -resistances + SPELL_TORNADO_EFFECT2 = 34683, // aoe damage + SPELL_TORNADO_EFFECT3 = 39261, + SPELL_SHOOT_BOW = 41188, + //unmodify + SPELL_TORNADO_LIGHTNING_VISUAL = 45869, //periodic, 1 sec +//CRYPT LORD + //modify + //impale cd 9 + //locust dur 30 cd 180 + SPELL_IMPALE = 53458, + SPELL_IMPALE_DAMAGE = 53454, + SPELL_IMPALE_VISUAL = 59446, + SPELL_CARRION_BEETLES = 53520, + SPELL_LOCUST_SWARM = 28785, + SPELL_SOUL_BITE = 11016, //special - pet + SPELL_ENERGIZE_VISUAL = 59198, + SPELL_BURROW = 68394, //special - pet (NYI) //SPELL_EFFECT_FORCE_DESELECT + //unmodify + SPELL_SPIKED_CARAPACE_DAMAGE = 14104, + +//OTHER + BASE_MANA_SPHYNX = 400 * 5, + BASE_MANA_SPELLBREAKER = 250 * 5, + BASE_MANA_NECROMANCER = 400 * 5, + //base mana at 10 + BASE_MANA_10_BM = 540 * 5, + BASE_MANA_10_ARCHMAGE = 705 * 5, + BASE_MANA_10_DREADLORD = 600 * 5, + BASE_MANA_10_DARK_RANGER = 570 * 5, + BASE_MANA_10_SEA_WITCH = 735 * 5, + BASE_MANA_10_CRYPT_LORD = 420 * 5, + //base mana at 1 + BASE_MANA_1_BM = 240 * 5, + BASE_MANA_1_ARCHMAGE = 285 * 5, + BASE_MANA_1_DREADLORD = 270 * 5, + BASE_MANA_1_DARK_RANGER = 225 * 5, + BASE_MANA_1_SEA_WITCH = 330 * 5, + BASE_MANA_1_CRYPT_LORD = 210 * 5, +}; + +enum BotMountSpells : uint32 +{ + //By game events + REINDEER = 25859, + REINDEER_FLY = 44827, + //AQ40 + QIRAJI_BATTLE_TANK_1 = 25953, + QIRAJI_BATTLE_TANK_2 = 26054, + QIRAJI_BATTLE_TANK_3 = 26055, + QIRAJI_BATTLE_TANK_4 = 26056, + //By class + BOT_DARK_RANGER_MOUNT = 17481, // Deathcharger's Reins + BOT_BE_PALLY_FAST_MOUNT = 34767, + BOT_BE_PALLY_MOUNT = 34769, + BOT_ALLI_PALLY_FAST_MOUNT = 23214, + BOT_ALLI_PALLY_MOUNT = 13819, + BOT_DEATH_KNIGHT_MOUNT = 48778, + BOT_WARLOCK_FAST_MOUNT = 23161, + BOT_WARLOCK_MOUNT = 5784, + //By race + BOT_MOUNT_HUMAN_60_1 = 458, + BOT_MOUNT_HUMAN_60_2 = 468, + BOT_MOUNT_HUMAN_60_3 = 470, + BOT_MOUNT_ORC_60_1 = 459, + BOT_MOUNT_ORC_60_2 = 578, + BOT_MOUNT_ORC_60_3 = 579, + BOT_MOUNT_DWARF_60_1 = 6777, + BOT_MOUNT_DWARF_60_2 = 6896, + BOT_MOUNT_DWARF_60_3 = 6897, + BOT_MOUNT_NIGHTELF_60_1 = 8394, + BOT_MOUNT_NIGHTELF_60_2 = 10787, + BOT_MOUNT_NIGHTELF_60_3 = 10789, + BOT_MOUNT_FORSAKEN_60_1 = 8980, + BOT_MOUNT_FORSAKEN_60_2 = 17462, + BOT_MOUNT_FORSAKEN_60_3 = 17463, + BOT_MOUNT_TAUREN_60_1 = 18363, + BOT_MOUNT_TAUREN_60_2 = 18989, + BOT_MOUNT_TAUREN_60_3 = 18990, + BOT_MOUNT_GNOME_60_1 = 10873, + BOT_MOUNT_GNOME_60_2 = 10969, + BOT_MOUNT_GNOME_60_3 = 15780, + BOT_MOUNT_TROLL_60_1 = 8395, + BOT_MOUNT_TROLL_60_2 = 10795, + BOT_MOUNT_TROLL_60_3 = 10796, + BOT_MOUNT_BLOODELF_60_1 = 34795, + BOT_MOUNT_BLOODELF_60_2 = 35018, + BOT_MOUNT_BLOODELF_60_3 = 35020, + BOT_MOUNT_DRAENEI_60_1 = 34406, + BOT_MOUNT_DRAENEI_60_2 = 35710, + BOT_MOUNT_DRAENEI_60_3 = 35711, + BOT_MOUNT_HUMAN_100_1 = 23227, + BOT_MOUNT_HUMAN_100_2 = 23228, + BOT_MOUNT_HUMAN_100_3 = 23229, + BOT_MOUNT_ORC_100_1 = 23250, + BOT_MOUNT_ORC_100_2 = 23251, + BOT_MOUNT_ORC_100_3 = 23252, + BOT_MOUNT_DWARF_100_1 = 23238, + BOT_MOUNT_DWARF_100_2 = 23239, + BOT_MOUNT_DWARF_100_3 = 23240, + BOT_MOUNT_NIGHTELF_100_1 = 23219, + BOT_MOUNT_NIGHTELF_100_2 = 23220, + BOT_MOUNT_NIGHTELF_100_3 = 23221, + BOT_MOUNT_FORSAKEN_100_1 = 17465, + BOT_MOUNT_FORSAKEN_100_2 = 22722, + BOT_MOUNT_FORSAKEN_100_3 = 23246, + BOT_MOUNT_TAUREN_100_1 = 23247, + BOT_MOUNT_TAUREN_100_2 = 23248, + BOT_MOUNT_TAUREN_100_3 = 23249, + BOT_MOUNT_GNOME_100_1 = 23222, + BOT_MOUNT_GNOME_100_2 = 23223, + BOT_MOUNT_GNOME_100_3 = 23225, + BOT_MOUNT_TROLL_100_1 = 23241, + BOT_MOUNT_TROLL_100_2 = 23242, + BOT_MOUNT_TROLL_100_3 = 23243, + BOT_MOUNT_BLOODELF_100_1 = 35025, + BOT_MOUNT_BLOODELF_100_2 = 35027, + BOT_MOUNT_BLOODELF_100_3 = 46628, + BOT_MOUNT_DRAENEI_100_1 = 35712, + BOT_MOUNT_DRAENEI_100_2 = 35713, + BOT_MOUNT_DRAENEI_100_3 = 35714, + //By Team (flyers) + BOT_MOUNT_FLY_ALLIANCE_150_1 = 32235, + BOT_MOUNT_FLY_ALLIANCE_150_2 = 32239, + BOT_MOUNT_FLY_ALLIANCE_150_3 = 32240, + BOT_MOUNT_FLY_HORDE_150_1 = 32243, + BOT_MOUNT_FLY_HORDE_150_2 = 32244, + BOT_MOUNT_FLY_HORDE_150_3 = 32245, + BOT_MOUNT_FLY_ALLIANCE_280_1 = 32242, + BOT_MOUNT_FLY_ALLIANCE_280_2 = 32289, + BOT_MOUNT_FLY_ALLIANCE_280_3 = 32290, + BOT_MOUNT_FLY_HORDE_280_1 = 32246, + BOT_MOUNT_FLY_HORDE_280_2 = 32295, + BOT_MOUNT_FLY_HORDE_280_3 = 32296 +}; +constexpr std::size_t NUM_MOUNTS_PER_SPEED = 3; + +enum BotItemUseSpellTargeting : uint8 +{ + BOT_ITEM_USE_SPELL_TARGET_NONE = 0, + BOT_ITEM_USE_SPELL_TARGET_SELF = 1, + BOT_ITEM_USE_SPELL_TARGET_ATTACKTARGET = 2, + BOT_ITEM_USE_SPELL_TARGET_ALLY = 3 +}; + +SpellInfo const* GetBotSpellInfoOverride(uint32 spellId); +SpellInfo const* AssertBotSpellInfoOverride(uint32 spellId); +SpellProcEntry const* GetBotSpellProceEntryOverride(uint32 spellId); +void GenerateBotCustomSpells(); + +#endif //_BOTSPELL_H diff --git a/src/server/game/AI/NpcBots/bottext.h b/src/server/game/AI/NpcBots/bottext.h new file mode 100644 index 000000000..7e9737dd9 --- /dev/null +++ b/src/server/game/AI/NpcBots/bottext.h @@ -0,0 +1,426 @@ +#ifndef BOTTEXT_H +#define BOTTEXT_H + +#include "Define.h" + +enum BotTexts : uint32 +{ + GOSSIP_NORMAL_SERVE_MASTER = 70001,//"I live only to serve the master." + GOSSIP_GREET_NEED_SMTH = 70002,//"You need something?" + GOSSIP_GREET_MURDER = 70003,//"Mortals... usually I kill wretches like you at sight" + GOSSIP_GREET_CUSTOM_SPHYNX = 70004, + GOSSIP_NORMAL_CUSTOM_SPHYNX = 70005, + GOSSIP_GREET_CUSTOM_DREADLORD = 70006, + GOSSIP_NORMAL_CUSTOM_DREADLORD = 70007, + GOSSIP_GREET_CUSTOM_DARKRANGER = 70008, + GOSSIP_NORMAL_CUSTOM_DARKRANGER = 70009, + GOSSIP_GREET_CUSTOM_SEAWITCH = 70010, + GOSSIP_NORMAL_CUSTOM_SEAWITCH = 70011, + GOSSIP_GREET_CUSTOM_CRYPTLORD = 70012, + GOSSIP_NORMAL_CUSTOM_CRYPTLORD = 70013, + //70012-70100 reserved for bot gossip texts (not selectable) + GOSSIP_CLASSDESC_BM = 70101, + GOSSIP_CLASSDESC_SPHYNX = 70102, + GOSSIP_CLASSDESC_ARCHMAGE = 70103, + GOSSIP_CLASSDESC_DREADLORD = 70104, + GOSSIP_CLASSDESC_SPELLBREAKER = 70105, + GOSSIP_CLASSDESC_DARKRANGER = 70106, + GOSSIP_CLASSDESC_NECROMANCER = 70107, + GOSSIP_CLASSDESC_SEAWITCH = 70108, + GOSSIP_CLASSDESC_CRYPTLORD = 70109, + //70109-70200 reserved for bot class descriptions gossip texts (not selectable) + GOSSIP_BOTGIVER_GREET = 70201, + GOSSIP_BOTGIVER_HIRE = 70202, + GOSSIP_BOTGIVER_HIRE_CLASS = 70203, + GOSSIP_BOTGIVER_HIRE_EMPTY = 70204, + //70205-70299 reserved for botgiver gossip texts (not selectable) + BOT_TEXT_DIE = 70300, //"Die!" + BOT_TEXT_REZZING_YOU = 70301, //"Rezzing You" + BOT_TEXT_REZZING_ = 70302, //"Rezzing " + BOT_TEXT_YOUR_BOT = 70303, //"your bot" + BOT_TEXT__S_BOT = 70304, //"'s bot" + BOT_TEXT_CANT_CONJURE_WATER_YET = 70305, //"I can't conjure water yet" + BOT_TEXT_CANT_CONJURE_FOOD_YET = 70306, //"I can't conjure food yet" + BOT_TEXT_CANT_RIGHT_NOW = 70307, //"I can't do it right now" + BOT_TEXT_HERE_YOU_GO = 70308, //"Here you go..." + BOT_TEXT_DISABLED = 70309, //"Disabled" + BOT_TEXT_NOT_READY_YET = 70310, //"Not ready yet" + BOT_TEXT_INVALID_OBJECT_TYPE = 70311, //"Invalid object type" + BOT_TEXT_FAILED = 70312, //"Failed" + BOT_TEXT_DONE = 70313, //"Done" + BOT_TEXT_NOT_SHAPESHIFTED = 70314, //"I am not shapeshifted" + BOT_TEXT_NO_HEALTHSTONE = 70315, //"I don't have a healthstone" + BOT_TEXT_CANT_CREATE_HEALTHSTONE = 70316, //"I can't create healthstones yet!" + BOT_TEXT_NO_LOCKPICKING = 70317, //"WTF I don't have lockpicking!" + BOT_TEXT_SKILL_LEVEL_TOO_LOW = 70318, //"My skill level in not high enough" + BOT_TEXT_CHANGING_MY_SPEC_TO_ = 70319, //"Changing my spec to " + BOT_TEXT_SPEC_ARMS = 70320, //"Arms" + BOT_TEXT_SPEC_FURY = 70321, //"Fury" + BOT_TEXT_SPEC_PROTECTION = 70322, //"Protection" + BOT_TEXT_SPEC_RETRIBUTION = 70323, //"Retribution" + BOT_TEXT_SPEC_BEASTMASTERY = 70324, //"Beast Mastery" + BOT_TEXT_SPEC_MARKSMANSHIP = 70325, //"Marksmanship" + BOT_TEXT_SPEC_SURVIVAL = 70326, //"Survival" + BOT_TEXT_SPEC_ASSASINATION = 70327, //"Assassination" + BOT_TEXT_SPEC_COMBAT = 70328, //"Combat" + BOT_TEXT_SPEC_SUBTLETY = 70329, //"Subtlety" + BOT_TEXT_SPEC_DISCIPLINE = 70330, //"Discipline" + BOT_TEXT_SPEC_HOLY = 70331, //"Holy" + BOT_TEXT_SPEC_SHADOW = 70332, //"Shadow" + BOT_TEXT_SPEC_BLOOD = 70333, //"Blood" + BOT_TEXT_SPEC_FROST = 70334, //"Frost" + BOT_TEXT_SPEC_UNHOLY = 70335, //"Unholy" + BOT_TEXT_SPEC_ELEMENTAL = 70336, //"Elemental" + BOT_TEXT_SPEC_ENHANCEMENT = 70337, //"Enhancement" + BOT_TEXT_SPEC_RESTORATION = 70338, //"Restoration" + BOT_TEXT_SPEC_ARCANE = 70339, //"Arcane" + BOT_TEXT_SPEC_FIRE = 70340, //"Fire" + BOT_TEXT_SPEC_AFFLICTION = 70341, //"Affliction" + BOT_TEXT_SPEC_DEMONOLOGY = 70342, //"Demonology" + BOT_TEXT_SPEC_DESTRUCTION = 70343, //"Destruction" + BOT_TEXT_SPEC_BALANCE = 70344, //"Balance" + BOT_TEXT_SPEC_FERAL = 70345, //"Feral Combat" + BOT_TEXT_SPEC_UNKNOWN = 70346, //"Unknown" + BOT_TEXT_HIREDENY_DK = 70347, //"Go away, weakling" + BOT_TEXT_HIREDENY_SPHYNX = 70348, //" is not convinced" + BOT_TEXT_HIREDENY_ARCHMAGE = 70349, //"I am not going to waste my time on just anything" + BOT_TEXT_HIREDENY_DREADLORD = 70350, //NIY + BOT_TEXT_HIREDENY_SPELLBREAKER = 70351, //NIY + BOT_TEXT_HIREDENY_DARKRANGER = 70352, //NIY + BOT_TEXT_HIRE_SUCCESS = 70353, //"I am ready" + BOT_TEXT_HIREDENY_MY_MASTER_IS_ = 70354, //"Go away. I serve my master " + BOT_TEXT_UNKNOWN = 70355, //"unknown" + BOT_TEXT__ON_YOU = 70356, //" on You!" + BOT_TEXT__ON_MYSELF = 70357, //" on myself!" + BOT_TEXT__ON_ = 70358, //" on " + BOT_TEXT__USED = 70359, //" used!" + BOT_TEXT_BOT_TANK = 70360, //"bot tank" + BOT_TEXT_CLASS = 70361, //"class" + BOT_TEXT_PLAYER = 70362, //"player" + BOT_TEXT_MASTER = 70363, //"master" + BOT_TEXT_NONE = 70364, //"none" + BOT_TEXT_RANK = 70365, //"Rank" + BOT_TEXT_TALENT = 70366, //"talent" + BOT_TEXT_PASSIVE = 70367, //"passive" + BOT_TEXT_HIDDEN = 70368, //"hidden" + BOT_TEXT_KNOWN = 70369, //"known" + BOT_TEXT_ABILITY = 70370, //"ability" + BOT_TEXT_STAT_STR = 70371, //"str" + BOT_TEXT_STAT_AGI = 70372, //"agi" + BOT_TEXT_STAT_STA = 70373, //"sta" + BOT_TEXT_STAT_INT = 70374, //"int" + BOT_TEXT_STAT_SPI = 70375, //"spi" + BOT_TEXT_STAT_UNK = 70376, //"unk stat" + BOT_TEXT_TOTAL = 70377, //"total" + BOT_TEXT_MELEE_AP = 70378, //"Melee AP" + BOT_TEXT_RANGED_AP = 70379, //"Ranged AP" + BOT_TEXT_ARMOR = 70380, //"armor" + BOT_TEXT_CRIT = 70381, //"crit" + BOT_TEXT_DEFENSE = 70382, //"defense" + BOT_TEXT_MISS = 70383, //"miss" + BOT_TEXT_DODGE = 70384, //"dodge" + BOT_TEXT_PARRY = 70385, //"parry" + BOT_TEXT_BLOCK = 70386, //"block" + BOT_TEXT_BLOCKVALUE = 70387, //"block value" + BOT_TEXT_DMG_TAKEN_MELEE = 70388, //"Damage taken melee" + BOT_TEXT_DMG_TAKEN_SPELL = 70389, //"Damage taken spell" + BOT_TEXT_DMG_RANGE_MAINHAND = 70390, //"Damage range mainhand" + BOT_TEXT_DMG_MULT_MAINHAND = 70391, //"Damage mult mainhand" + BOT_TEXT_ATTACK_TIME_MAINHAND = 70392, //"Attack time mainhand" + BOT_TEXT_DMG_RANGE_OFFHAND = 70393, //"Damage range offhand" + BOT_TEXT_DMG_MULT_OFFHAND = 70394, //"Damage mult offhand" + BOT_TEXT_ATTACK_TIME_OFFHAND = 70395, //"Attack time offhand" + BOT_TEXT_DMG_RANGE_RANGED = 70396, //"Damage range ranged" + BOT_TEXT_DMG_MULT_RANGED = 70397, //"Damage mult ranged" + BOT_TEXT_ATTACK_TIME_RANGED = 70398, //"Attack time ranged" + BOT_TEXT_MIN = 70399, //"min" + BOT_TEXT_MAX = 70400, //"max" + BOT_TEXT_DPS = 70401, //"DPS" + BOT_TEXT_BASE_HP = 70402, //"base hp" + BOT_TEXT_TOTAL_HP = 70403, //"total hp" + BOT_TEXT_BASE_MP = 70404, //"base mana" + BOT_TEXT_TOTAL_MP = 70405, //"total mana" + BOT_TEXT_CURR_MP = 70406, //"current mana" + BOT_TEXT_SPELLPOWER = 70407, //"spell power" + BOT_TEXT_REGEN_HP = 70408, //"health regen_5 bonus" + BOT_TEXT_REGEN_MP_CAST = 70409, //"mana regen_5 no cast" + BOT_TEXT_REGEN_MP_NOCAST = 70410, //"mana regen_5 casting" + BOT_TEXT_HASTE = 70411, //"haste" + BOT_TEXT_HIT = 70412, //"hit" + BOT_TEXT_EXPERTISE = 70413, //"expertise" + BOT_TEXT_ARMOR_PEN = 70414, //"armor penetration" + BOT_TEXT_SPELL_PEN = 70415, //"spell penetration" + BOT_TEXT_PCT = 70416, //"pct" + BOT_TEXT_HOLY = 70417, //"holy" + BOT_TEXT_FIRE = 70418, //"fire" + BOT_TEXT_NATURE = 70419, //"nature" + BOT_TEXT_FROST = 70420, //"frost" + BOT_TEXT_SHADOW = 70421, //"shadow" + BOT_TEXT_ARCANE = 70422, //"arcane" + BOT_TEXT_RESISTANCE = 70423, //"Resistance" + BOT_TEXT_COMMAND_STATES = 70424, //"Command states" + BOT_TEXT_COMMAND_FOLLOW = 70425, //"Follow" + BOT_TEXT_COMMAND_ATTACK = 70426, //"Attack" + BOT_TEXT_COMMAND_STAY = 70427, //"Stay" + BOT_TEXT_COMMAND_RESET = 70428, //"Reset" + BOT_TEXT_COMMAND_FULLSTOP = 70429, //"FullStop" + BOT_TEXT_FOLLOW_DISTANCE = 70430, //"Follow distance" + BOT_TEXT_SPEC = 70431, //"Spec" + BOT_TEXT_BOT_ROLEMASK_MAIN = 70432, //"Bot roles mask main" + BOT_TEXT_BOT_ROLEMASK_GATHERING = 70433, //"Bot roles mask gathering" + BOT_TEXT_PVP_KILLS = 70434, //"PvP kills" + BOT_TEXT_PLAYERS = 70435, //"players" + BOT_TEXT_DIED_ = 70436, //"Died " + BOT_TEXT__TIMES = 70437, //" times" + BOT_TEXT_BOT_TICKLED = 70438, //"%s (bot) calms down" + BOT_TEXT_DEBUG = 70439, //"" + BOT_TEXT_HIREWARN_SPHYNX_1 = 70440, //"Are you sure you want to risk drawing " + BOT_TEXT_HIREWARN_SPHYNX_2 = 70441, //"'s attention?" + BOT_TEXT_HIREOPTION_SPHYNX = 70442, //"" + BOT_TEXT_HIREWARN_DREADLORD = 70443, //"Do you want to entice " + BOT_TEXT_HIREOPTION_DREADLORD = 70444, //"" + BOT_TEXT_HIREWARN_DEFAULT = 70445, //"Do you wish to hire " + BOT_TEXT_HIREOPTION_DEFAULT = 70446, //"" + BOT_TEXT_MANAGE_EQUIPMENT = 70447, //"Manage equipment..." + BOT_TEXT_MANAGE_ROLES = 70448, //"Manage roles..." + BOT_TEXT_MANAGE_FORMATION = 70449, //"Manage formation..." + BOT_TEXT_MANAGE_ABILITIES = 70450, //"Manage abilities..." + BOT_TEXT_MANAGE_TALENTS = 70451, //"Manage talents..." + BOT_TEXT_GIVE_CONSUMABLE = 70452, //"Give consumable..." + BOT_TEXT_CREATE_GROUP = 70453, //"" + BOT_TEXT_CREATE_GROUP_ALL = 70454, //"" + BOT_TEXT_ADD_TO_GROUP = 70455, //"" + BOT_TEXT_ADD_TO_GROUP_ALL = 70456, //"" + BOT_TEXT_REMOVE_FROM_GROUP = 70457, //"" + BOT_TEXT_FOLLOW_ME = 70458, //"Follow me" + BOT_TEXT_HOLD_POSITION = 70459, //"Hold your position" + BOT_TEXT_STAY_HERE = 70460, //"Stay here and don't do anything" + BOT_TEXT_MAGE_FOOD = 70461, //"I need food" + BOT_TEXT_MAGE_DRINK = 70462, //"I need water" + BOT_TEXT_MAGE_TABLE = 70463, //"I need a refreshment table" + BOT_TEXT_ROGUE_PICKLOCK = 70464, //"Help me pick a lock" + BOT_TEXT_WARLOCK_HEALTHSTONE = 70465, //"I need your your healthstone" + BOT_TEXT_WARLOCK_SOULWELL = 70466, //"I need a soulwell" + BOT_TEXT_ROGUE_POISON_REFRESH = 70467, //"I need you to refresh poisons" + BOT_TEXT_ROGUE_POISON_MH = 70468, //"" + BOT_TEXT_ROGUE_POISON_OH = 70469, //"" + BOT_TEXT_SHAMAN_ENCH_REFRESH = 70470, //"I need you to refresh enchants" + BOT_TEXT_SHAMAN_ENCH_MH = 70471, //"" + BOT_TEXT_SHAMAN_ENCH_OH = 70472, //"" + BOT_TEXT_REMOVE_SHAPESHIFT = 70473, //"I need you to remove shapeshift" + BOT_TEXT_CHOOSE_PET_TYPE = 70474, //"" + BOT_TEXT_UR_DISMISSED = 70475, //"You are dismissed" + BOT_TEXT_ABANDON_WARN_1 = 70476, //"Are you going to abandon " + BOT_TEXT_ABANDON_WARN_2 = 70477, //"You may regret it..." + BOT_TEXT_PULL_URSELF = 70478, //"Pull yourself together, damnit" + BOT_TEXT_STUDY_CREATURE = 70479, //"" + BOT_TEXT_NEVERMIND = 70480, //"Nevermind" + BOT_TEXT_DISTANCE_SHORT = 70481, //"dist" + BOT_TEXT_BACK = 70482, //"BACK" + BOT_TEXT_AUTO = 70483, //"" + BOT_TEXT_NONE2 = 70484, //"" + BOT_TEXT_RANDOMPET_CUNNING = 70485, //"Random (Cunning)" + BOT_TEXT_RANDOMPET_FEROCITY = 70486, //"Random (Ferocity)" + BOT_TEXT_RANDOMPET_TENACITY = 70487, //"Random (Tenacity)" + BOT_TEXT_SHOW_INVENTORY = 70488, //"Show me your inventory" + BOT_TEXT_AUTOEQUIP = 70489, //"Auto-equip" + BOT_TEXT_SLOT_MH = 70490, //"Main hand" + BOT_TEXT_SLOT_OH = 70491, //"Off-hand" + BOT_TEXT_SLOT_RH = 70492, //"Ranged" + BOT_TEXT_SLOT_RELIC = 70493, //"Relic" + BOT_TEXT_SLOT_HEAD = 70494, //"Head" + BOT_TEXT_SLOT_SHOULDERS = 70495, //"Shoulders" + BOT_TEXT_SLOT_CHEST = 70496, //"Chest" + BOT_TEXT_SLOT_WAIST = 70497, //"Waist" + BOT_TEXT_SLOT_LEGS = 70498, //"Legs" + BOT_TEXT_SLOT_FEET = 70499, //"Feet" + BOT_TEXT_SLOT_WRIST = 70500, //"Wrist" + BOT_TEXT_SLOT_HANDS = 70501, //"Hands" + BOT_TEXT_SLOT_BACK = 70502, //"Back" + BOT_TEXT_SLOT_SHIRT = 70503, //"Shirt" + BOT_TEXT_SLOT_FINGER1 = 70504, //"Finger1" + BOT_TEXT_SLOT_FINGER2 = 70505, //"Finger2" + BOT_TEXT_SLOT_TRINKET1 = 70506, //"Trinket1" + BOT_TEXT_SLOT_TRINKET2 = 70507, //"Trinket2" + BOT_TEXT_SLOT_NECK = 70508, //"Neck" + BOT_TEXT_UNEQUIP_ALL = 70509, //"Unequip all" + BOT_TEXT_UPDATE_VISUAL = 70510, //"Update visual" + BOT_TEXT_VISUALONLY = 70511, //"visual only" + BOT_TEXT_EQUIPPED = 70512, //"Equipped" + BOT_TEXT_NOTHING = 70513, //"nothing" + BOT_TEXT_USE_OLD_EQUIPMENT = 70514, //"Use your old equipment" + BOT_TEXT_UNEQUIP = 70515, //"Unequip it" + BOT_TEXT_NOTHING_TO_GIVE = 70516, //"Hm... I have nothing to give you" + BOT_TEXT_GATHERING = 70517, //"Gathering" + BOT_TEXT_ABILITIES_STATUS = 70518, //"Abilities status" + BOT_TEXT_ALLOWED_ABILITIES = 70519, //"Manage allowed abilities" + BOT_TEXT_USE_ = 70520, //"Use " + BOT_TEXT_UPDATE = 70521, //"Update" + BOT_TEXT_DAMAGE = 70522, //"Damage" + BOT_TEXT_CONTROL = 70523, //"Control" + BOT_TEXT_HEAL = 70524, //"Heal" + BOT_TEXT_OTHER = 70525, //"Other" + BOT_TEXT_HIRE_EMOTE_SPHYNX = 70526, //" makes a grinding sound and begins to follow " + BOT_TEXT_HIREFAIL_OWNED = 70527, //"%s will not join you until dismissed by the owner" + BOT_TEXT_HIREFAIL_LVL60 = 70528, //"%s will not join you until you are level 60" + BOT_TEXT_HIREFAIL_LVL55 = 70529, //"%s will not join you until you are level 55" + BOT_TEXT_HIREFAIL_LVL40 = 70530, //"%s will not join you until you are level 40" + BOT_TEXT_HIREFAIL_LVL20 = 70531, //"%s will not join you until you are level 20" + BOT_TEXT_HIREFAIL_MAXBOTS = 70532, //"You exceed max npcbots for your level (%u)" + BOT_TEXT_HIREFAIL_COST = 70533, //"You don't have enough money" + BOT_TEXT_HIREFAIL_MAXCLASSBOTS = 70534, //"You cannot have more bots of that class! %u of %u" + BOT_TEXT_CANT_DISMISS_EQUIPMENT = 70535, //"Cannot reset equipment in slot %u (%s)! Cannot dismiss bot!" + BOT_TEXT_CURRENT = 70536, //"current" + BOT_TEXT_ATTACK_DISTANCE = 70537, //"Attack distance" + BOT_TEXT_SHORT_RANGE_ATTACKS = 70538, //"Short range attacks" + BOT_TEXT_LONG_RANGE_ATTACKS = 70539, //"Long range attacks" + BOT_TEXT_EXACT = 70540, //"Exact" + BOT_TEXT_REMOVE_BUFF = 70541, //"Remove buff" + BOT_TEXT_FIX_POWER = 70542, //"Fix your power type" + BOT_TEXT_CANT_UNEQUIP_MAILING = 70543, //"Cannot unequip %s for some stupid reason! Sending through mail" + BOT_TEXT_TANK = 70544, //"Tank" + BOT_TEXT_RANGED = 70545, //"Ranged" + BOT_TEXT_MINER = 70546, //"Miner" + BOT_TEXT_HERBALIST = 70547, //"Herbalist" + BOT_TEXT_SKINNER = 70548, //"Skinner" + BOT_TEXT_ENGINEER = 70549, //"Engineer" + BOT_TEXT_OWNERSHIP_EXPIRED = 70550, //"Bot ownership expired due to inactivity" + BOT_TEXT_BOTADDFAIL_DISABLED = 70551, //"NpcBot system is currently disabled. Please contact administration." + BOT_TEXT_BOTADDFAIL_OWNED = 70552, //"%s will not join you, already has master: %s" + BOT_TEXT_DO_REUSE_1 = 70553, // WAS BOT_TEXT_BOTADDFAIL_TELEPORTED "%s cannot join you while about to teleport" + BOT_TEXT_ASPECT = 70554, //"Aspect" + BOT_TEXT_MONKEY = 70555, //"Monkey" + BOT_TEXT_HAWK = 70556, //"Hawk" + BOT_TEXT_CHEETAH = 70557, //"Cheetah" + BOT_TEXT_VIPER = 70558, //"Viper" + BOT_TEXT_BEAST = 70559, //"Beast" + BOT_TEXT_PACK = 70560, //"Pack" + BOT_TEXT_WILD = 70561, //"Wild" + BOT_TEXT_DRAGONHAWK = 70562, //"Dragonhawk" + BOT_TEXT_NOASPECT = 70563, //"No Aspect" + BOT_TEXT_AURA = 70564, //"Aura" + BOT_TEXT_DEVOTION = 70565, //"Devotion" + BOT_TEXT_CONCENTRATION = 70566, //"Concentration" + BOT_TEXT_FIRERESISTANCE = 70567, //"Fire Resistance" + BOT_TEXT_FROSTRESISTANCE = 70568, //"Frost Resistance" + BOT_TEXT_SHADOWRESISTANCE = 70569, //"Shadow Resistance" + BOT_TEXT_RETRIBUTION = 70570, //"Retribution" + BOT_TEXT_CRUSADER = 70571, //"Crusader" + BOT_TEXT_NOAURA = 70572, //"No Aura" + BOT_TEXT_CRIPPLING = 70573, //"Crippling" + BOT_TEXT_INSTANT = 70574, //"Instant" + BOT_TEXT_DEADLY = 70575, //"Deadly" + BOT_TEXT_WOUND = 70576, //"Wound" + BOT_TEXT_MINDNUMBING = 70577, //"Mind-Numbing" + BOT_TEXT_ANESTHETIC = 70578, //"Anesthetic" + BOT_TEXT_NOTHING_C = 70579, //"Nothing" + BOT_TEXT_FLAMETONGUE = 70580, //"Flametongue" + BOT_TEXT_FROSTBRAND = 70581, //"Frostbrand" + BOT_TEXT_WINDFURY = 70582, //"Windfury" + BOT_TEXT_EARTHLIVING = 70583, //"Earthliving" + BOT_TEXT_BOTGIVER_SERVICE = 70584, //"I need your services" + BOT_TEXT_BOTGIVER_TOO_MANY_BOTS = 70585, //"You have too many bots" + BOT_TEXT_BOTGIVER_WISH_TO_HIRE_ = 70586, //"Do you wish to hire " + BOT_TEXT_BOTGIVER__BOT_BUSY = 70587, //" is a bit busy at the moment, try again later." + BOT_TEXT_BOTGIVER_HIRESUCCESS = 70588, //"Pleasure doing business with you" + BOT_TEXT_CLASS_WARRIOR_PLU = 70589, //"Warriors" + BOT_TEXT_CLASS_PALADIN_PLU = 70590, //"Paladins" + BOT_TEXT_CLASS_MAGE_PLU = 70591, //"Mages" + BOT_TEXT_CLASS_PRIEST_PLU = 70592, //"Priests" + BOT_TEXT_CLASS_WARLOCK_PLU = 70593, //"Warlocks" + BOT_TEXT_CLASS_DRUID_PLU = 70594, //"Druids" + BOT_TEXT_CLASS_DEATH_KNIGHT_PLU = 70595, //"Death Knights" + BOT_TEXT_CLASS_ROGUE_PLU = 70596, //"Rogues" + BOT_TEXT_CLASS_SHAMAN_PLU = 70597, //"Shamans" + BOT_TEXT_CLASS_HUNTER_PLU = 70598, //"Hunters" + BOT_TEXT_CLASS_BM_PLU = 70599, //"Blademasters" + BOT_TEXT_CLASS_SPHYNX_PLU = 70600, //"Destroyers" + BOT_TEXT_CLASS_ARCHMAGE_PLU = 70601, //"Archmagi" + BOT_TEXT_CLASS_DREADLORD_PLU = 70602, //"Dreadlords" + BOT_TEXT_CLASS_SPELLBREAKER_PLU = 70603, //"Spell Breakers" + BOT_TEXT_CLASS_DARK_RANGER_PLU = 70604, //"Dark Rangers" + BOT_TEXT_CLASS_WARRIOR = 70605, //"Warrior" + BOT_TEXT_CLASS_PALADIN = 70606, //"Paladin" + BOT_TEXT_CLASS_MAGE = 70607, //"Mage" + BOT_TEXT_CLASS_PRIEST = 70608, //"Priest" + BOT_TEXT_CLASS_WARLOCK = 70609, //"Warlock" + BOT_TEXT_CLASS_DRUID = 70610, //"Druid" + BOT_TEXT_CLASS_DEATH_KNIGHT = 70611, //"Death Knight" + BOT_TEXT_CLASS_ROGUE = 70612, //"Rogue" + BOT_TEXT_CLASS_SHAMAN = 70613, //"Shaman" + BOT_TEXT_CLASS_HUNTER = 70614, //"Hunter" + BOT_TEXT_CLASS_BM = 70615, //"Blademaster" + BOT_TEXT_CLASS_SPHYNX = 70616, //"Destroyer" + BOT_TEXT_CLASS_ARCHMAGE = 70617, //"Archmage" + BOT_TEXT_CLASS_DREADLORD = 70618, //"Dreadlord" + BOT_TEXT_CLASS_SPELLBREAKER = 70619, //"Spell Breaker" + BOT_TEXT_CLASS_DARK_RANGER = 70620, //"Dark Ranger" + BOT_TEXT_GENDER_MALE = 70621, //"Male" + BOT_TEXT_GENDER_FEMALE = 70622, //"Female" + BOT_TEXT_RACE_HUMAN = 70623, //"Human" + BOT_TEXT_RACE_ORC = 70624, //"Orc" + BOT_TEXT_RACE_DWARF = 70625, //"Dwarf" + BOT_TEXT_RACE_NELF = 70626, //"Night Elf" + BOT_TEXT_RACE_UNDEAD = 70627, //"Undead" + BOT_TEXT_RACE_TAUREN = 70628, //"Tauren" + BOT_TEXT_RACE_GNOME = 70629, //"Gnome" + BOT_TEXT_RACE_TROLL = 70630, //"Troll" + BOT_TEXT_RACE_BELF = 70631, //"Blood Elf" + BOT_TEXT_RACE_DRAENEI = 70632, //"Draenei" + BOT_TEXT_RACE_UNKNOWN = 70633, //"Unknown" + BOT_TEXT_LOOTING = 70634, //"Looting" + BOT_TEXT_POOR = 70635, //"Poor" + BOT_TEXT_COMMON = 70636, //"Common" + BOT_TEXT_UNCOMMON = 70637, //"Uncommon" + BOT_TEXT_RARE = 70638, //"Rare" + BOT_TEXT_EPIC = 70639, //"Epic" + BOT_TEXT_LEGENDARY = 70640, //"Legendary" + BOT_TEXT_ENGAGE_BEHAVIOR = 70641, //"Engage behavior" + BOT_TEXT_DELAY_ATTACK_BY = 70642, //"Delay attack by" + BOT_TEXT_DELAY_HEALING_BY = 70643, //"Delay healing by" + BOT_TEXT_SECOND_SHORT = 70644, //"s" + BOT_TEXT_TANK_OFF = 70645, //"Off-Tank" + BOT_TEXT_CLASS_NECROMANCER_PLU = 70646, //"Necromancers" + BOT_TEXT_CLASS_NECROMANCER = 70647, //"Necromancer" + BOT_TEXT_ATTACK_ANGLE = 70648, //"Attack angle" + BOT_TEXT_NORMAL = 70649, //"Normal" + BOT_TEXT_AVOID_FRONTAL_AOE = 70650, //"Avoid frontal AOE" + BOT_TEXT_HIREDENY_SEAWITCH = 70651, //NIY + BOT_TEXT_HIREWARN_SEAWITCH = 70652, //"Are you sure this is gonna work? It's better be the best water in the world..." + BOT_TEXT_HIREOPTION_SEAWITCH = 70653, //"Seems like you could really use a drink of fresh water." + BOT_TEXT_CLASS_SEAWITCH_PLU = 70654, //"Sea Witches" + BOT_TEXT_CLASS_SEAWITCH = 70655, //"Sea Witch" + BOT_TEXT_MANA_PER_DAMAGE = 70656, //"Mana per damage" + BOT_TEXT_DAMAGE_PER_MANA = 70657, //"Damage per mana" + BOT_TEXT_TRANSMOGRIFICATION = 70658, //"Transmogrification..." + BOT_TEXT_DISABLE_COMBAT_POSITIONING = 70659, //"DISABLE combat positioning" + BOT_TEXT_PRIORITY_TARGET = 70660, //"Priority target" + BOT_TEXT_BOT_GEAR_BANK = 70661, //"Bot gear bank..." + BOT_TEXT_DEPOSIT_ITEMS = 70662, //"Deposit items..." + BOT_TEXT_WITHDRAW_ITEMS = 70663, //"Withdraw items..." + BOT_TEXT_BANK_IS_EMPTY = 70664, //"Bank is empty" + BOT_TEXT_PREVIOUS_PAGE = 70665, //"Previous page" + BOT_TEXT_NEXT_PAGE = 70666, //"Next page" + BOT_TEXT_HIREWARN_CRYPTLORD = 70667, //"Do you really want to spend all this money to make Crypt Lord move again?" + BOT_TEXT_HIREOPTION_CRYPTLORD = 70668, //"I doubt your ability to do much harm in your current state, but I am willing to lead you and help you restore your powers." + BOT_TEXT_CLASS_CRYPT_LORD_PLU = 70669, //"Crypt Lords" + BOT_TEXT_CLASS_CRYPT_LORD = 70670, //"Crypt Lord" + BOT_TEXT_REFLECT = 70671, //"Reflect" + BOT_TEXT_LOCUSTS = 70672, //"Locusts" + BOT_TEXT_HEAL_TARGET_HEALTH_THRESHOLD = 70673, //"Heal target health threshold" + BOT_TEXT_I_NEED_A_PORTAL = 70674, //"I need a portal" + BOT_TEXT_STORMWIND = 70675, //"Stormwind" + BOT_TEXT_IRONFORGE = 70676, //"Ironforge" + BOT_TEXT_DARNASSUS = 70677, //"Darnassus" + BOT_TEXT_EXORDAR = 70678, //"Exordar" + BOT_TEXT_ORGRIMMAR = 70679, //"Orgrimmar" + BOT_TEXT_UNDERCITY = 70680, //"Undercity" + BOT_TEXT_THUNDER_BLUFF = 70681, //"Thunder Bluff" + BOT_TEXT_SILVERMOON = 70682, //"Silvermoon" + BOT_TEXT_SHATTRATH = 70683, //"Shattrath" + BOT_TEXT_DALARAN = 70684, //"Dalaran" + BOT_TEXT_HIREFAIL_MAXBOTS_ACCOUNT = 70685, //"You exceed max npcbots for your account (%u >= %u)" + //70686-70799 reserved for custom localization strings +}; + +#endif //BOTTEXT_H diff --git a/src/server/game/AI/NpcBots/botwanderful.cpp b/src/server/game/AI/NpcBots/botwanderful.cpp new file mode 100644 index 000000000..be76827e5 --- /dev/null +++ b/src/server/game/AI/NpcBots/botwanderful.cpp @@ -0,0 +1,352 @@ +#include "botwanderful.h" +#include "DBCStores.h" +#include "TemporarySummon.h" + +#include +#include +#include + +#ifdef _MSC_VER +# pragma warning(push, 4) +#endif + +uint32 WanderNode::nextWPId = 0; +WanderNode::node_ltype WanderNode::ALL_WPS = {}; +WanderNode::node_mtype WanderNode::ALL_WPS_PER_MAP = {}; +WanderNode::node_mtype WanderNode::ALL_WPS_PER_ZONE = {}; +WanderNode::node_mtype WanderNode::ALL_WPS_PER_AREA = {}; + +WanderNode::mutex_type* WanderNode::GetLock() +{ + static mutex_type _lock; + return &_lock; +} + +bool WanderNode::IsWP(Creature const* creature) +{ + if (!creature) + return false; + + lock_type lock(*GetLock()); + + return std::find_if(ALL_WPS.cbegin(), ALL_WPS.cend(), [=](WanderNode const* wp) { + return wp->GetCreature() == creature; + }) != ALL_WPS.cend(); +} + +WanderNode* WanderNode::FindInAllWPs(uint32 wpId) +{ + lock_type lock(*GetLock()); + + auto ci = std::find_if(ALL_WPS.cbegin(), ALL_WPS.cend(), [wpId = wpId](WanderNode const* wp) { + return wp->GetWPId() == wpId; + }); + + return ci == ALL_WPS.cend() ? nullptr : *ci; +} + +WanderNode* WanderNode::FindInAllWPs(Creature const* creature) +{ + if (!creature) + return nullptr; + + lock_type lock(*GetLock()); + + auto ci = std::find_if(ALL_WPS.cbegin(), ALL_WPS.cend(), [=](WanderNode const* wp) { + return wp->GetCreature() == creature; + }); + + return ci == ALL_WPS.cend() ? nullptr : *ci; +} + +WanderNode* WanderNode::FindInMapWPs(uint32 mapId, Creature const* creature) +{ + if (!creature) + return nullptr; + + lock_type lock(*GetLock()); + + auto cim = ALL_WPS_PER_MAP.find(mapId); + if (cim == ALL_WPS_PER_MAP.cend()) + return nullptr; + auto ci = std::find_if(ALL_WPS_PER_MAP.at(mapId).cbegin(), ALL_WPS_PER_MAP.at(mapId).cend(), [=](WanderNode const* wp) { + return wp->GetCreature() == creature; + }); + + return ci == ALL_WPS_PER_MAP.at(mapId).cend() ? nullptr : *ci; +} + +WanderNode* WanderNode::FindInMapWPs(uint32 mapId, uint32 wpId) +{ + lock_type lock(*GetLock()); + + auto cim = ALL_WPS_PER_MAP.find(mapId); + if (cim == ALL_WPS_PER_MAP.cend()) + return nullptr; + auto ci = std::find_if(ALL_WPS_PER_MAP.at(mapId).cbegin(), ALL_WPS_PER_MAP.at(mapId).cend(), [=](WanderNode const* wp) { + return wp->GetWPId() == wpId; + }); + + return ci == ALL_WPS_PER_MAP.at(mapId).cend() ? nullptr : *ci; +} + +WanderNode* WanderNode::FindInMapWPs(uint32 mapId, node_check_ftype_c const& pred) +{ + lock_type lock(*GetLock()); + + auto cim = ALL_WPS_PER_MAP.find(mapId); + if (cim == ALL_WPS_PER_MAP.cend()) + return nullptr; + auto ci = std::find_if(ALL_WPS_PER_MAP.at(mapId).cbegin(), ALL_WPS_PER_MAP.at(mapId).cend(), pred); + + return ci == ALL_WPS_PER_MAP.at(mapId).cend() ? nullptr : *ci; +} + +WanderNode* WanderNode::FindInZoneWPs(uint32 zoneId, node_check_ftype_c const& pred) +{ + lock_type lock(*GetLock()); + + auto cim = ALL_WPS_PER_ZONE.find(zoneId); + if (cim == ALL_WPS_PER_ZONE.cend()) + return nullptr; + auto ci = std::find_if(ALL_WPS_PER_ZONE.at(zoneId).cbegin(), ALL_WPS_PER_ZONE.at(zoneId).cend(), pred); + + return ci == ALL_WPS_PER_ZONE.at(zoneId).cend() ? nullptr : *ci; +} + +WanderNode* WanderNode::FindInAreaWPs(uint32 areaId, node_check_ftype_c const& pred) +{ + lock_type lock(*GetLock()); + + auto cim = ALL_WPS_PER_AREA.find(areaId); + if (cim == ALL_WPS_PER_AREA.cend()) + return nullptr; + auto ci = std::find_if(ALL_WPS_PER_AREA.at(areaId).cbegin(), ALL_WPS_PER_AREA.at(areaId).cend(), pred); + + return ci == ALL_WPS_PER_AREA.at(areaId).cend() ? nullptr : *ci; +} + +void WanderNode::DoForAllWPs(node_proc_ftype&& func) +{ + lock_type lock(*GetLock()); + + DoForContainerWPs(ALL_WPS, std::forward(func)); +} + +void WanderNode::DoForAllZoneWPs(uint32 zoneId, node_proc_ftype_c&& func) +{ + lock_type lock(*GetLock()); + + node_mtype::const_iterator ci = ALL_WPS_PER_ZONE.find(zoneId); + if (ci != ALL_WPS_PER_ZONE.end()) + DoForContainerWPs(ci->second, std::forward(func)); +} + +void WanderNode::DoForAllMapWPs(uint32 mapId, node_proc_ftype_c&& func) +{ + lock_type lock(*GetLock()); + + node_mtype::const_iterator ci = ALL_WPS_PER_MAP.find(mapId); + if (ci != ALL_WPS_PER_MAP.end()) + DoForContainerWPs(ci->second, std::forward(func)); +} + +void WanderNode::DoForAllAreaWPs(uint32 areaId, node_proc_ftype_c&& func) +{ + lock_type lock(*GetLock()); + + node_mtype::const_iterator ci = ALL_WPS_PER_AREA.find(areaId); + if (ci != ALL_WPS_PER_AREA.end()) + DoForContainerWPs(ci->second, std::forward(func)); +} + +size_t WanderNode::GetAllWPsCount() +{ + lock_type lock(*GetLock()); + + return ALL_WPS.size(); +} + +size_t WanderNode::GetMapWPsCount(uint32 mapId) +{ + lock_type lock(*GetLock()); + + node_mtype::const_iterator ci = ALL_WPS_PER_MAP.find(mapId); + return ci != ALL_WPS_PER_MAP.end() ? ci->second.size() : 0u; +} + +size_t WanderNode::GetWPMapsCount() +{ + lock_type lock(*GetLock()); + + return ALL_WPS_PER_MAP.size(); +} + +WanderNode::WanderNode(uint32 wpId, uint32 mapId, float x, float y, float z, float o, uint32 zoneId, uint32 areaId, std::string const& name) + : Position(x, y, z, o), + _wpId(wpId), _mapId(mapId), _zoneId(zoneId), _areaId(areaId), _name(name), _minLevel(1u), _maxLevel(DEFAULT_MAX_LEVEL), _flags(0), + _creature(nullptr) +{ + ASSERT(!!sMapStore.LookupEntry(_mapId), "WanderNode::Ctr(): Invalid value for _mapId"); + ASSERT(!!sAreaTableStore.LookupEntry(_zoneId), "WanderNode::Ctr(): Invalid value for _zoneId"); + ASSERT(!!sAreaTableStore.LookupEntry(_areaId), "WanderNode::Ctr(): Invalid value for _areaId"); + + lock_type lock(*GetLock()); + + ALL_WPS.push_back(this); + ALL_WPS_PER_MAP[_mapId].push_back(this); + ALL_WPS_PER_ZONE[_zoneId].push_back(this); + ALL_WPS_PER_AREA[_areaId].push_back(this); +} + +WanderNode::~WanderNode() +{ + RemoveWP(this); +} + +void WanderNode::RemoveWP(WanderNode* wp) +{ + while (!wp->GetLinks().empty()) + wp->UnLink(wp->GetLinks().front()); + + if (wp->GetCreature() && wp->GetCreature()->IsInWorld()) + wp->GetCreature()->ToTempSummon()->DespawnOrUnsummon(); + + ALL_WPS_PER_AREA.at(wp->_areaId).remove(wp); + ALL_WPS_PER_ZONE.at(wp->_zoneId).remove(wp); + ALL_WPS_PER_MAP.at(wp->_mapId).remove(wp); + ALL_WPS.remove(wp); + + //WE LET THE NODE LEAK for threadsafety + //delete wp +} + +void WanderNode::RemoveAllWPs() +{ + lock_type lock(*GetLock()); + + while (!ALL_WPS.empty()) + RemoveWP(ALL_WPS.front()); +} + +WanderNode::node_ltype_c WanderNode::GetShortestPathLinks(WanderNode const* target, WanderNode::node_ltype_c const& base_links) const +{ + using NodeList = WanderNode::node_ltype_c; + + ASSERT(std::all_of(base_links.cbegin(), base_links.cend(), [this](WanderNode const* wp) { return HasLink(wp); })); + + NodeList retlist; + if (this == target) + retlist.push_back(this); + else + { + std::list> validLinks; + for (WanderNode const* link : base_links) + { + if (link == target) + { + retlist.push_back(link); + validLinks.clear(); + break; + } + + std::unordered_set checked_links; + checked_links.insert(this); + NodeList vlinks_cur; + NodeList clinks; + clinks.push_back(link); + for (uint32 level = 0; !clinks.empty(); ++level) + { + for (WanderNode const* wp : clinks) + { + if (wp->HasLink(target)) + vlinks_cur.push_back(link); + } + if (!vlinks_cur.empty()) + { + validLinks.emplace_back(level, link); + break; + } + NodeList clinks_new; + for (WanderNode const* wp : clinks) + { + checked_links.insert(wp); // cut off all ways back (2-ways, circular) + std::copy_if(wp->GetLinks().begin(), wp->GetLinks().end(), std::back_inserter(clinks_new), [&checked_links](WanderNode const* cwp) { + return !checked_links.contains(cwp); + }); + } + clinks = std::move(clinks_new); + } + } + + if (!validLinks.empty()) + { + //only choose one of the shortest routes + if (validLinks.size() > 1) + { + auto minlevel = std::numeric_limits::max(); + for (auto const& kv : validLinks) + minlevel = std::min(minlevel, kv.first); + validLinks.remove_if([=](decltype(validLinks)::value_type const& p) { return p.first > minlevel; }); + } + for (decltype(validLinks)::value_type const& vt : validLinks) + retlist.emplace_back(std::move(vt.second)); + } + } + + return retlist; +} + +void WanderNode::SetCreature(Creature* creature) +{ + if (creature != nullptr) + ASSERT(!_creature); + + _creature = creature; +} + +Creature* WanderNode::GetCreature() const +{ + return _creature; +} + +std::string WanderNode::FormatLinks() const +{ + std::ostringstream lss; + for (WanderNode* lwp : _links) + lss << lwp->GetWPId() << ":0 "; //TODO: chance + + return lss.str(); +} + +void WanderNode::SetFlags(BotWPFlags flags) +{ + _flags |= AsUnderlyingType(flags); +} + +void WanderNode::RemoveFlags(BotWPFlags flags) +{ + _flags &= ~AsUnderlyingType(flags); +} + +bool WanderNode::HasFlag(BotWPFlags flags) const +{ + return !!(_flags & AsUnderlyingType(flags)); +} + +std::string WanderNode::ToString() const +{ + std::ostringstream wps; + wps << "WP " << _wpId << " '" << _name << "', " << uint32(_links.size()) << " link(s)" << ", Map " << _mapId + << ", Zone " << _zoneId << " (" << std::string(sAreaTableStore.LookupEntry(_zoneId)->AreaName[0]) + << "), Area " << _areaId << " (" << std::string(sAreaTableStore.LookupEntry(_areaId)->AreaName[0]) + << "), minLvl " << uint32(_minLevel) << ", maxLvl " << uint32(_maxLevel) + << " (" << static_cast(this)->ToString() << ')' + << ", flags: 0x" << std::hex << std::setw(8) << std::setfill('0') << _flags << std::dec; + + return wps.str(); +} + +#ifdef _MSC_VER +# pragma warning(pop) +#endif diff --git a/src/server/game/AI/NpcBots/botwanderful.h b/src/server/game/AI/NpcBots/botwanderful.h new file mode 100644 index 000000000..cb75d9b2c --- /dev/null +++ b/src/server/game/AI/NpcBots/botwanderful.h @@ -0,0 +1,169 @@ +#ifndef BOTWANDERFUL_H_ +#define BOTWANDERFUL_H_ + +#include "Position.h" + +#include +#include +#include +#include +#include + +/* +NpcBot System by Trickerer (onlysuffering@gmail.com) +Original patch from: LordPsyan https://bitbucket.org/lordpsyan/trinitycore-patches/src/3b8b9072280e/Individual/11185-BOTS-NPCBots.patch +*/ + +class Creature; + +enum class BotWPFlags : uint32 +{ + BOTWP_FLAG_NONE = 0x00000000, + BOTWP_FLAG_SPAWN = 0x00000001, // wandering bots can spawn at this WP location + BOTWP_FLAG_ALLIANCE_ONLY = 0x00000002, // only alliance bots can move here, SPAWN+A = only alliance bots can spawn at this WP location + BOTWP_FLAG_HORDE_ONLY = 0x00000004, // only horde bots can move here, SPAWN+H = only horde bots can spawn at this WP location + BOTWP_FLAG_CAN_BACKTRACK_FROM = 0x00000008, // can move back to WPs links even if other links exist + BOTWP_FLAG_MOVEMENT_IGNORES_FACTION = 0x00000010, // ignore faction flags when trying to select this WP as move point + BOTWP_FLAG_MOVEMENT_IGNORES_PATHING = 0x00000020, // do not generate path between 2 WPs having this flag + BOTWP_FLAG_BG_FLAG_DELIVER_TARGET = 0x00000040, // flag carrier destination marker + BOTWP_FLAG_BG_FLAG_PICKUP_TARGET = 0x00000080, // flag pick/activate up marker + BOTWP_FLAG_BG_BOSS_ROOM = 0x00000100, // boss room to attack as group / defend + BOTWP_FLAG_BG_MISC_OBJECTIVE_1 = 0x00000200, // misc objective 1 (AV = mine) + BOTWP_FLAG_END = 0x00000400, + + BOTWP_FLAG_ALLIANCE_OR_HORDE_ONLY = BOTWP_FLAG_ALLIANCE_ONLY | BOTWP_FLAG_HORDE_ONLY, + BOTWP_FLAG_ALLIANCE_BOSS_ROOM = BOTWP_FLAG_ALLIANCE_ONLY | BOTWP_FLAG_BG_BOSS_ROOM, + BOTWP_FLAG_HORDE_BOSS_ROOM = BOTWP_FLAG_HORDE_ONLY | BOTWP_FLAG_BG_BOSS_ROOM +}; + +class WanderNode : public Position +{ + using node_ltype = std::list; + using node_ltype_c = std::list; + using node_mtype = std::unordered_map; + + using node_proc_ftype = std::function; + using node_proc_ftype_c = std::function; + using node_check_ftype_c = std::function; + + using mutex_type = std::recursive_mutex; + using lock_type = std::unique_lock; + + static node_ltype ALL_WPS; + static node_mtype ALL_WPS_PER_MAP; + static node_mtype ALL_WPS_PER_ZONE; + static node_mtype ALL_WPS_PER_AREA; + + template + struct is_container : std::false_type {}; + template + struct is_container()), decltype(std::declval().size())>> : std::true_type {}; + template + static constexpr bool is_container_v = is_container::value; + +public: + static uint32 nextWPId; + + static mutex_type* GetLock(); + + static bool IsWP(Creature const* creature); + static WanderNode* FindInAllWPs(uint32 wpId); + static WanderNode* FindInAllWPs(Creature const* creature); + static WanderNode* FindInMapWPs(uint32 mapId, Creature const* creature); + static WanderNode* FindInMapWPs(uint32 mapId, uint32 wpId); + static WanderNode* FindInMapWPs(uint32 mapId, node_check_ftype_c const& pred); + static WanderNode* FindInZoneWPs(uint32 zoneId, node_check_ftype_c const& pred); + static WanderNode* FindInAreaWPs(uint32 areaId, node_check_ftype_c const& pred); + + template + static void DoForContainerWPs(Container const& c, Func&& func) { + static_assert(WanderNode::is_container_v); + static_assert(std::is_same_v>, WanderNode>); + static_assert(std::is_convertible_v); + //lock_type lock(*GetLock()); + for (auto* wp : c) + func(wp); + } + + static void DoForAllWPs(node_proc_ftype&& func); + static void DoForAllMapWPs(uint32 mapId, node_proc_ftype_c&& func); + static void DoForAllZoneWPs(uint32 zoneId, node_proc_ftype_c&& func); + static void DoForAllAreaWPs(uint32 areaId, node_proc_ftype_c&& func); + static size_t GetAllWPsCount(); + static size_t GetMapWPsCount(uint32 mapId); + static size_t GetWPMapsCount(); + + WanderNode(uint32 wpId, uint32 mapId, float x, float y, float z, float o, uint32 zoneId, uint32 areaId, std::string const& name); + ~WanderNode(); + + static void RemoveAllWPs(); + static void RemoveWP(WanderNode* wp); + + //utils + WanderNode::node_ltype_c GetShortestPathLinks(WanderNode const* target, WanderNode::node_ltype_c const& base_links) const; + + //base + void SetCreature(Creature* creature); + Creature* GetCreature() const; + + std::string FormatLinks() const; + + void Link(WanderNode* wp, bool oneway = false) { + if (!HasLink(wp)) { + _links.push_back(wp); + if (!oneway) + wp->Link(this); + } + } + void UnLink(WanderNode* wp) { + if (HasLink(wp)) { + _links.remove(wp); + wp->UnLink(this); + } + } + bool HasLink(WanderNode const* wp) const { + return std::find(_links.cbegin(), _links.cend(), wp) != _links.cend(); + } + auto GetLinks() const -> typename std::add_const_t& { + return _links; + } + + void SetLevels(std::pair levels) { + std::tie(_minLevel, _maxLevel) = levels; + } + inline void SetLevels(uint8 minLevel, uint8 maxLevel) { + SetLevels(std::pair{ minLevel, maxLevel }); + } + + void SetFlags(BotWPFlags flags); + void RemoveFlags(BotWPFlags flags); + bool HasFlag(BotWPFlags flags) const; + + void SetName(std::string const& name) { _name = name; } + + std::string ToString() const; + + uint32 GetWPId() const { return _wpId; } + uint32 GetMapId() const { return _mapId; } + uint32 GetZoneId() const { return _zoneId; } + uint32 GetAreaId() const { return _areaId; } + std::string const& GetName() const { return _name; } + std::pair GetLevels() const { return { _minLevel, _maxLevel }; } + uint32 GetFlags() const { return _flags; } + +private: + const uint32 _wpId; + const uint32 _mapId; + const uint32 _zoneId; + const uint32 _areaId; + /*const*/ std::string _name; + uint8 _minLevel; + uint8 _maxLevel; + uint32 _flags; + + node_ltype _links; + + Creature* _creature; +}; + +#endif //BOTWANDERFUL_H_ diff --git a/src/server/game/AI/NpcBots/bpet_ai.cpp b/src/server/game/AI/NpcBots/bpet_ai.cpp new file mode 100644 index 000000000..7ec4a93f3 --- /dev/null +++ b/src/server/game/AI/NpcBots/bpet_ai.cpp @@ -0,0 +1,2656 @@ +#include "bpet_ai.h" +#include "bot_GridNotifiers.h" +#include "botmgr.h" +#include "Containers.h" +#include "LFGMgr.h" +#include "Log.h" +#include "Map.h" +#include "MotionMaster.h" +#include "ObjectMgr.h" +#include "PointMovementGenerator.h" +#include "SpellAuraEffects.h" +#include "Transport.h" +#include "World.h" +/* +NpcBot Pet System by Trickerer (https://github.com/trickerer/Trinity-Bots; onlysuffering@gmail.com) +*/ + +static constexpr uint32 SHAMAN_MAX_PET_POSITIONS = 2; +static constexpr uint32 DRUID_MAX_PET_POSITIONS = 3; +static constexpr uint32 DK_MAX_PET_POSITIONS = 10; +static constexpr uint32 DARK_RANGER_MAX_PET_POSITIONS = 5; +static constexpr uint32 NECROMANCER_MAX_PET_POSITIONS = 12; +static constexpr uint32 CRYPT_LORD_MAX_PET_POSITIONS = 6; +float constexpr ShamanPetPositionAnglesByPosNumber[SHAMAN_MAX_PET_POSITIONS] = +{ + 0.f,//left + float(M_PI)//right +}; +float constexpr DruidPetPositionAnglesByPosNumber[DRUID_MAX_PET_POSITIONS] = +{ + 0.f,//left + float(M_PI)/2,//back + float(M_PI)//right +}; +float constexpr DKPetPositionAnglesByPosNumber[DK_MAX_PET_POSITIONS] = +{ + 0.f, + float(M_PI), + 0.3490658f,//1*M_PI/9 + 0.6981317f,//2*M_PI/9 + 1.0471975f,//3*M_PI/9 + 1.3962634f,//4*M_PI/9 + 1.7453292f,//5*M_PI/9 + 2.0943951f,//6*M_PI/9 + 2.4434609f,//7*M_PI/9 + 2.7925268f //8*M_PI/9 +}; +float constexpr DarkRangerPetPositionAnglesByPosNumber[DARK_RANGER_MAX_PET_POSITIONS] = +{ + 0.f, + float(M_PI), + 0.7853981f,//1*M_PI/4 + 1.5707963f,//2*M_PI/4 + 2.3561944f //3*M_PI/4 +}; +float constexpr NecromancerPetPositionAnglesByPosNumber[NECROMANCER_MAX_PET_POSITIONS] = +{ + 0.f, + float(M_PI), + float(1 *M_PI/11), + float(2 *M_PI/11), + float(3 *M_PI/11), + float(4 *M_PI/11), + float(5 *M_PI/11), + float(6 *M_PI/11), + float(7 *M_PI/11), + float(8 *M_PI/11), + float(9 *M_PI/11), + float(10*M_PI/11) +}; +float constexpr CryptLordPetPositionAnglesByPosNumber[CRYPT_LORD_MAX_PET_POSITIONS] = +{ + 0.7853981f,//1*M_PI/4 + 2.3561944f,//3*M_PI/4 + 0.0f, + float(M_PI), + 5.4977875f,//7*M_PI/4 + 3.9269910f //5*M_PI/4 +}; + +extern uint8 GroupIconsFlags[TARGET_ICONS_COUNT]; + +static uint16 __rand; //calculated for each bot separately once every updateAI tick + +extern bool _botPvP; +extern uint8 _healTargetIconFlags; + +bot_pet_ai::bot_pet_ai(Creature* creature) : CreatureAI(creature) +{ + m_botCommandState = BOT_COMMAND_FOLLOW; + regenTimer = 0; + waitTimer = 0; + _moveBehindTimer = 0; + indoorsTimer = 0; + outdoorsTimer = 0; + GC_Timer = 0; + lastdiff = 0; + _energyFraction = 0.f; + _updateTimerMedium = 0; + _updateTimerEx1 = urand(12000, 15000); + checkAurasTimer = 0; + + _wanderer = false; + + _auraRaidUpdateMask = 0; + + myType = 0; + petOwner = nullptr; + canUpdate = true; +} +bot_pet_ai::~bot_pet_ai() +{ + while (!_spells.empty()) + { + BotPetSpellMap::iterator itr = _spells.begin(); + delete itr->second; + _spells.erase(itr); + } +} + +uint16 bot_pet_ai::Rand() const +{ + return __rand; +} +//0-178 +void bot_pet_ai::GenerateRand() const +{ + __rand = urand(0, IAmFree() ? 100 : 100 + (petOwner->GetBotOwner()->GetNpcBotsCount() - 1) * 2); +} + +bool bot_pet_ai::_checkImmunities(Unit const* target, SpellInfo const* spellInfo) const +{ + return target && spellInfo && !target->IsImmunedToDamage(spellInfo); +} +//Follow point calculation +void bot_pet_ai::_calculatePos(Position& pos) const +{ + switch (myType) + { + case BOT_PET_LOCUST_SWARM: + pos.Relocate(me); + return; + default: + break; + } + + float x,y,z; + //destination + petOwner->GetPosition(x, y, z); + //relative angle + uint32 movFlags = petOwner->m_movementInfo.GetMovementFlags(); + float o = petOwner->GetOrientation() + PET_FOLLOW_ANGLE; + uint8 posNum = petOwner->GetBotAI()->GetPetPositionNumber(me); + if (petOwner->GetBotClass() == BOT_CLASS_DEATH_KNIGHT) + o += DKPetPositionAnglesByPosNumber[posNum]; + else if (petOwner->GetBotClass() == BOT_CLASS_DRUID) + o += DruidPetPositionAnglesByPosNumber[posNum]; + else if (petOwner->GetBotClass() == BOT_CLASS_SHAMAN) + o += ShamanPetPositionAnglesByPosNumber[posNum]; + else if (petOwner->GetBotClass() == BOT_CLASS_DARK_RANGER) + o += DarkRangerPetPositionAnglesByPosNumber[posNum]; + else if (petOwner->GetBotClass() == BOT_CLASS_NECROMANCER) + o += NecromancerPetPositionAnglesByPosNumber[posNum]; + else if (petOwner->GetBotClass() == BOT_CLASS_CRYPT_LORD) + o += CryptLordPetPositionAnglesByPosNumber[posNum]; + + o = Position::NormalizeOrientation(o); + //distance + x += (PET_FOLLOW_DIST + me->GetCombatReach() + petOwner->GetCombatReach()) * std::cos(o); + y += (PET_FOLLOW_DIST + me->GetCombatReach() + petOwner->GetCombatReach()) * std::sin(o); + if (movFlags & MOVEMENTFLAG_FORWARD) + { + static float const aheadDist = 6.f; + x = x + aheadDist * std::cos(petOwner->GetOrientation()); + y = y + aheadDist * std::sin(petOwner->GetOrientation()); + } + if (!petOwner->GetTransport()) + me->UpdateGroundPositionZ(x, y, z); + if (me->GetPositionZ() < z) + z += 0.5f; //prevent going underground + + pos.m_positionX = x; + pos.m_positionY = y; + pos.m_positionZ = z; +} +void bot_pet_ai::SetBotCommandState(uint32 st, bool force, Position* newpos) +{ + if (!(st & (BOT_COMMAND_INACTION))) + { + if (!me->IsAlive() || JumpingOrFalling()) + return; + } + + switch (myType) + { + case BOT_PET_LOCUST_SWARM: + return; + default: + break; + } + + if ((st & BOT_COMMAND_FOLLOW) && !IsChanneling() && + ((!me->isMoving() && !IsCasting() && petOwner->GetBotOwner()->IsAlive()) || force)) + { + if (CCed(me, true)) return; + if (me->isMoving() && Rand() > 10) return; + + float x,y,z; + bool dest_valid = petOwner->GetMotionMaster()->GetDestination(x, y, z); + float pdist = dest_valid ? me->GetDistance(x, y, z) : 0.0f; + if (dest_valid && (pdist < 6.f || pdist > 20.f)) + { + if (!me->HasUnitState(UNIT_STATE_FOLLOW)) + me->GetMotionMaster()->MoveFollow(petOwner, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE); + } + else + { + if (!newpos) + _calculatePos(movepos); + else + { + movepos.m_positionX = newpos->m_positionX; + movepos.m_positionY = newpos->m_positionY; + movepos.m_positionZ = newpos->m_positionZ; + } + + float speed = 0.0f; + if (!IAmFree() && !(petOwner->IsWalking() || HasBotCommandState(BOT_COMMAND_WALK))) + { + const float baserunspeed = petOwner->GetSpeed(MOVE_RUN); + if (pdist > 50.0f) + speed = baserunspeed * 2.0f; + else if (pdist > 30.0f) + speed = baserunspeed * 1.5f; + else if (pdist > 10.0f) + speed = baserunspeed * 1.25f; + } + me->GetMotionMaster()->Add(new PointMovementGenerator(1, movepos.m_positionX, movepos.m_positionY, movepos.m_positionZ, true, speed)); + } + RemoveBotCommandState(BOT_COMMAND_STAY | BOT_COMMAND_FULLSTOP | BOT_COMMAND_ATTACK | BOT_COMMAND_COMBATRESET); + } + else if (st & BOT_COMMAND_INACTION) + { + uint32 removeMask = BOT_COMMAND_INACTION & GetBotCommandState(); + st &= ~removeMask; + RemoveBotCommandState(removeMask | BOT_COMMAND_MASK_NOCAST_ANY | BOT_COMMAND_STAY | BOT_COMMAND_FULLSTOP | BOT_COMMAND_ATTACK); + me->AttackStop(); + me->InterruptNonMeleeSpells(true); + } + else if (st & BOT_COMMAND_FULLSTOP) + { + RemoveBotCommandState(BOT_COMMAND_FOLLOW | BOT_COMMAND_STAY | BOT_COMMAND_ATTACK); + me->AttackStop(); + me->InterruptNonMeleeSpells(true); + if (me->isMoving()) + me->BotStopMovement(); + } + else if (st & BOT_COMMAND_STAY) + { + RemoveBotCommandState(BOT_COMMAND_FOLLOW | BOT_COMMAND_FULLSTOP); + if (me->isMoving()) + me->BotStopMovement(); + } + else if (st & BOT_COMMAND_ATTACK) + { + RemoveBotCommandState(BOT_COMMAND_FOLLOW); + } + else if (st & BOT_COMMAND_COMBATRESET) + { + RemoveBotCommandState(BOT_COMMAND_ATTACK); + } + m_botCommandState |= st; +} + +void bot_pet_ai::RemoveBotCommandState(uint32 st) +{ + m_botCommandState &= ~st; +} +// CURES +//cycle through the group sending members for cure +void bot_pet_ai::CureGroup(uint32 cureSpell, uint32 diff) +{ + if (!cureSpell) return; + if (GC_Timer > diff) return; + if (IsCasting()) return; + + if (IAmFree()) + { + std::list cureTargets; + + if (_canCureTarget(me, cureSpell)) + cureTargets.push_back(me); + if (_canCureTarget(petOwner, cureSpell)) + cureTargets.push_back(petOwner); + + if (!cureTargets.empty()) + me->CastSpell(Trinity::Containers::SelectRandomContainerElement(cureTargets), cureSpell, false); + + return; + } + + if (!me->GetMap()->IsRaid() && Rand() > 35) + return; + + std::list targets; + Group const* pGroup = petOwner->GetBotOwner()->GetGroup(); + BotMap const* map; + Unit* u; + if (!pGroup) + { + if (_canCureTarget(petOwner->GetBotOwner(), cureSpell)) + targets.push_back(petOwner->GetBotOwner()); + + map = petOwner->GetBotOwner()->GetBotMgr()->GetBotMap(); + for (BotMap::const_iterator itr = map->begin(); itr != map->end(); ++itr) + { + u = itr->second; + if (!u || !u->IsInWorld() || me->GetMap() != u->FindMap() || !u->IsAlive()) continue; + if (_canCureTarget(u, cureSpell)) + targets.push_back(u); + } + + for (Unit::ControlList::const_iterator itr = petOwner->GetBotOwner()->m_Controlled.begin(); itr != petOwner->GetBotOwner()->m_Controlled.end(); ++itr) + { + u = *itr; + if (!u || !u->IsPet() || !u->IsAlive() || me->GetDistance(u) > 30) continue; + + if (_canCureTarget(u, cureSpell)) + targets.push_back(u); + } + } + else + { + bool Bots = false; + for (GroupReference const* itr = pGroup->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* tPlayer = itr->GetSource(); + if (!tPlayer || (!tPlayer->IsAlive() && !tPlayer->HaveBot())) continue; + if (!tPlayer->IsInWorld() || tPlayer->IsBeingTeleported()) continue; + if (me->GetMap() != tPlayer->FindMap()) continue; + if (!Bots && tPlayer->HaveBot()) + Bots = true; + if (_canCureTarget(tPlayer, cureSpell)) + targets.push_back(tPlayer); + } + if (!Bots) return; + for (GroupReference const* itr = pGroup->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* tPlayer = itr->GetSource(); + if (tPlayer == nullptr) continue; + if (!tPlayer->IsInWorld() || tPlayer->IsBeingTeleported()) continue; + if (me->GetMap() != tPlayer->FindMap()) continue; + + if (tPlayer->HaveBot()) + { + map = tPlayer->GetBotMgr()->GetBotMap(); + for (BotMap::const_iterator bitr = map->begin(); bitr != map->end(); ++bitr) + { + u = bitr->second; + if (!u || !u->IsInWorld() || me->GetMap() != u->FindMap() || !u->IsAlive()) continue; + if (_canCureTarget(u, cureSpell)) + targets.push_back(u); + } + } + + for (Unit::ControlList::const_iterator citr = tPlayer->m_Controlled.begin(); citr != tPlayer->m_Controlled.end(); ++citr) + { + u = *citr; + if (!u || !u->IsPet() || !u->IsAlive() || me->GetDistance(u) > 30) continue; + + if (_canCureTarget(u, cureSpell)) + targets.push_back(u); + } + } + } + + if (!targets.empty()) + me->CastSpell(Trinity::Containers::SelectRandomContainerElement(targets), cureSpell, false); +} + +// determines if unit has something to cure +bool bot_pet_ai::_canCureTarget(Unit const* target, uint32 cureSpell) const +{ + if (me->GetLevel() < 10 || target->GetLevel() < 10) return false; + if (target->HasUnitState(UNIT_STATE_ISOLATED)) return false; + if (target->GetTypeId() == TYPEID_UNIT && target->ToCreature()->IsTempBot()) return false; + + SpellInfo const* info = sSpellMgr->GetSpellInfo(cureSpell); + if (!info) + return false; + + if (me->GetDistance(target) > CalcSpellMaxRange(cureSpell, false)) + return false; + + uint32 dispelMask = 0; + for (uint8 i = 0; i != MAX_SPELL_EFFECTS; ++i) + if (info->_effects[i].Effect == SPELL_EFFECT_DISPEL) + dispelMask |= SpellInfo::GetDispelMask(DispelType(info->_effects[i].MiscValue)); + + if (dispelMask == 0) + return false; + + std::list dispel_list; + _getBotDispellableAuraList(target, me, dispelMask, dispel_list); + + return !(dispel_list.empty()); +} + +void bot_pet_ai::_getBotDispellableAuraList(Unit const* target, Unit const* caster, uint32 dispelMask, std::list &dispelList) const +{ + //Unholy Blight prevents diseases from being dispelled + if ((dispelMask & (1<GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_DEATHKNIGHT, 1494, 0)) + dispelMask &= ~(1<GetOwnedAuras(); + for (Unit::AuraMap::const_iterator itr = auras.begin(); itr != auras.end(); ++itr) + { + Aura const* aura = itr->second; + + if (aura->IsPassive()) + continue; + + AuraApplication const* aurApp = aura->GetApplicationOfTarget(target->GetGUID()); + if (!aurApp) + continue; + + if (aura->GetSpellInfo()->GetDispelMask() & dispelMask) + { + //do not dispel positive auras from enemies and negative ones from friends + if (aurApp->IsPositive() == target->IsFriendlyTo(caster)) + continue; + + //skip Vampiric Touch to prevent being CCed just heal it out + if (aura->GetSpellInfo()->IsRankOf(sSpellMgr->GetSpellInfo(34914))) + continue; + + if (((aura->GetSpellInfo()->AttributesEx7 & SPELL_ATTR7_DISPEL_CHARGES) ? aura->GetCharges() : aura->GetStackAmount()) > 0) + dispelList.push_back(aura); + } + } +} + +uint32 bot_pet_ai::GetData(uint32 data) const +{ + switch (data) + { + case BOTPETAI_MISC_DURATION: + return 0; + case BOTPETAI_MISC_MAXLEVEL: + return petOwner->GetLevel(); + case BOTPETAI_MISC_FIXEDLEVEL: + return 0; + case BOTPETAI_MISC_CARRY: + return 0; + case BOTPETAI_MISC_CAPACITY: + return 0; + case BOTPETAI_MISC_MAX_ATTACKERS: + return 0; + default: + TC_LOG_DEBUG("entities.unit", "bot_pet_ai::GetData(): unk data type {}!", data); + return 0; + } +} + +void bot_pet_ai::SetPetStats(bool force) +{ + switch (myType) + { + //warlock + case BOT_PET_IMP: + case BOT_PET_VOIDWALKER: + case BOT_PET_SUCCUBUS: + case BOT_PET_FELHUNTER: + case BOT_PET_FELGUARD: + //hunter + //cunning + case BOT_PET_SPIDER: + case BOT_PET_SERPENT: + case BOT_PET_BIRDOFPREY: + case BOT_PET_BAT: + case BOT_PET_WINDSERPENT: + case BOT_PET_RAVAGER: + case BOT_PET_DRAGONHAWK: + case BOT_PET_NETHERRAY: + case BOT_PET_SPOREBAT: + //ferocity + case BOT_PET_CARRIONBIRD: + case BOT_PET_RAPTOR: + case BOT_PET_WOLF: + case BOT_PET_TALLSTRIDER: + case BOT_PET_CAT: + case BOT_PET_HYENA: + case BOT_PET_WASP: + case BOT_PET_TEROMOTH: + //tenacity + case BOT_PET_SCORPID: + case BOT_PET_TURTLE: + case BOT_PET_GORILLA: + case BOT_PET_BEAR: + case BOT_PET_BOAR: + case BOT_PET_CRAB: + case BOT_PET_CROCOLISK: + case BOT_PET_WARPSTALKER: + //cunning (exotic) + case BOT_PET_SILITHID: + case BOT_PET_CHIMAERA: + //ferocity (exotic) + case BOT_PET_SPIRITBEAST: + case BOT_PET_COREHOUND: + case BOT_PET_DEVILSAUR: + //tenacity (exotic) + case BOT_PET_RHINO: + case BOT_PET_WORM: + //death knight + case BOT_PET_GHOUL: + //case BOT_PET_GARGOYLE: + //case BOT_PET_DANCING_RUNE_WEAPON: + //case BOT_PET_AOD_GHOUL: + //priest + case BOT_PET_SHADOWFIEND: + //shaman + case BOT_PET_SPIRIT_WOLF: + //mage + case BOT_PET_WATER_ELEMENTAL: + //druid + case BOT_PET_FORCE_OF_NATURE: + //archmage + case BOT_PET_AWATER_ELEMENTAL: + //dreadlord + case BOT_PET_INFERNAL: + //dark ranger + case BOT_PET_DARK_MINION: + case BOT_PET_DARK_MINION_ELITE: + //necromancer + case BOT_PET_NECROSKELETON: + //sea witch + case BOT_PET_TORNADO: + //crypt lord + case BOT_PET_CARRION_BEETLE1: + case BOT_PET_CARRION_BEETLE2: + case BOT_PET_CARRION_BEETLE3: + case BOT_PET_LOCUST_SWARM: + break; + default: + TC_LOG_ERROR("entities.player", "bot_pet_ai::SetPetStats(): unk pet type {}, aborting", myType); + return; + } + + //some time limited summons can only init stats and never change them + switch (myType) + { + case BOT_PET_SHADOWFIEND: + case BOT_PET_SPIRIT_WOLF: + //case BOT_PET_WATER_ELEMENTAL: + case BOT_PET_FORCE_OF_NATURE: + case BOT_PET_DARK_MINION: + case BOT_PET_DARK_MINION_ELITE: + case BOT_PET_NECROSKELETON: + case BOT_PET_TORNADO: + case BOT_PET_CARRION_BEETLE1: + case BOT_PET_CARRION_BEETLE2: + case BOT_PET_CARRION_BEETLE3: + case BOT_PET_LOCUST_SWARM: + if (force == false) + return; + break; + default: + break; + } + + uint8 level = GetData(BOTPETAI_MISC_FIXEDLEVEL); + level = level ? level : std::min(petOwner->GetLevel(), GetData(BOTPETAI_MISC_MAXLEVEL)); + if (level != me->GetLevel()) + { + me->SetLevel(level); + force = true; + } + + int32 spdtotal; + switch (petOwner->GetBotClass()) + { + case BOT_CLASS_WARLOCK: + case BOT_CLASS_PRIEST: + case BOT_CLASS_MAGE: + case BOT_CLASS_DRUID: + case BOT_CLASS_ARCHMAGE: + case BOT_CLASS_DREADLORD: + case BOT_CLASS_SEA_WITCH: + spdtotal = petOwner->SpellBaseDamageBonusDone(SPELL_SCHOOL_MASK_MAGIC); + break; + default: + spdtotal = 0; + break; + } + + if (force) + { + InitPetSpells(); + ApplyPetPassives(); + + me->RemoveAurasDueToSpell(DAMAGEDONE_PASSIVE); + me->RemoveAurasDueToSpell(DAMAGETAKEN_PASSIVE); + me->RemoveAurasDueToSpell(SPELLDAMAGE_PASSIVE); + me->RemoveAurasDueToSpell(SPELLPENETRATION_PASSIVE); + me->RemoveAurasDueToSpell(SPELLHASTE_PASSIVE); + me->RemoveAurasDueToSpell(CRITBONUS_PASSIVE); + + me->CastSpell(me, DAMAGEDONE_PASSIVE, true); + me->CastSpell(me, DAMAGETAKEN_PASSIVE, true); + me->CastSpell(me, SPELLDAMAGE_PASSIVE, true); + me->CastSpell(me, SPELLPENETRATION_PASSIVE, true); + me->CastSpell(me, SPELLHASTE_PASSIVE, true); + me->CastSpell(me, CRITBONUS_PASSIVE, true); + } + + //base stats + uint32 myarmor = 0; + if (force) + { + uint32 origEntry = bot_ai::GetPetOriginalEntry(myType); + CreatureTemplate const* cinfo = sObjectMgr->GetCreatureTemplate(origEntry); + ASSERT(cinfo); + + me->SetMeleeDamageSchool(SpellSchools(cinfo->dmgschool)); + + PetLevelInfo const* pInfo = sObjectMgr->GetPetLevelInfo(origEntry, level); + if (pInfo) + { + me->SetCreateHealth(pInfo->health); + if (petOwner->GetBotClass() == BOT_CLASS_HUNTER) //hunter pet use focus + { + //prevent from modifying powers inside + if (me->GetPowerType() != POWER_FOCUS) + { + me->SetMaxPower(POWER_FOCUS, 100); + me->SetByteValue(UNIT_FIELD_BYTES_0, 3, POWER_FOCUS); + } + } + else if (myType == BOT_PET_GHOUL) + { + if (me->GetPowerType() != POWER_ENERGY) + { + me->SetMaxPower(POWER_ENERGY, 100); + me->SetByteValue(UNIT_FIELD_BYTES_0, 3, POWER_ENERGY); + } + } + else if (myType == BOT_PET_SHADOWFIEND) + { + me->SetCreateHealth(28 + 30*level); + me->SetCreateMana(28 + 10*level); + me->SetPowerType(POWER_MANA); + } + else if (myType == BOT_PET_SPIRIT_WOLF) + { + me->SetCreateHealth(30*level); + me->SetByteValue(UNIT_FIELD_BYTES_0, 3, MAX_POWERS); + } + else if (myType == BOT_PET_FORCE_OF_NATURE) + { + me->SetCreateHealth(30*(level+1)); + me->SetByteValue(UNIT_FIELD_BYTES_0, 3, MAX_POWERS); + } + else if (myType == BOT_PET_DARK_MINION) + { + me->SetCreateHealth(pInfo->health / 4); + me->SetByteValue(UNIT_FIELD_BYTES_0, 3, MAX_POWERS); + } + else if (myType == BOT_PET_DARK_MINION_ELITE) + { + me->SetCreateHealth(pInfo->health / 2); + me->SetByteValue(UNIT_FIELD_BYTES_0, 3, MAX_POWERS); + } + else if (myType == BOT_PET_NECROSKELETON) + { + me->SetCreateHealth(pInfo->health / 5); + me->SetByteValue(UNIT_FIELD_BYTES_0, 3, MAX_POWERS); + } + else if (myType == BOT_PET_AWATER_ELEMENTAL || myType == BOT_PET_INFERNAL) + { + //custom pets / not using mana + me->SetByteValue(UNIT_FIELD_BYTES_0, 3, MAX_POWERS); + } + else if (myType == BOT_PET_CARRION_BEETLE1) + { + me->SetCreateHealth(pInfo->health / 4); + me->SetByteValue(UNIT_FIELD_BYTES_0, 3, MAX_POWERS); + } + else if (myType == BOT_PET_CARRION_BEETLE2) + { + me->SetCreateHealth(pInfo->health / 4); + me->SetByteValue(UNIT_FIELD_BYTES_0, 3, MAX_POWERS); + } + else if (myType == BOT_PET_CARRION_BEETLE3) + { + me->SetCreateHealth(pInfo->health / 3); + me->SetByteValue(UNIT_FIELD_BYTES_0, 3, MAX_POWERS); + } + else + { + me->SetCreateMana(pInfo->mana); + me->SetPowerType(POWER_MANA); + } + + if (pInfo->armor > 0) + myarmor = pInfo->armor; + + for (uint8 i = STAT_STRENGTH; i != MAX_STATS; ++i) + me->SetCreateStat(Stats(i), pInfo->stats[i]); + } + else + { + CreatureBaseStats const* stats = sObjectMgr->GetCreatureBaseStats(level, me->GetCreatureTemplate()->unit_class); + me->SetCreateHealth(stats->BaseHealth[cinfo->expansion]); + me->SetCreateMana(stats->BaseMana); + + me->SetCreateStat(STAT_STRENGTH, level * 3 + 20); + me->SetCreateStat(STAT_AGILITY, level * 2 + 20); + me->SetCreateStat(STAT_STAMINA, level * 5 + 20); + me->SetCreateStat(STAT_INTELLECT, level * 2 + 25); + me->SetCreateStat(STAT_SPIRIT, level * 2 + 30); + TC_LOG_ERROR("entities.unit", "SetPetStats(): pInfo is NULL, setting default stats for pet {}", myType); + } + } + + //STAT INHERITANCE + //STAT -- 'mod' -- description + // WARLOCK + //AP x0.57 -- attack power from spd + //Armor x0.35 -- armor + //Resist x0.4 -- resistances + //Stamina x0.75 -- stamina + //Int x0.3 -- int + //Spd x0.15 -- spd + //Sppen x1.00 -- sppenetration + // HUNTER + //AP x0.22 -- attack power/spd from ranged AP (0.338 wild hunt) + //Stamina x0.4 -- health (0.63 wild hunt) + //Spd x0.1287 -- spd from ranged AP (0.18 wild hunt) + //rest is same as warlock + // DK + //AP x1.52 -- attack power from master's strength + //Stamina x0.88 -- health + //rest is same as warlock + // PRIEST + //Damage from spd + // SHAMAN + //AP x0.6 -- attack power glyphed + //Stamina x0.75 -- health + // MAGE + //Stamina x0.75 -- health + //Spd x0.40 -- spd + //Int x0.3 -- int + // DRUID + //Stamina x0.45 -- health + // + // SHAMAN + // + // ARCHMAGE + //Stamina x2.5 -- stamina + //Spd x1.0 -- spd + //rest is same as warlock + // DREADLORD + //AP x1.00 -- attack power from spd + //Resist x2.0 -- resistances + //Stamina x2.5 -- stamina + //Spd x1.0 -- spd + // DARK RANGER + //AP x0.50 -- attack power + //Resist x0.3 -- resistances + //Stamina x0.8 -- stamina + //rest is same as warlock + // NECROMANCER + //AP x0.40 -- attack power + //Resist x0.25 -- resistances + //Stamina x0.8 -- stamina + //rest is same as warlock + // SEA WITCH + //Spd x1.0 -- spd + //rest is same as warlock + // CRYPT LORD + //AP x0.40 -- attack power + //Resist x1.0 -- resistances + //Stamina x1.5 -- stamina + //Spd x1.0 -- spd + + //attack power + if (force) + { + if (myType == BOT_PET_SHADOWFIEND) + { + me->SetBaseWeaponDamage(BASE_ATTACK, MINDAMAGE, float(level * 3 + spdtotal * 0.3f)); + me->SetBaseWeaponDamage(BASE_ATTACK, MAXDAMAGE, float(level * 5 + spdtotal * 0.3f)); + } + else if (myType == BOT_PET_SPIRIT_WOLF) + { + me->SetBaseWeaponDamage(BASE_ATTACK, MINDAMAGE, float(level * 3)); + me->SetBaseWeaponDamage(BASE_ATTACK, MAXDAMAGE, float(level * 5)); + } + else if (myType == BOT_PET_FORCE_OF_NATURE) + { + me->SetBaseWeaponDamage(BASE_ATTACK, MINDAMAGE, float(level * 2) + spdtotal * 0.15f); + me->SetBaseWeaponDamage(BASE_ATTACK, MAXDAMAGE, float(level * 3) + spdtotal * 0.15f); + } + else if (myType == BOT_PET_DARK_MINION) + { + me->SetBaseWeaponDamage(BASE_ATTACK, MINDAMAGE, float(level)); + me->SetBaseWeaponDamage(BASE_ATTACK, MAXDAMAGE, float(level + level / 2)); + } + else if (myType == BOT_PET_DARK_MINION_ELITE) + { + me->SetBaseWeaponDamage(BASE_ATTACK, MINDAMAGE, float(level * 3)); + me->SetBaseWeaponDamage(BASE_ATTACK, MAXDAMAGE, float(level * 4)); + } + else if (myType == BOT_PET_NECROSKELETON) + { + me->SetBaseWeaponDamage(BASE_ATTACK, MINDAMAGE, float(level)); + me->SetBaseWeaponDamage(BASE_ATTACK, MAXDAMAGE, float(level + level / 3)); + } + else if (myType == BOT_PET_CARRION_BEETLE1) + { + me->SetBaseWeaponDamage(BASE_ATTACK, MINDAMAGE, float(level / 2 + 2)); + me->SetBaseWeaponDamage(BASE_ATTACK, MAXDAMAGE, float(level / 4 * 3 + 2)); + } + else if (myType == BOT_PET_CARRION_BEETLE2) + { + me->SetBaseWeaponDamage(BASE_ATTACK, MINDAMAGE, float(level / 4 * 3 + 8)); + me->SetBaseWeaponDamage(BASE_ATTACK, MAXDAMAGE, float(level + level / 2 + 8)); + } + else if (myType == BOT_PET_CARRION_BEETLE3) + { + me->SetBaseWeaponDamage(BASE_ATTACK, MINDAMAGE, float(level + level / 2 + 10)); + me->SetBaseWeaponDamage(BASE_ATTACK, MAXDAMAGE, float(level + level / 4 * 3 + 15)); + } + else + { + me->SetBaseWeaponDamage(BASE_ATTACK, MINDAMAGE, float(level - (level / 4))); + me->SetBaseWeaponDamage(BASE_ATTACK, MAXDAMAGE, float(level + (level / 4))); + } + } + float atpower = /*IAmFree() ? 1000.f :*/ 0.f; //+1000/+0 base pet ap + switch (myType) + { + case BOT_PET_IMP: + atpower += me->GetTotalStatValue(STAT_STRENGTH) - 10.0f; + break; + case BOT_PET_GHOUL: + atpower += me->GetTotalStatValue(STAT_STRENGTH) - 10.0f; + atpower += 1.52f * petOwner->GetTotalStatValue(STAT_STRENGTH); + atpower += 0.3f * petOwner->GetTotalAttackPowerValue(BASE_ATTACK); + atpower += level * 8; + break; + case BOT_PET_DARK_MINION: + atpower += 2 * me->GetTotalStatValue(STAT_STRENGTH) - 20.0f; + atpower += 0.3f * petOwner->GetTotalAttackPowerValue(RANGED_ATTACK); + break; + case BOT_PET_DARK_MINION_ELITE: + atpower += 2 * me->GetTotalStatValue(STAT_STRENGTH) - 20.0f; + atpower += 0.4f * petOwner->GetTotalAttackPowerValue(RANGED_ATTACK); + break; + case BOT_PET_NECROSKELETON: + atpower += 2 * me->GetTotalStatValue(STAT_STRENGTH) - 20.0f; + break; + case BOT_PET_CARRION_BEETLE1: + atpower += 2 * me->GetTotalStatValue(STAT_STRENGTH) - 20.0f; + atpower += 0.15f * petOwner->GetTotalAttackPowerValue(BASE_ATTACK); + break; + case BOT_PET_CARRION_BEETLE2: + atpower += 2 * me->GetTotalStatValue(STAT_STRENGTH) - 15.0f; + atpower += 0.20f * petOwner->GetTotalAttackPowerValue(BASE_ATTACK); + break; + case BOT_PET_CARRION_BEETLE3: + atpower += 2 * me->GetTotalStatValue(STAT_STRENGTH) - 10.0f; + atpower += 0.25f * petOwner->GetTotalAttackPowerValue(BASE_ATTACK); + break; + default: + //atpower += 2 * me->GetTotalStatValue(STAT_STRENGTH) - 20.0f; + break; + } + switch (petOwner->GetBotClass()) + { + case BOT_CLASS_HUNTER: + atpower += (level >= 80 ? 0.338f : 0.22f) * petOwner->GetTotalAttackPowerValue(RANGED_ATTACK); + //Hunter vs. Wild + if (level >= 30 && Spec() == BOT_SPEC_HUNTER_SURVIVAL) + atpower += 0.3f * petOwner->GetBotAI()->GetTotalBotStat(BOT_STAT_MOD_STAMINA); + //Animal Handler + if (level >= 35 && Spec() == BOT_SPEC_HUNTER_BEASTMASTERY) + atpower *= 1.1f; + break; + case BOT_CLASS_SHAMAN: + //Glyph of Feral Spirit + atpower += 0.6f * petOwner->GetTotalAttackPowerValue(BASE_ATTACK); + break; + case BOT_CLASS_DRUID: + atpower += 300 + 0.3f * petOwner->GetTotalAttackPowerValue(BASE_ATTACK); + break; + case BOT_CLASS_WARLOCK: + atpower += 0.57f * spdtotal; + //TC_LOG_ERROR("entities.player", "SetPetStat(): atpower += 0.57 of {} = {}2f", spdtotal, atpower); + break; + case BOT_CLASS_DREADLORD: + atpower += spdtotal * 6; + break; + case BOT_CLASS_NECROMANCER: + atpower += 0.75f * spdtotal; + break; + default: + break; + } + me->SetStatFlatModifier(UNIT_MOD_ATTACK_POWER, BASE_VALUE, atpower); + me->UpdateAttackPowerAndDamage(); + //armor + myarmor = std::max(myarmor, level*50); + myarmor += me->GetStat(STAT_AGILITY)*2 + petOwner->GetArmor()*0.35f; + //armor bonuses + if (petOwner->GetBotClass() == BOT_CLASS_HUNTER) + { + //5% innate + myarmor += myarmor / 20; + //Thick Hide + if (level >= 15) + myarmor += myarmor / 5; + //Natural Armor + if (level >= 20) + myarmor += myarmor / 10; + //Pet Barding + if (level >= 32) + myarmor += myarmor / 10; + } + if (petOwner->GetBotClass() == BOT_CLASS_DARK_RANGER || petOwner->GetBotClass() == BOT_CLASS_NECROMANCER) + { + //even though skeletons have shields their armor needs to be very low + myarmor = myarmor / 4; + } + else if (petOwner->GetBotClass() == BOT_CLASS_CRYPT_LORD) + { + myarmor /= 3; + } + me->SetStatFlatModifier(UNIT_MOD_ARMOR, BASE_VALUE, float(myarmor)); + me->UpdateArmor(); + //resistances + for (uint8 i = SPELL_SCHOOL_HOLY; i != MAX_SPELL_SCHOOL; ++i) + { + float petResist; + switch (petOwner->GetBotClass()) + { + case BOT_CLASS_DREADLORD: + petResist = (petOwner->GetBotAI()->GetBotResistanceBonus(SpellSchools(i)) + petOwner->GetResistance(SpellSchools(i)))*2.0f; + break; + case BOT_CLASS_DARK_RANGER: + case BOT_CLASS_NECROMANCER: + petResist = (petOwner->GetBotAI()->GetBotResistanceBonus(SpellSchools(i)) + petOwner->GetResistance(SpellSchools(i)))*0.3f; + break; + case BOT_CLASS_CRYPT_LORD: + petResist = (petOwner->GetBotAI()->GetBotResistanceBonus(SpellSchools(i)) + petOwner->GetResistance(SpellSchools(i)))*1.0f; + break; + default: + petResist = (petOwner->GetBotAI()->GetBotResistanceBonus(SpellSchools(i)) + petOwner->GetResistance(SpellSchools(i)))*0.4f; + break; + } + me->SetStatFlatModifier(UnitMods(UNIT_MOD_RESISTANCE_START + i), BASE_VALUE, petResist); + me->UpdateResistances(i); + } + //crit physical + if (AuraEffect* critbonus = me->GetAuraEffect(CRITBONUS_PASSIVE, 1, me->GetGUID())) + { + int32 amount = 5; //base crit + switch (petOwner->GetBotClass()) + { + case BOT_CLASS_WARLOCK: + //Demonic Tactics part 1 (pet) + amount += level >= 45 ? 10 : 0; + //Improved Demonic Tactics (pshysical) + amount += level >= 50 ? petOwner->GetCreatureCritChance() * 0.3f : 0; + break; + case BOT_CLASS_HUNTER: + //Ferocity + if (level >= 20) + amount += 10; + //Spider's Bite + if (level >= 44) + amount += 9; + break; + case BOT_CLASS_DREADLORD: + amount += petOwner->GetCreatureCritChance() * 0.5f; + break; + case BOT_CLASS_DARK_RANGER: + case BOT_CLASS_NECROMANCER: + case BOT_CLASS_CRYPT_LORD: + amount += petOwner->GetCreatureCritChance() * 0.35f; + break; + default: + break; + } + critbonus->ChangeAmount(amount); + } + //crit spell + if (AuraEffect* critbonus = me->GetAuraEffect(CRITBONUS_PASSIVE, 0, me->GetGUID())) + { + int32 amount = 5; //base crit + switch (myType) + { + case BOT_PET_IMP: + case BOT_PET_SUCCUBUS: + //Master Demonologist part 1.2 (pet) + //Master Demonologist part 3.2 (pet) + amount += level >= 35 ? 5 : 0; + //Improved Demonic Tactics (pshysical) + amount += level >= 50 ? petOwner->GetCreatureCritChance() * 0.3f : 0; + break; + default: + break; + } + switch (petOwner->GetBotClass()) + { + case BOT_CLASS_WARLOCK: + //Demonic Tactics part 2 (pet) + amount += level >= 45 ? 10 : 0; + break; + case BOT_CLASS_HUNTER: + //Ferocity + if (level >= 20) + amount += 10; + //Spider's Bite + if (level >= 44) + amount += 9; + break; + case BOT_CLASS_ARCHMAGE: + case BOT_CLASS_SEA_WITCH: + amount += petOwner->GetCreatureCritChance(); + break; + default: + break; + } + critbonus->ChangeAmount(amount); + } + //damage done physical + if (AuraEffect* physdam = me->GetAuraEffect(DAMAGEDONE_PASSIVE, 0, me->GetGUID())) + { + int32 amount = 0; + switch (myType) + { + case BOT_PET_IMP: + //Empowered Imp part 1 + amount += level >= 50 ? 30 : 0; + break; + case BOT_PET_INFERNAL: + amount += 100; + break; + default: + break; + } + switch (petOwner->GetBotClass()) + { + case BOT_CLASS_WARLOCK: + //Unholy Power + if (myType != BOT_PET_IMP) + amount += level >= 30 ? 20 : 0; + break; + case BOT_CLASS_HUNTER: + //Hapiness bonus (always happy) + 5% innate + amount += 30; + //Unleashed Fury + if (level >= 20) + amount += 15; + //Kindred Spirits + if (level >= 55 && Spec() == BOT_SPEC_HUNTER_BEASTMASTERY) + amount += 20; + //Spiked Collar + if (level >= 32) + amount += 9; + //Shark Attack + if (level >= 80) + amount += 6; + break; + default: + break; + } + //Command (Racial) + if (petOwner->GetRace() == RACE_ORC) + amount += 5; + physdam->ChangeAmount(amount); + } + //damage done magic + if (AuraEffect* spelldam = me->GetAuraEffect(DAMAGEDONE_PASSIVE, 1, me->GetGUID())) + { + int32 amount = 0; + switch (myType) + { + case BOT_PET_IMP: + //Empowered Imp part 1, Master Demonologist part 1.2 (pet), Improved Imp part 1 + amount += level >= 50 ? 65 : level >= 35 ? 35 : level >= 10 ? 30 : 0; + break; + case BOT_PET_SUCCUBUS: + //Master Demonologist part 3 (pet) + amount += level >= 35 ? 5 : 0; + break; + default: + break; + } + switch (petOwner->GetBotClass()) + { + case BOT_CLASS_WARLOCK: + //Unholy Power + if (myType == BOT_PET_IMP) + amount += level >= 30 ? 20 : 0; + break; + case BOT_CLASS_HUNTER: + //Hapiness bonus (always happy) + 5% innate + amount += 30; + //Unleashed Fury + if (level >= 20) + amount += 15; + //Kindred Spirits + if (level >= 55 && Spec() == BOT_SPEC_HUNTER_BEASTMASTERY) + amount += 20; + //Spiked Collar + if (level >= 32) + amount += 9; + //Shark Attack + if (level >= 80) + amount += 6; + break; + default: + break; + } + //Command (Racial) + if (petOwner->GetRace() == RACE_ORC) + amount += 5; + spelldam->ChangeAmount(amount); + } + //spell power + if (AuraEffect* spelldam = me->GetAuraEffect(SPELLDAMAGE_PASSIVE, 0, me->GetGUID())) + { + int32 amount = 0; + switch (petOwner->GetBotClass()) + { + case BOT_CLASS_HUNTER: + amount += int32(petOwner->GetTotalAttackPowerValue(RANGED_ATTACK) * (level >= 80 ? 0.18f : 0.1287f)); + break; + case BOT_CLASS_WARLOCK: + amount += int32(spdtotal * 0.15f); + break; + case BOT_CLASS_MAGE: + amount += int32(spdtotal * 0.4f); + break; + case BOT_CLASS_ARCHMAGE: + amount += int32(spdtotal * 1.0f); + break; + case BOT_CLASS_DREADLORD: + amount += int32(spdtotal * 1.0f); + break; + case BOT_CLASS_SEA_WITCH: + amount += int32(spdtotal * 1.0f); + break; + default: + break; + } + spelldam->ChangeAmount(amount); + } + //spell penetration + if (AuraEffect* spellpenet = me->GetAuraEffect(SPELLPENETRATION_PASSIVE, 0, me->GetGUID())) + { + int32 amount = int32(petOwner->GetCreatureSpellPenetration()); + spellpenet->ChangeAmount(amount); + } + //haste spell + if (AuraEffect* spellhaste = me->GetAuraEffect(SPELLHASTE_PASSIVE, 0, me->GetGUID())) + { + int32 amount = 0; + switch (myType) + { + case BOT_PET_IMP: + //Demonic Power part 2 + amount += level >= 20 ? 25 : 0; + break; + case BOT_PET_SUCCUBUS: + //Improved Succubus part 1 + amount += level >= 20 ? 200 : 0; + break; + case BOT_PET_AWATER_ELEMENTAL: + amount += petOwner->GetBotAI()->GetHaste(); + break; + default: + break; + } + spellhaste->ChangeAmount(amount); + } + //dmgtaken + if (AuraEffect* dmgtaken = me->GetAuraEffect(DAMAGETAKEN_PASSIVE, 0, me->GetGUID())) + { + int32 amount = 0; + + if (petOwner->GetBotClass() == BOT_CLASS_WARLOCK) + { + //Demonic Resilience part 2 + if (level >= 40) + amount += 15; + } + if (petOwner->GetBotClass() == BOT_CLASS_HUNTER) + { + //Great Resistance (everything) + if (level >= 44) + amount += 15; + } + if (petOwner->GetBotClass() == BOT_CLASS_DREADLORD) + { + amount += 25; + } + if (petOwner->GetBotClass() == BOT_CLASS_CRYPT_LORD) + { + switch (myType) + { + case BOT_PET_CARRION_BEETLE1: + amount += 10; + break; + case BOT_PET_CARRION_BEETLE2: + amount += 15; + break; + case BOT_PET_CARRION_BEETLE3: + amount += 20; + break; + default: + break; + } + } + + dmgtaken->ChangeAmount(amount); + } + //hp + float stamValue = me->GetTotalStatValue(STAT_STAMINA) - me->GetCreateStat(STAT_STAMINA); + switch (petOwner->GetBotClass()) + { + case BOT_CLASS_HUNTER: + stamValue += (level >= 80 ? 0.63f : 0.4f) * petOwner->GetBotAI()->GetTotalBotStat(BOT_STAT_MOD_STAMINA); + break; + case BOT_CLASS_WARLOCK: + stamValue += 0.75f * petOwner->GetBotAI()->GetTotalBotStat(BOT_STAT_MOD_STAMINA); + break; + case BOT_CLASS_DEATH_KNIGHT: + switch (myType) + { + case BOT_PET_GHOUL: + stamValue += 0.88f * petOwner->GetBotAI()->GetTotalBotStat(BOT_STAT_MOD_STAMINA); + break; + default: + stamValue += 0.3f * petOwner->GetBotAI()->GetTotalBotStat(BOT_STAT_MOD_STAMINA); + break; + } + break; + case BOT_CLASS_SHAMAN: + stamValue += 0.75f * petOwner->GetBotAI()->GetTotalBotStat(BOT_STAT_MOD_STAMINA); + break; + case BOT_CLASS_MAGE: + stamValue += 0.75f * petOwner->GetBotAI()->GetTotalBotStat(BOT_STAT_MOD_STAMINA); + break; + case BOT_CLASS_DRUID: + stamValue += 0.45f * petOwner->GetBotAI()->GetTotalBotStat(BOT_STAT_MOD_STAMINA); + break; + case BOT_CLASS_ARCHMAGE: + stamValue += 2.50f * petOwner->GetBotAI()->GetTotalBotStat(BOT_STAT_MOD_STAMINA); + break; + case BOT_CLASS_DREADLORD: + stamValue += 2.50f * petOwner->GetBotAI()->GetTotalBotStat(BOT_STAT_MOD_STAMINA); + break; + case BOT_CLASS_DARK_RANGER: + switch (myType) + { + case BOT_PET_DARK_MINION_ELITE: + stamValue += 1.0f * petOwner->GetBotAI()->GetTotalBotStat(BOT_STAT_MOD_STAMINA); + break; + default: + stamValue += 0.8f * petOwner->GetBotAI()->GetTotalBotStat(BOT_STAT_MOD_STAMINA); + break; + } + break; + case BOT_CLASS_NECROMANCER: + stamValue += 0.75f * petOwner->GetBotAI()->GetTotalBotStat(BOT_STAT_MOD_STAMINA); + break; + case BOT_CLASS_CRYPT_LORD: + switch (myType) + { + case BOT_PET_CARRION_BEETLE1: + stamValue += 0.25f * petOwner->GetBotAI()->GetTotalBotStat(BOT_STAT_MOD_STAMINA); + break; + case BOT_PET_CARRION_BEETLE2: + stamValue += 0.40f * petOwner->GetBotAI()->GetTotalBotStat(BOT_STAT_MOD_STAMINA); + break; + case BOT_PET_CARRION_BEETLE3: + stamValue += 0.70f * petOwner->GetBotAI()->GetTotalBotStat(BOT_STAT_MOD_STAMINA); + break; + default: + break; + } + break; + default: + break; + } + float stamMult; + switch (myType) + { + case BOT_PET_IMP: stamMult = 8.4f; break; + case BOT_PET_VOIDWALKER: stamMult = 11.0f; break; + case BOT_PET_SUCCUBUS: stamMult = 9.1f; break; + case BOT_PET_FELHUNTER: stamMult = 9.5f; break; + case BOT_PET_FELGUARD: stamMult = 11.0f; break; + default: stamMult = 10.f; break; + } + //stam bonuses + if (petOwner->GetBotClass() == BOT_CLASS_WARLOCK) + { + //Fel Vitality (pet) part 1 + if (level >= 15) + stamValue *= 1.15f; + //Glyph of Voidwalker + if (myType == BOT_PET_VOIDWALKER && level >= 15) + stamValue *= 1.2f; + } + if (petOwner->GetBotClass() == BOT_CLASS_HUNTER) + { + //Endurance Training + if (level >= 10) + stamValue *= 1.1f; + //Greater Stamina + if (level >= 20) + stamValue *= 1.12f; + //Blood of the Rhino part 1 + if (level >= 32) + stamValue *= 1.04f; + } + //additional: store stat + me->SetStat(STAT_STAMINA, int32(stamValue)); + float m_totalhp = stamValue * stamMult + me->GetCreateHealth() + (/*IAmFree() ? level * 125.f :*/ 0); //+10000/+0 hp at 80 + //hp bonuses + if (petOwner->GetBotClass() == BOT_CLASS_HUNTER) + { + //Innate 5% + m_totalhp *= 1.05f; + } + //TC_LOG_ERROR("entities.player", "SetPetStat(): hp stamval {}1f, stammult {}1f, base {}, total {}2f", stamValue, stamMult, botPet->GetCreateHealth(), m_totalhp); + bool fullhp = me->GetHealth() == me->GetMaxHealth(); + float pct = fullhp ? 100.f : me->GetHealthPct(); // needs for regeneration + me->SetStatFlatModifier(UNIT_MOD_HEALTH, BASE_VALUE, m_totalhp); + me->UpdateMaxHealth(); + me->SetHealth(fullhp ? me->GetMaxHealth() : uint32(0.5f + float(me->GetMaxHealth()) * pct / 100.f)); //restore pct + //mana + if (me->GetPowerType() == POWER_MANA) + { + float intValue = me->GetTotalStatValue(STAT_INTELLECT) - me->GetCreateStat(STAT_INTELLECT); + intValue += 0.3f * petOwner->GetBotAI()->GetTotalBotStat(BOT_STAT_MOD_INTELLECT); + float intMult; + switch (myType) + { + case BOT_PET_IMP: intMult = 4.95f; break; + case BOT_PET_VOIDWALKER: + case BOT_PET_SUCCUBUS: + case BOT_PET_FELHUNTER: + case BOT_PET_FELGUARD: intMult = 11.5f; break; + default: intMult = 15.f; break; + } + //int/mana bonuses + if (petOwner->GetBotClass() == BOT_CLASS_WARLOCK) + { + //Fel Vitality (pet) part 2 + if (level >= 15) + intValue *= 1.15f; + } + //additional: store stat + me->SetStat(STAT_INTELLECT, int32(intValue)); + float m_totalmana = intValue * intMult/* + me->GetCreatePowerValue(POWER_MANA)*/ + (IAmFree() ? level * 25.f : 0); //+2000/+0 mana at 80 + //TC_LOG_ERROR("entities.player", "SetPetStat(): mana intValue {}1f, intMult {}1f, base {}, total {}2f", intValue, intMult, botPet->GetCreatePowerValue(POWER_MANA), m_totalmana); + bool fullmana = me->GetPower(POWER_MANA) == me->GetMaxPower(POWER_MANA); + pct = fullmana ? 100.f : (float(me->GetPower(POWER_MANA)) * 100.f) / float(me->GetMaxPower(POWER_MANA)); + me->SetStatFlatModifier(UNIT_MOD_MANA, BASE_VALUE, m_totalmana); + me->UpdateMaxPower(POWER_MANA); + me->SetPower(POWER_MANA, fullmana ? me->GetMaxPower(POWER_MANA) : + uint32(0.5f + float(me->GetMaxPower(POWER_MANA)) * pct / 100.f)); //restore pct + } + + if (force) + { + me->SetFullHealth(); + if (me->GetPowerType() == POWER_MANA) + me->SetPower(POWER_MANA, me->GetMaxPower(POWER_MANA)); + else if (me->GetPowerType() == POWER_FOCUS) + me->SetPower(POWER_FOCUS, me->GetMaxPower(POWER_FOCUS)); + } +} +//Force pet to start attack anyone who tries to DAMAGE me or owner +//This means that anyone who attacks party will be attacked by whole bot party (see GetTarget()) +void bot_pet_ai::OnOwnerDamagedBy(Unit* attacker) +{ + switch (myType) + { + case BOT_PET_TORNADO: + case BOT_PET_LOCUST_SWARM: + return; + default: + break; + } + + if (petOwner->GetBotAI()->HasBotCommandState(BOT_COMMAND_MASK_UNMOVING)) + return; + if (!petOwner->GetBotAI()->CanBotAttack(attacker)) + return; + + SetBotCommandState(BOT_COMMAND_COMBATRESET); + me->Attack(attacker, IsPetMelee()); +} + +bool bot_pet_ai::IsPetMelee() const +{ + return bot_ai::IsPetMelee(myType); +} + +uint8 bot_pet_ai::Spec() const +{ + return petOwner->GetBotAI()->GetSpec(); +} + +//ISINBOTPARTY +//Returns group members (and their npcbots too) +//For now all your puppets are in your group automatically +bool bot_pet_ai::IsInBotParty(Unit const* unit) const +{ + if (!unit) return false; + if (unit == petOwner->GetBotOwner() || unit == me || unit == petOwner) return true; + + if (IAmFree()) + { + if (me->GetFaction() == 14 || unit->GetFaction() == 14) + return false; + + if (me->HasByteFlag(UNIT_FIELD_BYTES_2, 1, UNIT_BYTE2_FLAG_FFA_PVP) || + unit->HasByteFlag(UNIT_FIELD_BYTES_2, 1, UNIT_BYTE2_FLAG_FFA_PVP)) + return false; + + return + (unit->GetTypeId() == TYPEID_PLAYER || unit->ToCreature()->IsPet() || unit->IsNPCBot() || unit->IsNPCBotPet()) && + (unit->GetFaction() == me->GetFaction() || + (me->GetReactionTo(unit) >= REP_FRIENDLY && unit->GetReactionTo(me) >= REP_FRIENDLY)); + } + + //cheap check + if (Group const* gr = petOwner->GetBotOwner()->GetGroup()) + { + //group member case + if (gr->IsMember(unit->GetGUID())) + return true; + //pointed target case + for (uint8 i = 0; i != TARGET_ICONS_COUNT; ++i) + if (BotMgr::GetHealTargetIconFlags() & GroupIconsFlags[i] && + !((BotMgr::GetOffTankTargetIconFlags() | BotMgr::GetDPSTargetIconFlags()) & GroupIconsFlags[i])) + if (ObjectGuid guid = gr->GetTargetIcons()[i]) + if (guid == unit->GetGUID()) + return true; + } + + //Player-controlled creature case + if (Creature const* cre = unit->ToCreature()) + { + ObjectGuid ownerGuid = unit->GetOwnerGUID() ? unit->GetOwnerGUID() : unit->GetCreator() ? unit->GetCreator()->GetGUID() : ObjectGuid::Empty; + //controlled by master + if (ownerGuid == petOwner->GetBotOwner()->GetGUID()) + return true; + //npcbot/npcbot's pet case + if (cre->GetBotOwner() == petOwner->GetBotOwner()) + return true; + if (ownerGuid && petOwner->GetBotOwner()->GetBotMgr()->GetBot(ownerGuid)) + return true; + //controlled by group member + //pets, minions, guardians etc. + //bot pets too + if (ownerGuid) + if (Group const* gr = petOwner->GetBotOwner()->GetGroup()) + if (gr->IsMember(ownerGuid)) + return true; + } + + return false; +} + +//REFRESHAURA +//Applies/removes/reapplies aura stacks +void bot_pet_ai::RefreshAura(uint32 spellId, int8 count, Unit* target) const +{ + if (count < 0 || count > 10) + { + TC_LOG_ERROR("entities.player", "bot_pet_ai::RefreshAura(): count is out of bounds ({}) for bot {} (botclass: {}, entry: {})", + int32(count), me->GetName(), uint32(petOwner->GetBotClass()), me->GetEntry()); + return; + } + if (!spellId) + { + TC_LOG_ERROR("entities.player", "bot_pet_ai::RefreshAura(): spellId is 0 for bot {} (botclass: {}, entry: {})", + me->GetName(), uint32(petOwner->GetBotClass()), me->GetEntry()); + return; + } + + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); + if (!spellInfo) + { + TC_LOG_ERROR("entities.player", "bot_pet_ai::RefreshAura(): Invalid spellInfo for spell {}! Bot - {} (botclass: {}, entry: {})", + spellId, me->GetName(), uint32(petOwner->GetBotClass()), me->GetEntry()); + return; + } + + if (!target) + target = me; + + target->RemoveAurasDueToSpell(spellId); + + for (int8 i = 0; i < count; ++i) + target->AddAura(spellInfo, MAX_EFFECT_MASK, target); +} +//GETTARGET +//Returns attack target or 'no target' +//All code above 'x = _getTarget() call must not dereference opponent since it can be invalid +Unit* bot_pet_ai::_getTarget(bool &reset) const +{ + if (petOwner->GetBotAI()->HasBotCommandState(BOT_COMMAND_FULLSTOP | BOT_COMMAND_INACTION)) + return nullptr; + if (petOwner->GetBotAI()->GetEngageTimer() > lastdiff) + return nullptr; + + Unit* mytar = me->GetVictim(); + Unit* u = petOwner->GetVictim(); + + if (mytar && me->HasAuraType(SPELL_AURA_MOD_TAUNT)) + return mytar; + + if (u) + { + if (opponent && u != opponent) + reset = true; + return u; + } + + uint8 followdist = IAmFree() ? BotMgr::GetBotFollowDistDefault() : petOwner->GetBotOwner()->GetBotMgr()->GetBotFollowDist(); + + if (followdist == 0) + return nullptr; + + float foldist = _getAttackDistance(float(followdist)); + if (!IAmFree() && !IsPetMelee()) + { + float spelldist; + uint8 rangeMode = petOwner->GetBotOwner()->GetBotMgr()->GetBotAttackRangeMode(); + if (rangeMode == BOT_ATTACK_RANGE_EXACT) + spelldist = petOwner->GetBotOwner()->GetBotMgr()->GetBotExactAttackRange(); + else + spelldist = GetSpellAttackRange(rangeMode == BOT_ATTACK_RANGE_LONG); + foldist = std::max(foldist, spelldist + 4.f); + } + bool dropTarget = false; + if (!dropTarget && mytar) + { + dropTarget = IAmFree() ? + petOwner->GetDistance(mytar) > foldist : + (petOwner->GetBotOwner()->GetDistance(mytar) > foldist || (petOwner->GetBotOwner()->GetDistance(mytar) > foldist * 0.75f && !mytar->IsWithinLOSInMap(petOwner, LINEOFSIGHT_ALL_CHECKS, VMAP::ModelIgnoreFlags::M2))); + } + if (dropTarget) + return nullptr; + + if (mytar && (!IAmFree() || me->GetDistance(mytar) < float(BOT_MAX_CHASE_RANGE)) && me->IsValidAttackTarget(mytar) && !petOwner->GetBotAI()->IsPointedNoDPSTarget(mytar)) + { + if (me->GetDistance(mytar) > (!IsPetMelee() ? 20.f : 5.f) && m_botCommandState != COMMAND_STAY && m_botCommandState != COMMAND_FOLLOW) + reset = true; + return mytar; + } + + return nullptr; +} +//'CanAttack' function +//Only called in class ai UpdateAI function +bool bot_pet_ai::CheckAttackTarget() +{ + bool reset = false; + opponent = _getTarget(reset); + + if (!opponent) + { + if (me->GetVictim() || me->IsInCombat()) + { + if (me->GetVictim()) + me->AttackStop(); + } + + return false; + } + if (petOwner->GetBotAI()->IsLastOrder(BOT_ORDER_PULL, 0, opponent->GetGUID())) + return false; + + if (reset) + SetBotCommandState(BOT_COMMAND_COMBATRESET);//reset AttackStart() + + if (opponent != me->GetVictim()) + me->Attack(opponent, IsPetMelee()); + + return true; +} +//POSITION +//Ranged attack position +void bot_pet_ai::CalculateAttackPos(Unit* target, Position& pos) const +{ + uint8 followdist = IAmFree() ? BotMgr::GetBotFollowDistDefault() : petOwner->GetBotOwner()->GetBotMgr()->GetBotFollowDist(); + uint8 rangeMode = IAmFree() ? uint8(BOT_ATTACK_RANGE_LONG) : petOwner->GetBotOwner()->GetBotMgr()->GetBotAttackRangeMode(); + uint8 exactRange = rangeMode != BOT_ATTACK_RANGE_EXACT || IAmFree() ? 255 : petOwner->GetBotOwner()->GetBotMgr()->GetBotExactAttackRange(); + Position ppos; + float //x(0),y(0),z(0), + dist = (rangeMode == BOT_ATTACK_RANGE_EXACT) ? exactRange : + followdist >= 40 ? followdist : + 5 + urand(followdist/3, followdist/3 + 5)/*18-23 at 40, 15-20 at 30*/, + angle = target->GetAbsoluteAngle(me); + //most ranged classes have some sort of 20yd spell + if (rangeMode != BOT_ATTACK_RANGE_EXACT) + dist = std::min(dist, petOwner->GetBotAI()->HasRole(BOT_ROLE_DPS) ? GetSpellAttackRange(rangeMode == BOT_ATTACK_RANGE_LONG) - 4.f : 30.f); + + float clockwise = (me->GetEntry() % 2) ? 1.f : -1.f; + float angleDelta = frand(0.0f, float(M_PI)*0.10f) * clockwise; + + for (uint8 i = 0; i != 5; ++i) + { + ppos = target->GetFirstCollisionPosition(dist, angle - target->GetOrientation()); + //target->GetNearPoint(me, x, y, z, dist, angle); + if (!target->IsWithinLOS(/*x,y,z*/ppos.m_positionX, ppos.m_positionY, ppos.m_positionZ)) + { + if (rangeMode != BOT_ATTACK_RANGE_EXACT) + dist *= i >= 4 ? 0.1f : 0.33f; + if (i >= 4) + angle += angleDelta; + } + } + + pos.Relocate(ppos); + //pos.m_positionX = x; + //pos.m_positionY = y; + //pos.m_positionZ = z; +} +// Forces bot to chase opponent (if ranged then distance depends on follow distance) +void bot_pet_ai::GetInPosition(bool force, Unit* newtarget, Position* mypos) +{ + if (petOwner->GetBotAI()->HasBotCommandState(BOT_COMMAND_MASK_UNMOVING)) + return; + if (CCed(me, true) || JumpingOrFalling()) + return; + if (!newtarget) + newtarget = me->GetVictim(); + if (!newtarget) + return; + if ((!newtarget->IsInCombat() || me->isMoving()) && !force) + return; + if (IsCasting()) + return; + + if (!IAmFree() && petOwner->GetBotOwner()->GetBotMgr()->GetBotAttackRangeMode() == BOT_ATTACK_RANGE_EXACT && + petOwner->GetBotOwner()->GetBotMgr()->GetBotExactAttackRange() == 0) + { + attackpos.m_positionX = newtarget->GetPositionX() - frand(0.5f, 1.5f) * std::cos(me->GetAbsoluteAngle(newtarget)); + attackpos.m_positionY = newtarget->GetPositionY() - frand(0.5f, 1.5f) * std::sin(me->GetAbsoluteAngle(newtarget)); + attackpos.m_positionZ = newtarget->GetPositionZ(); + if (me->GetExactDist2d(&attackpos) > 3.5f) + me->GetMotionMaster()->MovePoint(newtarget->GetMapId(), attackpos); + return; + } + + uint8 followdist = IAmFree() ? BotMgr::GetBotFollowDistDefault() : petOwner->GetBotOwner()->GetBotMgr()->GetBotFollowDist(); + if (!IsPetMelee()) + { + //do not allow constant runaway from player + if (!force && newtarget->GetTypeId() == TYPEID_PLAYER && + me->GetDistance(newtarget) < 6 + urand(followdist/4, followdist/3)) + return; + + if (!mypos) + CalculateAttackPos(newtarget, attackpos); + else + { + attackpos.m_positionX = mypos->m_positionX; + attackpos.m_positionY = mypos->m_positionY; + attackpos.m_positionZ = mypos->m_positionZ; + } + if (me->GetExactDist2d(&attackpos) > 4.f || !me->IsWithinLOSInMap(newtarget, LINEOFSIGHT_ALL_CHECKS, VMAP::ModelIgnoreFlags::M2)) + { + me->GetMotionMaster()->MovePoint(newtarget->GetMapId(), attackpos); + if (!me->HasUnitState(UNIT_STATE_MELEE_ATTACKING)) + me->SetInFront(newtarget); + } + } + else if (!JumpingOrFalling() && ((!me->HasUnitState(UNIT_STATE_CHASE) && !me->isMoving()) || (!me->HasUnitState(UNIT_STATE_CHASE_MOVE) && me->GetDistance(newtarget) > 1.5f))) + { + //me->BotStopMovement(); + me->GetMotionMaster()->MoveChase(newtarget); + } + + if (newtarget != me->GetVictim()) + { + if (!me->Attack(newtarget, IsPetMelee())) + me->SetInFront(newtarget); + } +} + +void bot_pet_ai::CheckAttackState() +{ + if (me->GetVictim()) + { + MoveBehind(me->GetVictim()); + + if (petOwner->GetBotAI()->HasRole(BOT_ROLE_DPS) && + !me->HasAuraType(SPELL_AURA_MOD_STEALTH) && !me->HasAuraType(SPELL_AURA_MOD_INVISIBILITY)) + { + //if (!CCed(me->GetVictim()) || me->HasAuraType(SPELL_AURA_MOD_TAUNT)) + DoMeleeAttackIfReady(); + } + } +} + +void bot_pet_ai::MoveBehind(Unit const* target) const +{ + if (_moveBehindTimer > lastdiff || HasBotCommandState(BOT_COMMAND_MASK_UNMOVING) || !IsPetMelee() || CCed(me, true) || JumpingOrFalling()) + return; + + if (target->GetVictim() != me && !CCed(target) && target->IsWithinCombatRange(me, ATTACK_DISTANCE) && target->HasInArc(float(M_PI), me)) + { + float x,y,z; + target->GetNearPoint(me, x, y, z, me->GetCombatReach(), me->GetAbsoluteAngle(target)); + me->GetMotionMaster()->MovePoint(me->GetMapId(), x, y, z); + const_cast(this)->_moveBehindTimer = urand(1000, 4000); + } +} +bool bot_pet_ai::_canRegenerate() const +{ + switch (me->GetEntry()) + { + case BOT_PET_DARK_MINION: + case BOT_PET_DARK_MINION_ELITE: + case BOT_PET_NECROSKELETON: + case BOT_PET_TORNADO: + case BOT_PET_CARRION_BEETLE1: + case BOT_PET_CARRION_BEETLE2: + case BOT_PET_CARRION_BEETLE3: + case BOT_PET_LOCUST_SWARM: + return false; + default: + return true; + } +} +//Health and Powers regeneration +void bot_pet_ai::Regenerate() +{ + regenTimer += lastdiff; + + //every tick + if (me->GetPowerType() == POWER_FOCUS) + RegeneratePetFocus(); + else if (me->GetPowerType() == POWER_ENERGY) + RegeneratePetEnergy(); + + if (regenTimer >= REGEN_CD) + { + regenTimer -= REGEN_CD; + + // Regen Pet Health + if (_canRegenerate() && (!me->IsInCombat() || me->IsPolymorphed()) && me->GetHealth() < me->GetMaxHealth()) + { + int32 add = IAmFree() && !me->GetVictim() ? me->GetMaxHealth() / 32 : 20 + me->GetCreateHealth() / 64; + + if (me->IsPolymorphed()) + add += me->GetMaxHealth() / 6; + else if (!me->IsInCombat() || me->HasAuraType(SPELL_AURA_MOD_REGEN_DURING_COMBAT)) + { + if (!me->IsInCombat()) + { + Unit::AuraEffectList const& mModHealthRegenPct = me->GetAuraEffectsByType(SPELL_AURA_MOD_HEALTH_REGEN_PERCENT); + for (Unit::AuraEffectList::const_iterator i = mModHealthRegenPct.begin(); i != mModHealthRegenPct.end(); ++i) + AddPct(add, (*i)->GetAmount()); + + add += me->GetTotalAuraModifier(SPELL_AURA_MOD_REGEN) * REGEN_CD / 5000; + } + else if (me->HasAuraType(SPELL_AURA_MOD_REGEN_DURING_COMBAT)) + ApplyPct(add, me->GetTotalAuraModifier(SPELL_AURA_MOD_REGEN_DURING_COMBAT)); + } + + add += me->GetTotalAuraModifier(SPELL_AURA_MOD_HEALTH_REGEN_IN_COMBAT); + + if (add < 0) + add = 0; + + me->ModifyHealth(add); + } + // Regen Pet Mana (use bot's regen rate) warlock only + if (me->GetPowerType() == POWER_MANA && me->GetPower(POWER_MANA) < me->GetMaxPower(POWER_MANA)) + { + float addvalue; + if (me->IsUnderLastManaUseEffect()) + addvalue = petOwner->GetFloatValue(UNIT_FIELD_POWER_REGEN_INTERRUPTED_FLAT_MODIFIER); + else + addvalue = petOwner->GetFloatValue(UNIT_FIELD_POWER_REGEN_FLAT_MODIFIER); + + addvalue *= sWorld->getRate(RATE_POWER_MANA) * float(REGEN_CD) * 0.001f; //regenTimer threshold / 1000 + if (addvalue < 0.0f) + addvalue = 0.0f; + + me->ModifyPower(POWER_MANA, int32(addvalue)); + } + } +} + +void bot_pet_ai::RegeneratePetFocus() +{ + uint32 curValue = me->GetPower(POWER_FOCUS); + uint32 maxValue = me->GetMaxPower(POWER_FOCUS); + + // Regen Pet Focus + if (curValue < maxValue) + { + float addvalue = 0.005f * lastdiff * sWorld->getRate(RATE_POWER_FOCUS); //5 per sec + + //Bestial Discipline + if (petOwner->GetLevel() >= 30) + addvalue *= 2; + + addvalue += _energyFraction; + + if (addvalue == 0x0) //only if world rate for focus is 0 + return; + + uint32 integerValue = uint32(fabs(addvalue)); + + curValue += integerValue; + + if (curValue > maxValue) + { + curValue = maxValue; + _energyFraction = 0.f; + } + else + _energyFraction = addvalue - float(integerValue); + + if (curValue == maxValue || regenTimer >= REGEN_CD) + me->SetPower(POWER_FOCUS, curValue); + else + me->UpdateUInt32Value(UNIT_FIELD_POWER1 + uint16(POWER_FOCUS), curValue); + } +} + +void bot_pet_ai::RegeneratePetEnergy() +{ + uint32 curValue = me->GetPower(POWER_ENERGY); + uint32 maxValue = me->GetMaxPower(POWER_ENERGY); + + if (curValue < maxValue) + { + float addvalue = 0.01f * lastdiff * sWorld->getRate(RATE_POWER_ENERGY); //10 per sec + + if (addvalue == 0x0) //only if world rate for enegy is 0 + return; + + addvalue += _energyFraction; + + uint32 integerValue = uint32(fabs(addvalue)); + + curValue += integerValue; + + if (curValue > maxValue) + { + curValue = maxValue; + _energyFraction = 0.f; + } + else + _energyFraction = addvalue - float(integerValue); + + if (curValue == maxValue || regenTimer >= REGEN_CD) + me->SetPower(POWER_ENERGY, curValue); + else + me->UpdateUInt32Value(UNIT_FIELD_POWER1 + uint16(POWER_ENERGY), curValue); + } +} +////////// +//SPELLMAP +////////// +//Using first-rank spell as source, returns spellId of max rank allowed for given caster +//If you want bot to use this spell through doCast() go InitSpellMap(uint32) instead +uint32 bot_pet_ai::InitSpell(Unit const* caster, uint32 spell) +{ + SpellInfo const* info = sSpellMgr->GetSpellInfo(spell); + if (!info) + { + TC_LOG_ERROR("entities.player", "InitSpell(): No SpellInfo found for spell {}", spell); + return 0; //weird spell with no info, disable it + } + + uint8 lvl = caster->GetLevel(); + if (lvl < info->BaseLevel) //only 1st rank spells check + return 0; //cannot use this spell + + if (SpellInfo const* spInfo = info->GetNextRankSpell()) + { + if (lvl < spInfo->BaseLevel) + return spell; //cannot use next rank, use this one + else + return InitSpell(caster, spInfo->Id); //can use next rank, forward check + } + + return spell; //max rank, use this +} +//Using first-rank spell as source, puts spell of max rank allowed for given caster in spellmap +void bot_pet_ai::InitSpellMap(uint32 basespell, bool forceadd, bool forwardRank) +{ + SpellInfo const* info = sSpellMgr->GetSpellInfo(basespell); + if (!info) + { + TC_LOG_ERROR("entities.player", "bot_pet_ai::InitSpellMap(): No SpellInfo found for base spell {}", basespell); + return; //invalid spell id + } + + uint8 lvl = me->GetLevel(); + uint32 spellId = forceadd ? basespell : 0; + + while (info != nullptr && forwardRank && (forceadd || lvl >= info->BaseLevel)) + { + spellId = info->Id; //can use this spell + info = info->GetNextRankSpell(); //check next rank + } + + BotPetSpell* newSpell = _spells[basespell]; + if (!newSpell) + { + newSpell = new BotPetSpell(); + _spells[basespell] = newSpell; + } + + newSpell->spellId = spellId; +} +//Using first-rank spell as source, return current spell id +uint32 bot_pet_ai::GetSpell(uint32 basespell) const +{ + BotPetSpellMap::const_iterator itr = _spells.find(basespell); + return itr != _spells.end() && (itr->second->enabled == true || IAmFree()) ? itr->second->spellId : 0; +} +//Using first-rank spell as source, returns cooldown on current spell +uint32 bot_pet_ai::GetSpellCooldown(uint32 basespell) const +{ + BotPetSpellMap::const_iterator itr = _spells.find(basespell); + return itr != _spells.end() ? itr->second->cooldown : 0; +} +bool bot_pet_ai::IsSpellReady(uint32 basespell, uint32 diff, bool checkGCD) const +{ + if (checkGCD && GC_Timer > diff) + return false; + + BotPetSpellMap::const_iterator itr = _spells.find(basespell); + return itr == _spells.end() ? true : + ((itr->second->enabled == true || IAmFree()) && itr->second->spellId != 0 && itr->second->cooldown <= diff); +} +//Using first-rank spell as source, sets cooldown for current spell +void bot_pet_ai::SetSpellCooldown(uint32 basespell, uint32 msCooldown) +{ + //if (!msCooldown) + // return; + + BotPetSpellMap::iterator itr = _spells.find(basespell); + if (itr != _spells.end()) + { + itr->second->cooldown = msCooldown; + return; + } + else if (!msCooldown) + return; + + InitSpellMap(basespell, true, false); + SetSpellCooldown(basespell, msCooldown); +} +//Using first-rank spell as source, sets cooldown for spells of that category +void bot_pet_ai::SetSpellCategoryCooldown(SpellInfo const* spellInfo, uint32 msCooldown) +{ + if (!msCooldown) + return; + + uint32 category = spellInfo->GetCategory(); + if (!category) + return; + + SpellInfo const* info; + for (BotPetSpellMap::iterator itr = _spells.begin(); itr != _spells.end(); ++itr) + { + //skip spell which has triggered this category cooldown + if (itr->first == spellInfo->Id && itr->second->cooldown >= msCooldown) + continue; + + info = sSpellMgr->GetSpellInfo(itr->second->spellId); + if (info && itr->first == spellInfo->Id && info->GetCategory() != category) + { + if (itr->first != 7814) // Lash of Pain + { + TC_LOG_ERROR("scripts", "Warning: SetSpellCategoryCooldown: {} has baseId {} but category {}, not {}!", + info->Id, itr->first, info->GetCategory(), category); + } + } + + if (info && (info->GetCategory() == category || itr->first == spellInfo->Id) && itr->second->cooldown < msCooldown) + itr->second->cooldown = msCooldown; + } +} +//Handles spell cooldowns for spell with IsCooldownStartedOnEvent() == true +void bot_pet_ai::ReleaseSpellCooldown(uint32 basespell) +{ + SpellInfo const* baseInfo = sSpellMgr->GetSpellInfo(basespell); + + if (!baseInfo->IsCooldownStartedOnEvent()) + { + TC_LOG_ERROR("spells", "bot_pet_ai::ReleaseSpellCooldown is called for wrong spell {}!", basespell); + return; + } + + uint32 rec = baseInfo->RecoveryTime; + uint32 catrec = baseInfo->CategoryRecoveryTime; + + SetSpellCooldown(baseInfo->Id, rec > 0 ? rec : 0); + SetSpellCategoryCooldown(baseInfo, catrec > 0 && !(baseInfo->AttributesEx6 & SPELL_ATTR6_IGNORE_CATEGORY_COOLDOWN_MODS) ? catrec : 0); +} +//Using first-rank spell as source, disables certain spell for this bot +void bot_pet_ai::RemoveSpell(uint32 basespell) +{ + BotPetSpell* newSpell; + BotPetSpellMap::iterator itr = _spells.find(basespell); + if (itr == _spells.end()) + { + newSpell = new BotPetSpell(); + _spells[basespell] = newSpell; + } + else + newSpell = itr->second; + + newSpell->spellId = 0; + newSpell->cooldown = 0; +} +//See CommonTimers(uint32) +void bot_pet_ai::SpellTimers(uint32 diff) +{ + // spell must be initialized!!! + for (BotPetSpellMap::iterator itr = _spells.begin(); itr != _spells.end(); ++itr) + { + if (itr->second->cooldown >= diff) + itr->second->cooldown -= diff; + else if (itr->second->cooldown > 0) + itr->second->cooldown = 0; + } +} +//Bots cannot dodge/parry from behind so try to condense enemies at front +//opponent is always valid +void bot_pet_ai::AdjustTankingPosition() const +{ + if (/*!IsTank() || */!me->IsInCombat() || IsCasting() || + JumpingOrFalling() || CCed(me, true) || Rand() > 10 + 20*me->GetMap()->IsDungeon() || + HasBotCommandState(BOT_COMMAND_MASK_UNMOVING)) + return; + + Unit::AttackerSet const& myattackers = me->getAttackers(); + if (myattackers.size() < 2) + return; + + //TC_LOG_ERROR("entities.player", "AdjustTankingPosition() by {}", me->GetName()); + + uint32 bCount = 0; + for (Unit::AttackerSet::const_iterator itr = myattackers.begin(); itr != myattackers.end(); ++itr) + { + if (/*!CCed(*itr) && */(*itr)->GetDistance(me) < 5 && !me->HasInArc(float(M_PI), *itr)) + ++bCount; + //if (++bCount) + // break; + } + + if (bCount == 0) + return; + + //TC_LOG_ERROR("entities.player", "AdjustTankingPosition(): atts {}, behind {}", uint32(myattackers.size()), bCount); + + //calculate new position + float x = me->GetPositionX(); + float y = me->GetPositionY(); + float z = me->GetPositionZ(); + float ori = me->GetOrientation(); + float const moveDist = -1.f * std::max(opponent->GetCombatReach() * 0.6f, 3.f); + float moveX = 0.f; + float moveY = 0.f; + for (uint8 i = 0; i != 3; ++i) + { + if (i) + { + ori = Position::NormalizeOrientation(ori + (i+1)*(M_PI*0.5f)); + } + + //move back + moveX = moveDist * std::cos(ori); + moveY = moveDist * std::sin(ori); + + if (me->IsWithinLOS(x+moveX, y+moveY, z)) + break; + + if (i == 2) + { + moveX *= 0.2f; + moveY *= 0.2f; + } + } + + x += moveX; + y += moveY; + + me->UpdateAllowedPositionZ(x, y, z); + if (me->GetPositionZ() < z) + z += 0.75f; //prevent going underground + + //if (CCed(opponent, true)) + // me->AttackStop(); + //me->SetOrientation(ori); + me->GetMotionMaster()->MovePoint(me->GetMapId(), x, y, z); +} +//SpellHit()... OnSpellHit() +void bot_pet_ai::OnSpellHit(Unit* caster, SpellInfo const* spell) +{ + //uint32 const spellId = spell->Id; + + if (spell->HasAura(SPELL_AURA_MOD_TAUNT) || spell->HasEffect(SPELL_EFFECT_ATTACK_ME)) + if (caster && me->Attack(caster, true)) + me->GetMotionMaster()->MoveChase(caster); + + for (uint8 i = 0; i != MAX_SPELL_EFFECTS; ++i) + { + uint32 const auraname = spell->_effects[i].ApplyAuraName; + + //update stats + if (auraname == SPELL_AURA_MOD_STAT || auraname == SPELL_AURA_MOD_PERCENT_STAT || + auraname == SPELL_AURA_MOD_TOTAL_STAT_PERCENTAGE || + auraname == SPELL_AURA_MOD_ATTACK_POWER || auraname == SPELL_AURA_MOD_ATTACK_POWER_PCT || + auraname == SPELL_AURA_MOD_ATTACK_POWER_OF_STAT_PERCENT || auraname == SPELL_AURA_MOD_ATTACK_POWER_OF_ARMOR || + auraname == SPELL_AURA_MOD_SPELL_DAMAGE_OF_STAT_PERCENT || + auraname == SPELL_AURA_MOD_RATING || auraname == SPELL_AURA_MOD_RATING_FROM_STAT) + shouldUpdateStats = true; + else if (auraname == SPELL_AURA_MOD_INCREASE_HEALTH || + auraname == SPELL_AURA_MOD_INCREASE_HEALTH_2 || + auraname == SPELL_AURA_230 || + auraname == SPELL_AURA_MOD_INCREASE_HEALTH_PERCENT) + shouldUpdateStats = true; + else if (auraname == SPELL_AURA_MOD_INCREASE_ENERGY || auraname == SPELL_AURA_MOD_INCREASE_ENERGY_PERCENT) + shouldUpdateStats = true; + } + + if (!me->GetVictim() && (me->IsHostileTo(caster) || caster->IsHostileTo(me))) + { + if (me->CanSeeOrDetect(caster) && (caster->IsInCombat() || me->IsInCombat() || petOwner->IsInCombat())) + petOwner->GetBotAI()->OwnerAttackedBy(caster); + } +} +//Update delay +//Skip UpdateAI cycles for randomization of bots' reaction and performance adjustments +bool bot_pet_ai::Wait() +{ + if (waitTimer > lastdiff) + return true; + + if (IAmFree()) + waitTimer = me->IsInCombat() ? 250 : ((__rand + 100) * 20); + else if (!me->GetMap()->IsRaid()) + waitTimer = std::min(uint32(50 * (petOwner->GetBotOwner()->GetNpcBotsCount() - 1) + __rand + __rand), 500); + else + waitTimer = __rand; + + return false; +} +//Spell Mod Hooks +void bot_pet_ai::ApplyBotDamageMultiplierSpell(int32& damage, SpellNonMeleeDamage& damageinfo, SpellInfo const* spellInfo, WeaponAttackType attackType, bool crit) const +{ + //DAMAGE SPELLS damage bonus (DMG_CLASS_MAGIC) + ApplyClassDamageMultiplierSpell(damage, damageinfo, spellInfo, attackType, crit); +} +//Spell Mod Utilities +float bot_pet_ai::CalcSpellMaxRange(uint32 spellId, bool enemy) const +{ + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); + ASSERT(spellInfo); + + return spellInfo->GetMaxRange(!enemy); +} +bool bot_pet_ai::IAmFree() const +{ + return petOwner->IsFreeBot(); +} + +bool bot_pet_ai::CCed(Unit const* target, bool root) +{ + return bot_ai::CCed(target, root); +} + +bool bot_pet_ai::IsTank(Unit const* unit) const +{ + if (Creature const* bot = unit->ToCreature()) + return bot->GetBotAI() && bot->GetBotAI()->HasRole(BOT_ROLE_TANK); + else if (Player const* player = unit->ToPlayer()) + { + if (Group const* gr = player->GetGroup()) + { + if (gr->GetMemberFlags(unit->GetGUID()) & MEMBER_FLAG_MAINTANK) + return true; + if (gr->isLFGGroup() && sLFGMgr->GetRoles(unit->GetGUID()) & lfg::PLAYER_ROLE_TANK) + return true; + } + } + + return false; +} +//Unused +bool bot_pet_ai::IsOffTank(Unit const* unit) const +{ + if (Creature const* bot = unit->ToCreature()) + return bot->GetBotAI() && bot->GetBotAI()->HasRole(BOT_ROLE_TANK_OFF); + else if (Player const* player = unit->ToPlayer()) + { + if (Group const* gr = player->GetGroup()) + { + if (gr->isRaidGroup()) + { + Group::MemberSlotList const& slots = gr->GetMemberSlots(); + for (Group::member_citerator itr = slots.begin(); itr != slots.end(); ++itr) + if (itr->guid == unit->GetGUID()) + return itr->flags & MEMBER_FLAG_MAINASSIST; + } + } + } + + return false; +} + +void bot_pet_ai::OnStartAttack(Unit const* /*u*/) +{ + AdjustTankingPosition(); +} + +bool bot_pet_ai::StartAttack(Unit const* u, bool force) +{ + if (HasBotCommandState(BOT_COMMAND_ATTACK) && !force) + return false; + + SetBotCommandState(BOT_COMMAND_ATTACK); + OnStartAttack(u); + return true; +} + +void bot_pet_ai::JustDied(Unit*) +{ + KillEvents(false); +} + +void bot_pet_ai::KilledUnit(Unit* u) +{ + GetPetsOwner()->GetBotAI()->KilledUnit(u); +} + +void bot_pet_ai::AttackStart(Unit* /*u*/) +{ +} + +void bot_pet_ai::DamageDealt(Unit* victim, uint32& damage, DamageEffectType /*damageType*/) +{ + if (victim == me) + return; + + if (damage) + { + if (Creature* cre = victim->ToCreature()) + { + if (!cre->hasLootRecipient()) + cre->SetLootRecipient(petOwner->GetBotOwner()); + + //controlled case is handled in Unit::DealDamage + if (IAmFree()) + cre->LowerPlayerDamageReq(cre->GetHealth() < damage ? cre->GetHealth() : damage); + } + } +} + +void bot_pet_ai::IsSummonedBy(WorldObject* summoner) +{ + //TC_LOG_ERROR("entities.unit", "bot_pet_ai::IsSummonedBy for {} by {}", me->GetName(), summoner->GetName()); + //ASSERT(!petOwner); + //ASSERT(summoner->GetTypeId() == TYPEID_UNIT); + petOwner = summoner->ToCreature(); + m_botCommandState = petOwner->GetBotAI()->GetBotCommandState(); + myType = me->GetEntry(); + //myType = petOwner->GetBotAI()->GetAIMiscValue(BOTAI_MISC_PET_TYPE); + //ASSERT(myType); + me->setActive(true); + //me->SetUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED); + ASSERT(!me->GetBotAI()); + ASSERT(!me->GetBotPetAI()); + me->SetBotPetAI(this); + SetPetStats(true); + if (petOwner->GetTransport()) + { + petOwner->GetTransport()->AddPassenger(me); + me->m_movementInfo.transport.pos.Relocate(petOwner->GetTransOffset()); + me->Relocate(bot_ai::GetAbsoluteTransportPosition(petOwner)); + me->AddUnitState(UNIT_STATE_IGNORE_PATHFINDING); + } + //Send group update if not a minion + if (petOwner->GetBotAI()->GetBotsPet() == me && petOwner->GetBotAI()->GetGroup()) + BotMgr::SetBotGroupUpdateFlag(petOwner, GROUP_UPDATE_PET); +} +//This function is called after Spell::SendSpellCooldown() and Spell::DoAllEffects...() call +void bot_pet_ai::OnBotPetSpellGo(Spell const* spell, bool ok) +{ + if (!ok) + return; + + SpellInfo const* curInfo = spell->GetSpellInfo(); + + //Set cooldown + if (!curInfo->IsCooldownStartedOnEvent() && !curInfo->IsPassive()) + { + uint32 rec = curInfo->RecoveryTime; + uint32 catrec = curInfo->CategoryRecoveryTime; + + SetSpellCooldown(curInfo->GetFirstRankSpell()->Id, rec); + SetSpellCategoryCooldown(curInfo->GetFirstRankSpell(), catrec); + } + + if ((!curInfo->CastTimeEntry || !curInfo->CastTimeEntry->Base) && + curInfo->StartRecoveryTime) + { + GC_Timer = curInfo->StartRecoveryTime; + GC_Timer = std::max(GC_Timer, 1000); + GC_Timer = std::min(GC_Timer, 1500); + } + + OnPetClassSpellGo(curInfo); +} + +void bot_pet_ai::OnBotPetSpellInterrupted(SpellSchoolMask schoolMask, uint32 unTimeMs) +{ + SpellInfo const* info; + + for (BotPetSpellMap::iterator itr = _spells.begin(); itr != _spells.end(); ++itr) + { + info = sSpellMgr->GetSpellInfo(itr->second->spellId); + if (!info || !(info->GetSchoolMask() & schoolMask)) continue; + if (info->IsCooldownStartedOnEvent()) continue; + if (info->PreventionType != SPELL_PREVENTION_TYPE_SILENCE) continue; + + itr->second->cooldown += unTimeMs; + //TC_LOG_ERROR("entities.player", "OnBotPetSpellInterrupted(): Adding cooldown ({}, new: {}) to spell {} (id: {}, schoolmask: {}), reqSchoolMask = {}", + // unTimeMs, itr->second.second, info->SpellName[0], info->Id, info->SchoolMask, schoolMask); + } + + GC_Timer = 0; //reset global cooldown since cast is canceled +} +//GLOBAL UPDATE +//opponent unsafe +bool bot_pet_ai::GlobalUpdate(uint32 diff) +{ + if (!petOwner) + { + TC_LOG_ERROR("entities.unit", "botpet:GlobalUpdate(): no owner!"); + return false; + } + + if (!BotMgr::IsNpcBotModEnabled()) + return false; + + ReduceCD(diff); + + lastdiff = diff; + + if (_updateTimerMedium <= diff) + { + _updateTimerMedium = 500; + + //Medium-timed updates + if (!IAmFree()) + { + //update pvp state + if (me->GetByteValue(UNIT_FIELD_BYTES_2, 1) != petOwner->GetByteValue(UNIT_FIELD_BYTES_2, 1)) + me->SetByteValue(UNIT_FIELD_BYTES_2, 1, petOwner->GetByteValue(UNIT_FIELD_BYTES_2, 1)); + } + if (myType == BOT_PET_LOCUST_SWARM) + { + me->SetFloatValue(UNIT_FIELD_BOUNDINGRADIUS, 2.0f * DEFAULT_PLAYER_BOUNDING_RADIUS * me->GetObjectScale()); + me->SetFloatValue(UNIT_FIELD_COMBATREACH, 2.0f * DEFAULT_PLAYER_COMBAT_REACH * me->GetObjectScale()); + } + } + + if (!me->IsAlive()) + return false; + + //Check current cast state: interrupt casts that became pointless + if (me->HasUnitState(UNIT_STATE_CASTING) && urand(1,100) <= 75) + { + bool interrupt; + for (uint8 i = CURRENT_FIRST_NON_MELEE_SPELL; i != CURRENT_AUTOREPEAT_SPELL; ++i) + { + interrupt = false; + Spell* spell = me->GetCurrentSpell(CurrentSpellTypes(i)); + if (!spell) + continue; + if (spell->m_targets.GetObjectTargetGUID().IsAnyTypeCreature()) + spell->m_targets.Update(me); + Unit const* target = spell->m_targets.GetUnitTarget(); + if (!target) + continue; + SpellInfo const* info = spell->GetSpellInfo(); + if (!info->CastTimeEntry) + continue; + if (!info->IsPositive()) + { + if (!target->IsAlive()) + interrupt = true; + //control interruptions should be checked inside pet class ai + //else if ((info->Mechanic == MECHANIC_POLYMORPH || info->Mechanic == MECHANIC_SHACKLE || + // info->Mechanic == MECHANIC_DISORIENTED || info->Mechanic == MECHANIC_SLEEP || + // info->Mechanic == MECHANIC_CHARM || info->Mechanic == MECHANIC_BANISH || + // info->Mechanic == MECHANIC_STUN || info->Mechanic == MECHANIC_FREEZE) && + // !target->getAttackers().empty() && !IsCasting(target)) + // interrupt = true; //useless control + else if (target->HasAuraType(SPELL_AURA_PERIODIC_DAMAGE) && !IsCasting(target) && + (info->AuraInterruptFlags & AURA_INTERRUPT_FLAG_TAKE_DAMAGE)) + interrupt = true; //useless control breaks immediately + } + + if (interrupt) + { + me->InterruptSpell(CurrentSpellTypes(i)); + GC_Timer = 0; + break; + } + } + } + + if (_updateTimerEx1 <= diff && !IAmFree()) + { + _updateTimerEx1 = urand(2000, 2500); + + //Ex1-timed updates + + //DEBUG + /* + Sometimes bots are affected by zone (instance) scripts + Good example is CoT: Battle for Mount Hyjal + */ + //Faction + //ensure master is not controlled + ChrRacesEntry const* rEntry = sChrRacesStore.LookupEntry(petOwner->GetBotOwner()->GetRace()); + uint32 fac = rEntry ? rEntry->FactionID : 0; + if (me->GetFaction() != petOwner->GetBotOwner()->GetFaction() && petOwner->GetBotOwner()->GetFaction() == fac) + { + //std::ostringstream msg; + //msg << "Something changed my faction (now " << me->GetFaction() << "), changing back to " << fac << "!"; + //BotWhisper(msg.str().c_str()); + me->SetFaction(fac); + } + //Visibility + if (!me->IsVisible() && petOwner->GetBotOwner()->IsVisible()) + { + //BotWhisper("Something changed my visibility status! Making visible..."); + me->SetVisible(true); + } + if (me->IsVisible() && !petOwner->GetBotOwner()->IsVisible()) + { + //BotWhisper("Something changed my visibility status! Making invisible..."); + me->SetVisible(false); + } + //Phase + if (me->GetPhaseMask() != petOwner->GetBotOwner()->GetPhaseMask()) + { + //BotWhisper("Somehow we are not is same phase! Fixing that..."); + me->SetPhaseMask(petOwner->GetBotOwner()->GetPhaseMask(), true); + } + if (me->GetTransport() != petOwner->GetBotOwner()->GetTransport()) + { + if (petOwner->GetBotOwner()->GetTransport()) + { + if (me->GetDistance2d(petOwner->GetBotOwner()) < 20.f) + { + petOwner->GetBotOwner()->GetTransport()->AddPassenger(me); + me->m_movementInfo.transport.pos.Relocate(petOwner->GetBotOwner()->GetTransOffset()); + me->Relocate(bot_ai::GetAbsoluteTransportPosition(petOwner->GetBotOwner())); + me->AddUnitState(UNIT_STATE_IGNORE_PATHFINDING); + } + } + else + { + switch (me->GetEntry()) + { + case BOT_PET_TORNADO: + case BOT_PET_LOCUST_SWARM: + break; + default: + me->ClearUnitState(UNIT_STATE_IGNORE_PATHFINDING); + break; + } + me->GetTransport()->RemovePassenger(me); + } + } + //end DEBUG + } + + Regenerate(); + + //update flags + if (!me->IsInCombat()) + { + if (me->HasUnitFlag(UNIT_FLAG_PET_IN_COMBAT)) + me->RemoveUnitFlag(UNIT_FLAG_PET_IN_COMBAT); + } + + //update movement orders if near owner, otherwise get close + bool closeToOwner = false; + if (!opponent && !IsCasting()) + { + _calculatePos(movepos); + if (me->GetExactDist(&movepos) > 5.f) + SetBotCommandState(BOT_COMMAND_FOLLOW, true, &movepos); + else + closeToOwner = !me->isMoving(); + } + if (closeToOwner || me->IsInCombat()) + { + uint8 st = (petOwner->GetBotAI()->GetBotCommandState() & BOT_COMMAND_MASK_UNMOVING); + if (st && GetBotCommandState() != st) + { + SetBotCommandState(st); + return !(st & BOT_COMMAND_FULLSTOP); + } + } + + if (HasBotCommandState(BOT_COMMAND_FULLSTOP)) + return false; + + if (!HasBotCommandState(BOT_COMMAND_INACTION)) + CheckAttackState(); + + //second alive check - CheckAttackState() can cause bot to die + if (!me->IsAlive()) + return false; + + if (checkAurasTimer <= lastdiff) + { + Unit* victim = me->GetVictim(); + checkAurasTimer += uint32(__rand + __rand + (IAmFree() ? 1000 : 40 * (1 + petOwner->GetBotOwner()->GetNpcBotsCount()))); + + if (!HasBotCommandState(BOT_COMMAND_MASK_UNCHASE) && victim && !CCed(me, true) && + !me->isMoving() && !IsCasting() && myType != BOT_PET_TORNADO && myType != BOT_PET_LOCUST_SWARM) + { + if (!IAmFree() && petOwner->GetBotOwner()->GetBotMgr()->GetBotAttackRangeMode() == BOT_ATTACK_RANGE_EXACT && + petOwner->GetBotOwner()->GetBotMgr()->GetBotExactAttackRange() == 0) + { + GetInPosition(true, victim); + } + else if (IsPetMelee()) + { + if (me->GetDistance(victim) > 1.5f) + GetInPosition(true, victim); + } + else + { + CalculateAttackPos(victim, attackpos); + if (me->GetExactDist2d(&attackpos) > 4.f || !me->IsWithinLOSInMap(victim, LINEOFSIGHT_ALL_CHECKS, VMAP::ModelIgnoreFlags::M2)) + GetInPosition(true, victim, &attackpos); + } + } + if (shouldUpdateStats && me->GetPhaseMask() == petOwner->GetBotOwner()->GetPhaseMask()) + SetPetStats(false); + } + + if (Wait()) + return false; + + if (CCed(me)) + return false; + + GenerateRand(); + + if (HasBotCommandState(BOT_COMMAND_INACTION)) + return false; + + return true; +} + +void bot_pet_ai::CommonTimers(uint32 diff) +{ + //_petEvents.Update(diff); + SpellTimers(diff); + + if (GC_Timer > diff) GC_Timer -= diff; + if (checkAurasTimer > diff) checkAurasTimer -= diff; + if (waitTimer > diff) waitTimer -= diff; + if (_moveBehindTimer > diff) _moveBehindTimer -= diff; + + if (_updateTimerMedium > diff) _updateTimerMedium -= diff; + if (_updateTimerEx1 > diff) _updateTimerEx1 -= diff; +} + +void bot_pet_ai::KillEvents(bool /*force*/) +{ + //_petEvents.KillAllEvents(force); +} + +bool bot_pet_ai::IsChanneling(Unit const* u/* = nullptr*/) const +{ + if (!u) + u = me; + return u->GetCurrentSpell(CURRENT_CHANNELED_SPELL); +} +bool bot_pet_ai::IsCasting(Unit const* u/* = nullptr*/) const +{ + if (!u) + u = me; + return (u->HasUnitState(UNIT_STATE_CASTING) || IsChanneling(u) || u->IsNonMeleeSpellCast(false, false, true, false, false)); +} +bool bot_pet_ai::JumpingFlyingOrFalling() const +{ + return Jumping() || me->IsFalling() || me->HasUnitMovementFlag(MOVEMENTFLAG_PITCH_UP|MOVEMENTFLAG_PITCH_DOWN|MOVEMENTFLAG_SPLINE_ELEVATION|MOVEMENTFLAG_FALLING_SLOW); +} +bool bot_pet_ai::JumpingOrFalling() const +{ + return Jumping() || me->IsFalling() || me->HasUnitMovementFlag(MOVEMENTFLAG_PITCH_UP|MOVEMENTFLAG_PITCH_DOWN|MOVEMENTFLAG_FALLING_SLOW); +} +bool bot_pet_ai::Jumping() const +{ + return me->HasUnitState(UNIT_STATE_JUMPING); +} +bool bot_pet_ai::IsIndoors() const +{ + return indoorsTimer >= INOUTDOORS_ENSURE_TIMER && outdoorsTimer == 0; +} +bool bot_pet_ai::IsOutdoors() const +{ + return outdoorsTimer >= INOUTDOORS_ENSURE_TIMER && indoorsTimer == 0; +} + +uint32 bot_pet_ai::GetLostHP(Unit const* unit) +{ + return unit->GetMaxHealth() - unit->GetHealth(); +} +uint8 bot_pet_ai::GetHealthPCT(Unit const* u) +{ + if (!u || !u->IsAlive() || u->GetMaxHealth() <= 1) + return 100; + return uint8(((float(u->GetHealth()))/u->GetMaxHealth()) * 100); +} +uint8 bot_pet_ai::GetManaPCT(Unit const* u) +{ + if (!u || !u->IsAlive() || u->GetMaxPower(POWER_MANA) <= 1) + return 100; + return (u->GetPower(POWER_MANA)*10/(1 + u->GetMaxPower(POWER_MANA)/10)); +} diff --git a/src/server/game/AI/NpcBots/bpet_ai.h b/src/server/game/AI/NpcBots/bpet_ai.h new file mode 100644 index 000000000..a8399ab3f --- /dev/null +++ b/src/server/game/AI/NpcBots/bpet_ai.h @@ -0,0 +1,198 @@ +#ifndef _BOT_PET_AI_H +#define _BOT_PET_AI_H + +#include "botcommon.h" + +#include "CreatureAI.h" +#include "Position.h" + +/* +NpcBot Pet System by Trickerer (onlysuffering@gmail.com) +*/ + +struct SpellNonMeleeDamage; + +class Aura; +class Spell; +class Unit; + +class bot_pet_ai : public CreatureAI +{ + public: + virtual ~bot_pet_ai(); + + bool canUpdate; + + void InitializeAI() override { Reset(); } + void Reset() override {} + + void JustDied(Unit*) override; + void KilledUnit(Unit* u) override; + void AttackStart(Unit* u) override; + //virtual void JustEnteredCombat(Unit* u) override; + void MoveInLineOfSight(Unit* /*u*/) override {} + void DamageDealt(Unit* victim, uint32& damage, DamageEffectType damageType) override; + void DamageTaken(Unit* /*attacker*/, uint32& /*damage*/, DamageEffectType /*damageType*/, SpellInfo const* /*spellInfo*/) override { } + //void ReceiveEmote(Player* player, uint32 emote); + void EnterEvadeMode(EvadeReason/* why*/ = EVADE_REASON_OTHER) override { } + uint32 GetData(uint32 data) const override; + void IsSummonedBy(WorldObject* summoner) override; + + Creature* GetPetsOwner() const { return petOwner; } + void CalculatePetsOwnerFollowPosition(Position &pos) { _calculatePos(pos); } + + //EventProcessor* GetEvents() { return &_petEvents; } + uint32 GetLastDiff() const { return lastdiff; } + void CommonTimers(uint32 diff); + void KillEvents(bool force); + void SetBotCommandState(uint32 st, bool force = false, Position* newpos = nullptr); + void RemoveBotCommandState(uint32 st); + bool HasBotCommandState(uint32 st) const { return (m_botCommandState & st); } + uint8 GetBotCommandState() const { return m_botCommandState; } + bool IsInBotParty(Unit const* unit) const; + virtual void ApplyBotPetSpellRadiusMods(SpellInfo const* /*spellInfo*/, float& /*radius*/) const {} + bool IsTank(Unit const* unit) const; + bool IsOffTank(Unit const* unit) const; + + bool IAmFree() const; + + //wandering bots + bool IsWanderer() const { return _wanderer; } + void SetWanderer() { if (IAmFree()) _wanderer = true; } + + uint64 GetAuraUpdateMaskForRaid() const { return _auraRaidUpdateMask; } + void SetAuraUpdateMaskForRaid(uint8 slot) { _auraRaidUpdateMask |= (uint64(1) << slot); } + void ResetAuraUpdateMaskForRaid() { _auraRaidUpdateMask = 0; } + + static bool CCed(Unit const* target, bool root = false); + + inline void SetShouldUpdateStats() { shouldUpdateStats = true; } + + //virtual uint32 GetAIMiscValue(uint32 /*data*/) const { return 0; } + //virtual void SetAIMiscValue(uint32 /*data*/, uint32 /*value*/) {} + + void OnBotPetSpellInterrupted(SpellSchoolMask schoolMask, uint32 unTimeMs); + void OnBotPetSpellGo(Spell const* spell, bool ok = true); + virtual void OnPetClassSpellGo(SpellInfo const* /*spellInfo*/) {} + + bool IsSpellReady(uint32 basespell, uint32 diff, bool checkGCD = true) const; + void SetSpellCooldown(uint32 basespell, uint32 msCooldown); + void SetSpellCategoryCooldown(SpellInfo const* spellInfo, uint32 msCooldown); + void ReleaseSpellCooldown(uint32 basespell); + + void ApplyBotDamageMultiplierSpell(int32& damage, SpellNonMeleeDamage& damageinfo, SpellInfo const* spellInfo, WeaponAttackType attackType, bool crit) const; + + protected: + explicit bot_pet_ai(Creature* creature); + + virtual void ApplyClassDamageMultiplierSpell(int32& /*damage*/, SpellNonMeleeDamage& /*damageinfo*/, SpellInfo const* /*spellInfo*/, WeaponAttackType /*attackType*/, bool /*crit*/) const {} + + virtual void ReduceCD(uint32 /*diff*/) {} + bool GlobalUpdate(uint32 diff); + + void CureGroup(uint32 cureSpell, uint32 diff); + void SetPetStats(bool force); + + void OnOwnerDamagedBy(Unit* attacker); + + bool IsPetMelee() const; + uint8 Spec() const; + + static uint32 InitSpell(Unit const* caster, uint32 spell); + void InitSpellMap(uint32 basespell, bool forceadd = false, bool forwardRank = true); + uint32 GetSpell(uint32 basespell) const; + uint32 GetSpellCooldown(uint32 basespell) const; + void ResetSpellCooldown(uint32 basespell) { SetSpellCooldown(basespell, 0); } + void RemoveSpell(uint32 basespell); + void SpellTimers(uint32 diff); + + void RefreshAura(uint32 spellId, int8 count = 1, Unit* target = nullptr) const; + bool CheckAttackTarget(); + void MoveBehind(Unit const* target) const; + + void AdjustTankingPosition() const; + void OnStartAttack(Unit const* /*u*/); + bool StartAttack(Unit const* u, bool force = false); + + bool IsChanneling(Unit const* u = nullptr) const; + bool IsCasting(Unit const* u = nullptr) const; + bool JumpingFlyingOrFalling() const; + bool JumpingOrFalling() const; + bool Jumping() const; + bool IsIndoors() const; + bool IsOutdoors() const; + + float CalcSpellMaxRange(uint32 spellId, bool enemy = true) const; + void CalculateAttackPos(Unit* target, Position &pos) const; + void GetInPosition(bool force, Unit* newtarget, Position* pos = nullptr); + virtual float GetSpellAttackRange(bool longRange) const { return longRange ? 25.f : 15.f; } + virtual void CheckAttackState(); + void OnSpellHit(Unit* caster, SpellInfo const* spell); + + virtual void InitPetSpells() {} + virtual void ApplyPetPassives() const {} + + void Regenerate(); + void RegeneratePetFocus(); + void RegeneratePetEnergy(); + + bool Wait(); + uint16 Rand() const; + void GenerateRand() const; + + static uint32 GetLostHP(Unit const* unit); + static uint8 GetHealthPCT(Unit const* u); + static uint8 GetManaPCT(Unit const* u); + + Unit* opponent; + Creature* petOwner; + //EventProcessor _petEvents; + uint32 GC_Timer; + uint32 myType; + + private: + bool _canCureTarget(Unit const* target, uint32 cureSpell) const; + void _getBotDispellableAuraList(Unit const* target, Unit const* caster, uint32 dispelMask, std::list &dispelList) const; + void _calculatePos(Position& pos) const; + + bool _canRegenerate() const; + + Unit* _getTarget(bool &reset) const; + bool _checkImmunities(Unit const* target, SpellInfo const* spellInfo) const; + static inline float _getAttackDistance(float distance) { return distance*0.72f; } + + Position movepos, attackpos; + uint32 m_botCommandState; + + //timers + uint32 lastdiff, checkAurasTimer, regenTimer, _updateTimerMedium, _updateTimerEx1; + uint32 waitTimer; + uint32 _moveBehindTimer; + uint32 indoorsTimer; + uint32 outdoorsTimer; + + //wandering bots + bool _wanderer; + + uint64 _auraRaidUpdateMask; + + float _energyFraction; + + bool shouldUpdateStats; + + struct BotPetSpell + { + explicit BotPetSpell() : spellId(0), cooldown(0), enabled(true) {} + uint32 spellId; + uint32 cooldown; + bool enabled; + private: + BotPetSpell(BotPetSpell const&); + }; + + typedef std::unordered_map BotPetSpellMap; + BotPetSpellMap const& GetSpellMap() const { return _spells; } + BotPetSpellMap _spells; +}; + +#endif diff --git a/src/server/game/AI/NpcBots/bpet_archmage.cpp b/src/server/game/AI/NpcBots/bpet_archmage.cpp new file mode 100644 index 000000000..655ae65d9 --- /dev/null +++ b/src/server/game/AI/NpcBots/bpet_archmage.cpp @@ -0,0 +1,151 @@ +#include "bot_ai.h" +#include "botspell.h" +#include "bpet_ai.h" +#include "Creature.h" +#include "ScriptMgr.h" +/* +Archmage NpcBot Pets (by Trickerer onlysuffering@gmail.com) +Complete - 100% +TODO: +*/ + +enum ArchmagePetBaseSpells +{ + WATERBOLT_1 = SPELL_WATERBOLT +}; + +enum ArchmagePetSpecial +{ + ELEMENTAL_DURATION = 60000 //1 min +}; + +class archmage_pet_bot : public CreatureScript +{ +public: + archmage_pet_bot() : CreatureScript("archmage_pet_bot") { } + + CreatureAI* GetAI(Creature* creature) const override + { + return new awater_elemental_botpetAI(creature); + } + + struct awater_elemental_botpetAI : public bot_pet_ai + { + awater_elemental_botpetAI(Creature* creature) : bot_pet_ai(creature) { } + + void JustEnteredCombat(Unit* u) override { bot_pet_ai::JustEnteredCombat(u); } + void KilledUnit(Unit* u) override { bot_pet_ai::KilledUnit(u); } + void EnterEvadeMode(EvadeReason why = EVADE_REASON_OTHER) override { bot_pet_ai::EnterEvadeMode(why); } + void MoveInLineOfSight(Unit* u) override { bot_pet_ai::MoveInLineOfSight(u); } + void JustDied(Unit* u) override { bot_pet_ai::JustDied(u); } + void DoNonCombatActions(uint32 /*diff*/) { } + + void StartAttack(Unit* u, bool force = false) + { + if (!bot_pet_ai::StartAttack(u, force)) + return; + GetInPosition(force, u); + } + + void UpdateAI(uint32 diff) override + { + if ((liveTimer += diff) >= ELEMENTAL_DURATION * (IAmFree() ? 60u : 1u)) + { + canUpdate = false; + me->setDeathState(JUST_DIED); + return; + } + + if (!GlobalUpdate(diff)) + return; + + if (!me->IsInCombat()) + DoNonCombatActions(diff); + + //DoPetActions(diff); + //CheckDrainMana(diff); + + if (!CheckAttackTarget()) + return; + + if (IsCasting()) + return; + + DoPetAttack(diff); + } + + void DoPetAttack(uint32 diff) + { + StartAttack(opponent, IsPetMelee()); + + if (!petOwner->GetBotAI()->HasRole(BOT_ROLE_DPS)) + return; + + if (IsSpellReady(WATERBOLT_1, diff) && me->GetDistance(opponent) < 30) + { + me->CastSpell(opponent, GetSpell(WATERBOLT_1), false); + return; + } + } + + void OnPetClassSpellGo(SpellInfo const* /*spellInfo*/) override + { + } + + void SpellHit(WorldObject* wcaster, SpellInfo const* spell) override + { + Unit* caster = wcaster->ToUnit(); + if (!caster) + return; + + OnSpellHit(caster, spell); + } + + void SpellHitTarget(WorldObject* /*wtarget*/, SpellInfo const* /*spell*/) override + { + } + + void DamageDealt(Unit* victim, uint32& damage, DamageEffectType damageType) override + { + bot_pet_ai::DamageDealt(victim, damage, damageType); + } + + void DamageTaken(Unit* u, uint32& /*damage*/, DamageEffectType /*damageType*/, SpellInfo const* /*spellInfo*/) override + { + if (!u) + return; + if (!u->IsInCombat() && !me->IsInCombat()) + return; + OnOwnerDamagedBy(u); + } + + void OwnerAttackedBy(Unit* u) override + { + if (!u) + return; + OnOwnerDamagedBy(u); + } + + void Reset() override + { + liveTimer = 0; + } + + void InitPetSpells() override + { + InitSpellMap(WATERBOLT_1); + } + + void ApplyPetPassives() const override + { + } + + private: + uint32 liveTimer; + }; +}; + +void AddSC_archmage_bot_pets() +{ + new archmage_pet_bot(); +} diff --git a/src/server/game/AI/NpcBots/bpet_crypt_lord.cpp b/src/server/game/AI/NpcBots/bpet_crypt_lord.cpp new file mode 100644 index 000000000..96284444f --- /dev/null +++ b/src/server/game/AI/NpcBots/bpet_crypt_lord.cpp @@ -0,0 +1,383 @@ +#include "bot_ai.h" +#include "botspell.h" +#include "bpet_ai.h" +#include "CellImpl.h" +#include "Containers.h" +#include "GridNotifiers.h" +#include "GridNotifiersImpl.h" +#include "MotionMaster.h" +#include "ScriptMgr.h" +#include "SpellMgr.h" +#include "TemporarySummon.h" +/* +Crypt Lord NpcBot Pets (by Trickerer onlysuffering@gmail.com) +Notes: +Complete - 75% +TODO: Check if Burrow can be added +*/ + +enum CryptLordPetBaseSpells +{ +}; +enum CryptLordPetPassives +{ +}; +enum CryptLordPetSpecial +{ + LOCUST_SWARM_EFFECTIVE_RADIUS = 60, + LOCUST_SWARM_SPELL_DURATION = 30000, +}; + +class crypt_lord_pet_bot : public CreatureScript +{ +public: + crypt_lord_pet_bot() : CreatureScript("crypt_lord_pet_bot") { } + + CreatureAI* GetAI(Creature* creature) const override + { + switch (creature->GetEntry()) + { + case BOT_PET_LOCUST_SWARM: + return new locust_swarm_botpetAI(creature); + default: + return new carrion_beetle_botpetAI(creature); + } + } + + struct carrion_beetle_botpetAI : public bot_pet_ai + { + carrion_beetle_botpetAI(Creature* creature) : bot_pet_ai(creature) { } + + void JustEnteredCombat(Unit* u) override { bot_pet_ai::JustEnteredCombat(u); } + void KilledUnit(Unit* u) override { bot_pet_ai::KilledUnit(u); } + void EnterEvadeMode(EvadeReason why = EVADE_REASON_OTHER) override { bot_pet_ai::EnterEvadeMode(why); } + void MoveInLineOfSight(Unit* u) override { bot_pet_ai::MoveInLineOfSight(u); } + void JustDied(Unit* u) override { bot_pet_ai::JustDied(u); } + void DoNonCombatActions(uint32 /*diff*/) { } + + void StartAttack(Unit* u, bool force = false) + { + if (!bot_pet_ai::StartAttack(u, force)) + return; + GetInPosition(force, u); + } + + void UpdateAI(uint32 diff) override + { + if (!GlobalUpdate(diff)) + return; + + if (!me->IsInCombat()) + DoNonCombatActions(diff); + + if (!CheckAttackTarget()) + return; + + if (IsCasting()) + return; + + DoPetAttack(diff); + } + + void DoPetAttack(uint32 /*diff*/) + { + StartAttack(opponent, IsPetMelee()); + } + + void OnPetClassSpellGo(SpellInfo const* /*spellInfo*/) override + { + } + + void SpellHit(WorldObject* wcaster, SpellInfo const* spell) override + { + Unit* caster = wcaster->ToUnit(); + if (!caster) + return; + + OnSpellHit(caster, spell); + } + + void SpellHitTarget(WorldObject* /*wtarget*/, SpellInfo const* /*spell*/) override + { + } + + void DamageDealt(Unit* victim, uint32& damage, DamageEffectType damageType) override + { + bot_pet_ai::DamageDealt(victim, damage, damageType); + } + + void DamageTaken(Unit* u, uint32& /*damage*/, DamageEffectType /*damageType*/, SpellInfo const* /*spellInfo*/) override + { + if (!u) + return; + if (!u->IsInCombat() && !me->IsInCombat()) + return; + OnOwnerDamagedBy(u); + } + + void OwnerAttackedBy(Unit* u) override + { + if (!u) + return; + OnOwnerDamagedBy(u); + } + + uint32 GetData(uint32 data) const override + { + switch (data) + { + case BOTPETAI_MISC_MAXLEVEL: + return maxlevel; + default: + return bot_pet_ai::GetData(data); + } + } + + void SetData(uint32 data, uint32 value) override + { + switch (data) + { + case BOTPETAI_MISC_MAXLEVEL: + maxlevel = uint8(value); + SetPetStats(true); + break; + default: + break; + } + } + + void Reset() override + { + maxlevel = 1; + } + + void InitPetSpells() override + { + } + + void ApplyPetPassives() const override + { + } + + private: + uint8 maxlevel; + }; + + struct locust_swarm_botpetAI : public bot_pet_ai + { + locust_swarm_botpetAI(Creature* creature) : bot_pet_ai(creature) + { + (const_cast(me->GetMovementTemplate())).Ground = CreatureGroundMovementType::Hover; + } + + void JustEnteredCombat(Unit* u) override { bot_pet_ai::JustEnteredCombat(u); } + void KilledUnit(Unit* u) override { bot_pet_ai::KilledUnit(u); } + void EnterEvadeMode(EvadeReason why = EVADE_REASON_OTHER) override { bot_pet_ai::EnterEvadeMode(why); } + void MoveInLineOfSight(Unit* u) override { bot_pet_ai::MoveInLineOfSight(u); } + void JustDied(Unit* u) override { bot_pet_ai::JustDied(u); } + + void UpdateAI(uint32 diff) override + { + _activeTimer += diff; + + if (!GlobalUpdate(diff)) + return; + + DoLocustActions(diff); + } + + void DoLocustActions(uint32 diff) + { + bool is_full = _gathered >= _capacity; + bool expired = _activeTimer >= LOCUST_SWARM_SPELL_DURATION; + + if (Unit* u = me->GetVictim()) + { + if (petOwner->GetBotAI()->HasRole(BOT_ROLE_DPS) && me->IsWithinMeleeRange(u) && me->isAttackReady()) + { + me->resetAttackTimer(); + SpellInfo const* spellInfo = sSpellMgr->AssertSpellInfo(SPELL_SOUL_BITE); + spellInfo = spellInfo->TryGetSpellInfoOverride(me); + int32 bp = spellInfo->GetEffect(EFFECT_0).CalcValue(petOwner); + CastSpellExtraArgs args(true); + args.AddSpellBP0(bp); + me->CastSpell(u, SPELL_SOUL_BITE, args); + } + if (!is_full && !expired && u->IsWithinDist(petOwner, LOCUST_SWARM_EFFECTIVE_RADIUS)) + { + if (_chaseCheckTimer <= diff) + { + _chaseCheckTimer = urand(350, 1350); + float dist = CONTACT_DISTANCE + me->GetCombatReach() * frand(1.0f, 3.0f); + float angle = frand(0.001f, float(M_PI * 2)); + Position nearpos = u->GetNearPosition(dist, angle); + me->GetMotionMaster()->MovePoint(1, nearpos, false); + } + return; + } + else + { + me->AttackStop(); + me->BotStopMovement(); + } + } + else + { + _chaseCheckTimer = 0; + + if (me->GetExactDist(petOwner) < (1.5f + 5u * uint32(expired))) + { + if (_gathered > 0 && (is_full || expired)) + { + SpellInfo const* spellInfo = sSpellMgr->AssertSpellInfo(SPELL_LOCUST_SWARM); + spellInfo = spellInfo->TryGetSpellInfoOverride(petOwner); + HealInfo hinfo(petOwner, petOwner, _gathered, spellInfo, spellInfo->GetSchoolMask()); + petOwner->CastSpell(petOwner, SPELL_ENERGIZE_VISUAL, true); + petOwner->HealBySpell(hinfo); + _gathered = 0; + } + if (expired) + { + canUpdate = false; + me->ToTempSummon()->UnSummon(1); + return; + } + } + } + + if (_targetRecheckTimer <= diff) + { + _targetRecheckTimer = urand(1000, 1500); + + std::list targets; + if (petOwner->GetBotAI()->HasRole(BOT_ROLE_DPS) && !is_full && !expired) + { + Trinity::AnyUnfriendlyUnitInObjectRangeCheck check(petOwner, petOwner, LOCUST_SWARM_EFFECTIVE_RADIUS); + Trinity::UnitListSearcher searcher(petOwner, targets, check); + Cell::VisitAllObjects(petOwner, searcher, LOCUST_SWARM_EFFECTIVE_RADIUS); + + targets.remove_if([poguid = petOwner->GetGUID(), combat = petOwner->IsInCombat(), max_attackers = _attackers](Unit const* unit) { + Unit::AttackerSet const& attackers = unit->getAttackers(); + if (!(unit->IsInCombat() || (combat && !attackers.empty()))) + return true; + return max_attackers <= std::count_if(std::cbegin(attackers), std::cend(attackers), [oguid = poguid](Unit const* attacker) { + return attacker->GetEntry() == BOT_PET_LOCUST_SWARM && attacker->GetOwnerGUID() == oguid; + }); + }); + } + + if (!targets.empty()) + { + opponent = targets.size() == 1 ? targets.front() : Trinity::Containers::SelectRandomContainerElement(targets); + me->Attack(opponent, false); + me->GetMotionMaster()->MoveChase(opponent); + } + else + { + if (expired) + { + _targetRecheckTimer = 150; + for (auto rate : { MOVE_WALK, MOVE_RUN }) + me->SetSpeedRate(rate, std::min(1000.0f, me->GetSpeedRate(rate) * 1.35f)); + } + + float dist = (expired || is_full) ? 0.0f : frand(3.0f, 20.0f); + float angle = frand(0.001f, float(M_PI * 2)); + Position nearpos = petOwner->GetNearPosition(dist, angle); + me->GetMotionMaster()->MovePoint(1, nearpos, false); + } + } + } + + void SpellHit(WorldObject* wcaster, SpellInfo const* spell) override + { + Unit* caster = wcaster->ToUnit(); + if (!caster) + return; + + OnSpellHit(caster, spell); + } + + void DamageDealt(Unit* victim, uint32& damage, DamageEffectType damageType) override + { + _gathered = std::min(_gathered + CalculatePct(damage, 75.0f), _capacity); + + bot_pet_ai::DamageDealt(victim, damage, damageType); + } + + uint32 GetData(uint32 data) const override + { + switch (data) + { + case BOTPETAI_MISC_FIXEDLEVEL: + return me->GetCreatureTemplate()->maxlevel; + case BOTPETAI_MISC_CARRY: + return _gathered; + default: + return bot_pet_ai::GetData(data); + } + } + + void SetData(uint32 data, uint32 value) override + { + switch (data) + { + case BOTPETAI_MISC_CAPACITY: + _capacity = value; + break; + case BOTPETAI_MISC_MAX_ATTACKERS: + _attackers = value; + break; + default: + break; + } + } + + void CheckAttackState() override + { + } + + void Reset() override + { + _gathered = 0; + _capacity = 100; + _attackers = 7; + + _targetRecheckTimer = 0; + _chaseCheckTimer = 0; + + _activeTimer = 0; + } + + void ReduceCD(uint32 diff) override + { + if (_targetRecheckTimer > diff) _targetRecheckTimer -= diff; + if (_chaseCheckTimer > diff) _chaseCheckTimer -= diff; + } + + void InitPetSpells() override + { + } + + void ApplyPetPassives() const override + { + RefreshAura(SPELL_VERTEX_COLOR_BLACK); + RefreshAura(SPELL_BLACK_HOLE_VISUAL_2); + } + + private: + uint32 _gathered; + uint32 _capacity; + uint32 _attackers; + + uint32 _targetRecheckTimer; + uint32 _chaseCheckTimer; + + uint32 _activeTimer; + }; +}; + +void AddSC_crypt_lord_bot_pets() +{ + new crypt_lord_pet_bot(); +} diff --git a/src/server/game/AI/NpcBots/bpet_dark_ranger.cpp b/src/server/game/AI/NpcBots/bpet_dark_ranger.cpp new file mode 100644 index 000000000..044d2d7c8 --- /dev/null +++ b/src/server/game/AI/NpcBots/bpet_dark_ranger.cpp @@ -0,0 +1,212 @@ +#include "bot_ai.h" +#include "botspell.h" +#include "bpet_ai.h" +#include "Player.h" +#include "ScriptMgr.h" +/* +Dark Ranger NpcBot Pets (by Trickerer onlysuffering@gmail.com) +Notes: +Extra abilities. For the sake of defending the owner added Taunt. For self defense added Blocking (block value unchanged) +Both abilities are one-time use +Complete - 100% +TODO: +*/ + +enum DarkRangerPetBaseSpells +{ + BLOCKING_1 = 3248, + TAUNT_1 = 37548 +}; +enum DarkRangerPetPassives +{ +}; +enum DarkRangerPetSpecial +{ + SPELL_GENERATE_THREAT = 23604, //reduce threat + THREAT_BASE = 5, + MINION_DURATION = 80000 +}; + +class dark_ranger_pet_bot : public CreatureScript +{ +public: + dark_ranger_pet_bot() : CreatureScript("dark_ranger_pet_bot") { } + + CreatureAI* GetAI(Creature* creature) const override + { + return new dark_ranger_botpetAI(creature); + } + + struct dark_ranger_botpetAI : public bot_pet_ai + { + dark_ranger_botpetAI(Creature* creature) : bot_pet_ai(creature) { } + + void JustEnteredCombat(Unit* u) override { bot_pet_ai::JustEnteredCombat(u); } + void KilledUnit(Unit* u) override { bot_pet_ai::KilledUnit(u); } + void EnterEvadeMode(EvadeReason why = EVADE_REASON_OTHER) override { bot_pet_ai::EnterEvadeMode(why); } + void MoveInLineOfSight(Unit* u) override { bot_pet_ai::MoveInLineOfSight(u); } + void JustDied(Unit* u) override { bot_pet_ai::JustDied(u); } + void DoNonCombatActions(uint32 /*diff*/) { } + + void StartAttack(Unit* u, bool force = false) + { + if (!bot_pet_ai::StartAttack(u, force)) + return; + GetInPosition(force, u); + } + + void DoPetActions(uint32 /*diff*/) + { + //if (threatGenTimer < diff) + //{ + // threatGenTimer = 1500; + // int32 threat = THREAT_BASE; + // CastSpellExtraArgs args(true); + // args.AddSpellBP0(threat); + // me->CastSpell(me, SPELL_GENERATE_THREAT, args); + //} + } + + void UpdateAI(uint32 diff) override + { + if ((liveTimer += diff) >= MINION_DURATION * (IAmFree() ? 5u : 1u)) + { + canUpdate = false; + me->setDeathState(JUST_DIED); + return; + } + + if (!GlobalUpdate(diff)) + return; + + if (!me->IsInCombat()) + DoNonCombatActions(diff); + + DoPetActions(diff); + + if (!CheckAttackTarget()) + return; + + if (IsCasting()) + return; + + DoPetAttack(diff); + } + + void DoPetAttack(uint32 diff) + { + StartAttack(opponent, IsPetMelee()); + + if (IsSpellReady(TAUNT_1, diff, false) && Rand() < 50 && + ((opponent->GetVictim() == petOwner && !IsTank(petOwner)) || + (opponent->GetVictim() == petOwner->GetBotOwner() && !IsTank(petOwner->GetBotOwner()))) && + !opponent->HasAuraType(SPELL_AURA_MOD_TAUNT) && me->IsWithinMeleeRange(opponent)) + { + me->CastSpell(opponent, GetSpell(TAUNT_1), false); + SetSpellCooldown(TAUNT_1, std::numeric_limits::max()); + return; + } + + if (IsSpellReady(BLOCKING_1, diff) && !me->getAttackers().empty() && Rand() < 40) + { + me->CastSpell(me, GetSpell(BLOCKING_1), true); + SetSpellCooldown(BLOCKING_1, 20000); + return; + } + } + + void OnPetClassSpellGo(SpellInfo const* /*spellInfo*/) override + { + } + + void SpellHit(WorldObject* wcaster, SpellInfo const* spell) override + { + Unit* caster = wcaster->ToUnit(); + if (!caster) + return; + + OnSpellHit(caster, spell); + } + + void SpellHitTarget(WorldObject* /*wtarget*/, SpellInfo const* /*spell*/) override + { + } + + void DamageDealt(Unit* victim, uint32& damage, DamageEffectType damageType) override + { + bot_pet_ai::DamageDealt(victim, damage, damageType); + } + + void DamageTaken(Unit* u, uint32& /*damage*/, DamageEffectType /*damageType*/, SpellInfo const* /*spellInfo*/) override + { + if (!u) + return; + if (!u->IsInCombat() && !me->IsInCombat()) + return; + OnOwnerDamagedBy(u); + } + + void OwnerAttackedBy(Unit* u) override + { + if (!u) + return; + OnOwnerDamagedBy(u); + } + + uint32 GetData(uint32 data) const override + { + switch (data) + { + case BOTPETAI_MISC_DURATION: + return liveTimer; + case BOTPETAI_MISC_MAXLEVEL: + return maxlevel; + default: + return bot_pet_ai::GetData(data); + } + } + + void SetData(uint32 data, uint32 value) override + { + switch (data) + { + case BOTPETAI_MISC_MAXLEVEL: + maxlevel = uint8(value); + SetPetStats(true); + break; + default: + break; + } + } + + void Reset() override + { + liveTimer = 0; + maxlevel = 1; + } + + void InitPetSpells() override + { + InitSpellMap(TAUNT_1, true, false); + InitSpellMap(BLOCKING_1, true, false); + } + + void ApplyPetPassives() const override + { + + if (me->GetEntry() == BOT_PET_DARK_MINION_ELITE) + RefreshAura(SPELL_VERTEX_COLOR_BLACK); + else if (me->GetEntry() == BOT_PET_DARK_MINION) + RefreshAura(SPELL_VERTEX_COLOR_GREY); + } + + private: + uint32 liveTimer; + uint8 maxlevel; + }; +}; + +void AddSC_dark_ranger_bot_pets() +{ + new dark_ranger_pet_bot(); +} diff --git a/src/server/game/AI/NpcBots/bpet_death_knight.cpp b/src/server/game/AI/NpcBots/bpet_death_knight.cpp new file mode 100644 index 000000000..041a121d1 --- /dev/null +++ b/src/server/game/AI/NpcBots/bpet_death_knight.cpp @@ -0,0 +1,190 @@ +#include "bot_ai.h" +#include "bpet_ai.h" +#include "ScriptMgr.h" +/* +Deathknight NpcBot Pets (by Trickerer onlysuffering@gmail.com) +Complete - 25% +TODO: Garg, AOD, DRW +*/ +enum DeathknightPetBaseSpells +{ + CLAW_1 = 47468, //150% damage, 40 cost + GNAW_1 = 47481, //12% damage, stun 3 sec, 30 cost + LEAP_1 = 47482, //jump beh tar, 10 cost + HUDDLE_1 = 47484 //mini-shwall, channeled, 10 sec, 10 cost +}; + +enum DeathknightPetPassives +{ + AVOIDANCE = 62137 +}; + +enum DeathknightPetSpecial +{ + GHOUL_FRENZY_1 = 63560 //player-on-pet spell +}; + +class deathknight_pet_bot : public CreatureScript +{ +public: + deathknight_pet_bot() : CreatureScript("deathknight_pet_bot") { } + + CreatureAI* GetAI(Creature* creature) const override + { + return new deathknight_botpetAI(creature); + } + + struct deathknight_botpetAI : public bot_pet_ai + { + deathknight_botpetAI(Creature* creature) : bot_pet_ai(creature) { } + + void JustEnteredCombat(Unit* u) override { bot_pet_ai::JustEnteredCombat(u); } + void KilledUnit(Unit* u) override { bot_pet_ai::KilledUnit(u); } + void EnterEvadeMode(EvadeReason why = EVADE_REASON_OTHER) override { bot_pet_ai::EnterEvadeMode(why); } + void MoveInLineOfSight(Unit* u) override { bot_pet_ai::MoveInLineOfSight(u); } + void JustDied(Unit* u) override { bot_pet_ai::JustDied(u); } + void DoNonCombatActions(uint32 /*diff*/) { } + + void StartAttack(Unit* u, bool force = false) + { + if (!bot_pet_ai::StartAttack(u, force)) + return; + GetInPosition(force, u); + } + + void DoPetActions(uint32 /*diff*/) + { + } + + void UpdateAI(uint32 diff) override + { + if (!GlobalUpdate(diff)) + return; + + if (!me->IsInCombat()) + DoNonCombatActions(diff); + + DoPetActions(diff); + + if (!CheckAttackTarget()) + return; + + if (IsCasting()) + return; + + DoPetAttack(diff); + } + + void DoPetAttack(uint32 diff) + { + StartAttack(opponent, IsPetMelee()); + + float dist = me->GetDistance(opponent); + //Unit const* u = opponent->GetVictim(); + bool canDPS = petOwner->GetBotAI()->HasRole(BOT_ROLE_DPS); + + if (myType == BOT_PET_GHOUL) + { + if (IsSpellReady(GHOUL_FRENZY_1, diff) && canDPS && me->IsWithinMeleeRange(opponent)) + { + RefreshAura(GHOUL_FRENZY_1); + SetSpellCooldown(GHOUL_FRENZY_1, 30000); + } + + uint32 const energy = me->GetPower(POWER_ENERGY); + + if (IsSpellReady(HUDDLE_1, diff) && energy >= 10 && dist < 7 && + !me->getAttackers().empty() && GetHealthPCT(me) < 70) + { + me->CastSpell(me, GetSpell(HUDDLE_1), false); + return; + } + + if (IsSpellReady(LEAP_1, diff) && energy >= 10 && + !HasBotCommandState(BOT_COMMAND_STAY) && + !(opponent->GetTypeId() == TYPEID_UNIT && opponent->ToCreature()->isWorldBoss()) && + dist > 5 && dist < 30) + { + me->CastSpell(opponent, GetSpell(LEAP_1), false); + return; + } + + if (IsSpellReady(GNAW_1, diff) && canDPS && energy >= 30 && + me->IsWithinMeleeRange(opponent) && opponent->IsNonMeleeSpellCast(false, false, true)) + { + me->CastSpell(opponent, GetSpell(GNAW_1), false); + return; + } + + if (IsSpellReady(CLAW_1, diff) && canDPS && energy >= 40 && me->IsWithinMeleeRange(opponent)) + { + me->CastSpell(opponent, GetSpell(CLAW_1), false); + return; + } + } + } + + void OnPetClassSpellGo(SpellInfo const* /*spellInfo*/) override + { + } + + void SpellHit(WorldObject* wcaster, SpellInfo const* spell) override + { + Unit* caster = wcaster->ToUnit(); + if (!caster) + return; + + OnSpellHit(caster, spell); + } + + void SpellHitTarget(WorldObject* /*wtarget*/, SpellInfo const* /*spell*/) override + { + } + + void DamageDealt(Unit* victim, uint32& damage, DamageEffectType damageType) override + { + bot_pet_ai::DamageDealt(victim, damage, damageType); + } + + void DamageTaken(Unit* u, uint32& /*damage*/, DamageEffectType /*damageType*/, SpellInfo const* /*spellInfo*/) override + { + if (!u) + return; + if (!u->IsInCombat() && !me->IsInCombat()) + return; + OnOwnerDamagedBy(u); + } + + void OwnerAttackedBy(Unit* u) override + { + if (!u) + return; + OnOwnerDamagedBy(u); + } + + void Reset() override + { + } + + void InitPetSpells() override + { + InitSpellMap(CLAW_1); + InitSpellMap(GNAW_1); + InitSpellMap(LEAP_1); + InitSpellMap(HUDDLE_1); + } + + void ApplyPetPassives() const override + { + if (myType == BOT_PET_GHOUL/* || myType == BOT_PET_AOD_GHOUL*/) + RefreshAura(AVOIDANCE); + } + + private: + }; +}; + +void AddSC_deathknight_bot_pets() +{ + new deathknight_pet_bot(); +} diff --git a/src/server/game/AI/NpcBots/bpet_dreadlord.cpp b/src/server/game/AI/NpcBots/bpet_dreadlord.cpp new file mode 100644 index 000000000..69447d889 --- /dev/null +++ b/src/server/game/AI/NpcBots/bpet_dreadlord.cpp @@ -0,0 +1,163 @@ +#include "bot_ai.h" +#include "bpet_ai.h" +#include "Creature.h" +#include "ScriptMgr.h" +#include "SpellAuras.h" +/* +Dreadlord NpcBot Pets (by Trickerer onlysuffering@gmail.com) +Complete - 100% +TODO: +*/ + +enum DreadlordPetBaseSpells +{ +}; + +enum DreadlordPetPassives +{ +}; + +enum DreadlordPetSpecial +{ + IMMOLATION_DAMAGE = 35959, + + INFERNAL_DURATION = 180000 //3 min +}; + +class dreadlord_pet_bot : public CreatureScript +{ +public: + dreadlord_pet_bot() : CreatureScript("dreadlord_pet_bot") { } + + CreatureAI* GetAI(Creature* creature) const override + { + return new dreadlord_botpetAI(creature); + } + + struct dreadlord_botpetAI : public bot_pet_ai + { + dreadlord_botpetAI(Creature* creature) : bot_pet_ai(creature) { } + + void JustEnteredCombat(Unit* u) override { bot_pet_ai::JustEnteredCombat(u); } + void KilledUnit(Unit* u) override { bot_pet_ai::KilledUnit(u); } + void EnterEvadeMode(EvadeReason why = EVADE_REASON_OTHER) override { bot_pet_ai::EnterEvadeMode(why); } + void MoveInLineOfSight(Unit* u) override { bot_pet_ai::MoveInLineOfSight(u); } + void JustDied(Unit* u) override { bot_pet_ai::JustDied(u); } + void DoNonCombatActions(uint32 /*diff*/) { } + + void StartAttack(Unit* u, bool force = false) + { + if (!bot_pet_ai::StartAttack(u, force)) + return; + GetInPosition(force, u); + } + + void DoPetActions(uint32 /*diff*/) + { + } + + void UpdateAI(uint32 diff) override + { + if ((liveTimer += diff) >= INFERNAL_DURATION * (IAmFree() ? 20u : 1u) - 2000u) + { + canUpdate = false; + me->setDeathState(JUST_DIED); + return; + } + + if (!GlobalUpdate(diff)) + return; + + if (!me->IsInCombat()) + DoNonCombatActions(diff); + + //CheckDrainMana(diff); + + if (!CheckAttackTarget()) + return; + + if (IsCasting()) + return; + + DoPetAttack(diff); + } + + void DoPetAttack(uint32 /*diff*/) + { + StartAttack(opponent, IsPetMelee()); + } + + void ApplyClassDamageMultiplierSpell(int32& damage, SpellNonMeleeDamage& /*damageinfo*/, SpellInfo const* spellInfo, WeaponAttackType /*attackType*/, bool /*iscrit*/) const override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + float fdamage = float(damage); + + float pctbonus = 1.0f; + pctbonus *= 0.5f; + + if (baseId == IMMOLATION_DAMAGE) + fdamage += me->SpellBaseDamageBonusDone(SPELL_SCHOOL_MASK_MAGIC) * me->CalculateDefaultCoefficient(spellInfo, SPELL_DIRECT_DAMAGE) * me->CalculateSpellpowerCoefficientLevelPenalty(spellInfo); + + damage = int32(fdamage * pctbonus); + } + + void OnPetClassSpellGo(SpellInfo const* /*spellInfo*/) override + { + } + + void SpellHit(WorldObject* wcaster, SpellInfo const* spell) override + { + Unit* caster = wcaster->ToUnit(); + if (!caster) + return; + + OnSpellHit(caster, spell); + } + + void SpellHitTarget(WorldObject* /*wtarget*/, SpellInfo const* /*spell*/) override + { + } + + void DamageDealt(Unit* victim, uint32& damage, DamageEffectType damageType) override + { + bot_pet_ai::DamageDealt(victim, damage, damageType); + } + + void DamageTaken(Unit* u, uint32& /*damage*/, DamageEffectType /*damageType*/, SpellInfo const* /*spellInfo*/) override + { + if (!u) + return; + if (!u->IsInCombat() && !me->IsInCombat()) + return; + OnOwnerDamagedBy(u); + } + + void OwnerAttackedBy(Unit* u) override + { + if (!u) + return; + OnOwnerDamagedBy(u); + } + + void Reset() override + { + liveTimer = 0; + } + + void InitPetSpells() override + { + } + + void ApplyPetPassives() const override + { + } + + private: + uint32 liveTimer; + }; +}; + +void AddSC_dreadlord_bot_pets() +{ + new dreadlord_pet_bot(); +} diff --git a/src/server/game/AI/NpcBots/bpet_druid.cpp b/src/server/game/AI/NpcBots/bpet_druid.cpp new file mode 100644 index 000000000..6f37241a4 --- /dev/null +++ b/src/server/game/AI/NpcBots/bpet_druid.cpp @@ -0,0 +1,146 @@ +#include "bot_ai.h" +#include "bpet_ai.h" +#include "ScriptMgr.h" +#include "TemporarySummon.h" +/* +Druid NpcBot Pets (by Trickerer onlysuffering@gmail.com) +Complete - 100% +TODO: +*/ + +enum DruidPetBaseSpells +{ +}; + +enum DruidPetPassives +{ +}; + +enum DruidPetSpecial +{ + TREANT_DURATION = 30000 +}; + +class druid_pet_bot : public CreatureScript +{ +public: + druid_pet_bot() : CreatureScript("druid_pet_bot") { } + + CreatureAI* GetAI(Creature* creature) const override + { + return new druid_botpetAI(creature); + } + + struct druid_botpetAI : public bot_pet_ai + { + druid_botpetAI(Creature* creature) : bot_pet_ai(creature) { } + + void JustEnteredCombat(Unit* u) override { bot_pet_ai::JustEnteredCombat(u); } + void KilledUnit(Unit* u) override { bot_pet_ai::KilledUnit(u); } + void EnterEvadeMode(EvadeReason why = EVADE_REASON_OTHER) override { bot_pet_ai::EnterEvadeMode(why); } + void MoveInLineOfSight(Unit* u) override { bot_pet_ai::MoveInLineOfSight(u); } + void JustDied(Unit* u) override { bot_pet_ai::JustDied(u); } + void DoNonCombatActions(uint32 /*diff*/) { } + + void StartAttack(Unit* u, bool force = false) + { + if (!bot_pet_ai::StartAttack(u, force)) + return; + GetInPosition(force, u); + } + + void DoPetActions(uint32 /*diff*/) + { + } + + void UpdateAI(uint32 diff) override + { + if ((liveTimer += diff) >= TREANT_DURATION) + { + canUpdate = false; + me->ToTempSummon()->UnSummon(1); + return; + } + + if (!GlobalUpdate(diff)) + return; + + if (!me->IsInCombat()) + DoNonCombatActions(diff); + + //CheckDrainMana(diff); + + if (!CheckAttackTarget()) + return; + + if (IsCasting()) + return; + + DoPetAttack(diff); + } + + void DoPetAttack(uint32 /*diff*/) + { + StartAttack(opponent, IsPetMelee()); + } + + void OnPetClassSpellGo(SpellInfo const* /*spellInfo*/) override + { + } + + void SpellHit(WorldObject* wcaster, SpellInfo const* spell) override + { + Unit* caster = wcaster->ToUnit(); + if (!caster) + return; + + OnSpellHit(caster, spell); + } + + void SpellHitTarget(WorldObject* /*wtarget*/, SpellInfo const* /*spell*/) override + { + } + + void DamageDealt(Unit* victim, uint32& damage, DamageEffectType damageType) override + { + bot_pet_ai::DamageDealt(victim, damage, damageType); + } + + void DamageTaken(Unit* u, uint32& /*damage*/, DamageEffectType /*damageType*/, SpellInfo const* /*spellInfo*/) override + { + if (!u) + return; + if (!u->IsInCombat() && !me->IsInCombat()) + return; + OnOwnerDamagedBy(u); + } + + void OwnerAttackedBy(Unit* u) override + { + if (!u) + return; + OnOwnerDamagedBy(u); + } + + void Reset() override + { + liveTimer = 0; + } + + void InitPetSpells() override + { + } + + void ApplyPetPassives() const override + { + } + + private: + uint32 liveTimer; + }; +}; + +void AddSC_druid_bot_pets() +{ + new druid_pet_bot(); +} diff --git a/src/server/game/AI/NpcBots/bpet_hunter.cpp b/src/server/game/AI/NpcBots/bpet_hunter.cpp new file mode 100644 index 000000000..511fa087d --- /dev/null +++ b/src/server/game/AI/NpcBots/bpet_hunter.cpp @@ -0,0 +1,1000 @@ +#include "bot_ai.h" +#include "bpet_ai.h" +#include "CellImpl.h" +#include "GridNotifiers.h" +#include "GridNotifiersImpl.h" +#include "Map.h" +#include "ScriptMgr.h" +#include "SpellMgr.h" +#include "Player.h" +/* +Hunter NpcBot Pets (by Trickerer onlysuffering@gmail.com) +Complete - 100% +TODO: +*/ +//talent tiers 20-32-44-56-68-80 +enum HunterPetBaseSpells +{ + //common + GROWL_1 = 2649,//1 + COWER_1 = 1742,//20 + //semi-common + //attack + BITE_1 = 17253,//1 cost 25, Bat, Boar, Carrion Bird, Chimaera, Core Hound, Crocolisk, Devilsaur, Dragonhawk, Hyena, Nether Ray, Ravager, Serpent, Wolf, Worm + CLAW_1 = 16827,//1 cost 25, Bear, Bird of Prey, Cat, Crab, Raptor, Scorpid, Silithid, Spirit Beast, Tallstrider + SMACK_1 = 49966,//1 cost 25, Gorilla, Sporebat, Moth, Rhino, Wasp + //movement + //charge + SWOOP_1 = 52825,//44 cost 35 Carrion Bird, Wasp, Teromoth + CHARGE_1 = 61685,//20/44 cost 35 Bear, Boar, Cat, Core Hound, Crab, Crocolisk, Devilsaur, Gorilla, Hyena, Raptor, Rhino, Scorpid, Spirit Beast, Tallstrider, Turtle, Warp Stalker, Wolf, Worm + //sprint + DASH_1 = 61684,//20/44 cost 30 Cat, Core Hound, Devilsaur, Hyena, Raptor, Ravager, Serpent, Silithid, Spider, Spirit Beast, Tallstrider, Warp Stalker, Wolf + DIVE_1 = 23145,//20 cost 30 Bat, Bird of Prey, Carrion Bird, Chimaera, Dragonhawk, Moth, Nether Ray, Sporebat, Wasp, Wind Serpent + //talents cunning + CARRION_FEEDER_1 = 54045,//44 triggered spell + WOLVERINE_BITE_1 = 53508,//68 after crit (any time for bot) + ROAR_OF_RECOVERY_1 = 53517,//68 mana regen + BULLHEADED_1 = 53490,//68 movement imparing remove + //talents ferocity + //HEART_OF_THE_PHOENIX_1 = 55709, + RABID_1 = 53401,//68 attack increase proc + LICK_YOUR_WOUNDS_1 = 53426,//68 full heal over 5, channeled + CALL_OF_THE_WILD_1 = 53434,//68 10% AP for pet and hunter + //talents tenacity + THUNDERSTOMP_1 = 63900,//44 + LAST_STAND_1 = 53478,//68 30% + TAUNT_1 = 53477,//68 3 min cd 126 sec improved + ROAR_OF_SACRIFICE_1 = 53480,//68 + INTERVENE_1 = 53476,//68 + //pet-specific + //cunning + SONIC_BLAST_1 = 50519,//bat c80 dmg/stun 20y cd60 + SNATCH_1 = 50541,//birdop c20 dmg/disarm 5y cd60 + FROSTSTORM_BREATH_1 = 54644,//chimera c20 dmg/slow 30y cd10 + FIRE_BREATH_1 = 34889,//dhawk c20 dmg/dot 20y cd10 + NETHER_SHOCK_1 = 50479,//nray c20 dmg/interrupt 20y cd40 + RAVAGE_1 = 50518,//ravager c0 (bug?) dmg/stun 5y cd40 + POISON_SPIT_1 = 35387,//serpent c20 dot/slowcast 30y cd10 + VENOM_WEB_SPRAY_1 = 54706,//silithid c0 dot/root 30y cd40 + WEB_1 = 4167,//spider c0 root 30y cd40 + SPORE_CLOUD_1 = 50274,//sporebat c20 aoedot/-armorpct 6yd cd10 + LIGHTNING_BREATH_1 = 24844,//wserpent c20 dmg 20y cd10 + //ferocity + DEMORALIZING_SCREECH_1 = 24423,//cbird c20 dmg/aoe-ap 5y cd10 + PROWL_1 = 24450,//cat,spbeast c0 stealth 0y cd10 + LAVA_BREATH_1 = 58604,//chound c20 dmg/slowcast 30y cd10 + MONSTROUS_BITE_1 = 54680,//dsaur c20 dmg/buff 5y cd10 + TENDON_RIP_1 = 50271,//hyena c20 dmg/snare 5y cd20 + SERENITY_DUST_1 = 50318,//tmoth c0 hot/buff+ap 0y cd60 + SAVAGE_REND_1 = 50498,//raptor c20 dmg/dot 5y cd60 + SPIRIT_STRIKE_1 = 61193,//spbeast c20 dmg/dot 30y cd10 + DUST_CLOUD_1 = 50285,//tstrider c20 aoe-100hit 10y cd40 lvl6 + STING_1 = 56626,//wasp c20 dmg/-5%armor 5y cd6 + FURIOUS_HOWL_1 = 24604,//wolf c20 buff+ap 100y cd40 + //tenacity + SWIPE_1 = 50256,//bear c20 dmg 5y cd5 + GORE_1 = 35290,//boar c20 dmg 5y cd10 + PIN_1 = 50245,//crab c0 root/dot 5y cd40 + PUMMEL_1 = 26090,//gorilla c20 interrupt 5y cd30 + STAMPEDE_1 = 57386,//rhino c0 dmg/debuff+bleed 5y cd60 + SCORPID_POISON_1 = 24640,//scorpid c20 threat/dot 5y cd10 + SHELL_SHIELD_1 = 26064,//turtle c0 buff%dmgtaken 0y cd60 + WARP_1 = 35346,//wstalker c0 tp/buff50%avoid 30y cd15 + ACID_SPIT_1 = 55749,//worm c20 dmg/debuff-10%armor 30y cd10 + + //from hunter's talents + SPIRIT_BOND_PET = 24529, + KINDRED_SPIRITS_PET = 57475, + INTIMIDATION_1 = 24394, + BESTIAL_WRATH_1 = 19574, + BEAST_WITHIN_1 = 34471 +}; + +enum HunterPetPassives +{ + //common + AVOIDANCE = 65220, + //pet talents + COBRA_REFLEXES = 61683,//rank 2 + //BOARS_SPEED = 19596, + BLOOD_OF_THE_RHINO = 53482,//rank 2 + OWLS_FOCUS = 53516,//rank 2 + CULLING_THE_HERD = 52858,//rank 3 + GRACE_OF_THE_MANTIS = 53451,//rank 2 + CORNERED = 53497,//rank 2 + FEEDING_FRENZY = 53512,//rank 2 + SILVERBACK = 62765,//rank 2 + //special + //Catlike Reflexes and Serpent's Swiftness replacement + HASTE_DODGE_PASSIVE = 13789,//Lightning Reflexes rank 3 6 dodge 10 haste +}; + +enum HunterPetSpecial +{ + PET_CATEGORY_CUNNING = 1, + PET_CATEGORY_FEROCITY = 2, + PET_CATEGORY_TENACITY = 3, + + GO_FOR_THE_THROAT_ENERGIZE = 34953, + FRENZY_BUFF = 19615, + //HEART_OF_THE_PHOENIX_TRIGGERED = 54114,//resurrect pet effect + //HEART_OF_THE_PHOENIX_DEBUFF = 55711 //Weakened Heart dummy eff 0 icon 2787 +}; + +class hunter_pet_bot : public CreatureScript +{ +public: + hunter_pet_bot() : CreatureScript("hunter_pet_bot") { } + + CreatureAI* GetAI(Creature* creature) const override + { + return new hunter_botpetAI(creature); + } + + struct hunter_botpetAI : public bot_pet_ai + { + hunter_botpetAI(Creature* creature) : bot_pet_ai(creature) { } + + void JustEnteredCombat(Unit* u) override { bot_pet_ai::JustEnteredCombat(u); } + void KilledUnit(Unit* u) override { bot_pet_ai::KilledUnit(u); } + void EnterEvadeMode(EvadeReason why = EVADE_REASON_OTHER) override { bot_pet_ai::EnterEvadeMode(why); } + void MoveInLineOfSight(Unit* u) override { bot_pet_ai::MoveInLineOfSight(u); } + void JustDied(Unit* u) override { bot_pet_ai::JustDied(u); } + void DoNonCombatActions(uint32 /*diff*/) { } + + void StartAttack(Unit* u, bool force = false) + { + if (!bot_pet_ai::StartAttack(u, force)) + return; + GetInPosition(force, u); + } + + void DoPetActions(uint32 diff) + { + if (GetSpell(SPIRIT_BOND_PET) && IsSpellReady(SPIRIT_BOND_PET, diff, false) && + !petOwner->GetAuraEffect(SPELL_AURA_MOD_HEALING_PCT, SPELLFAMILY_GENERIC, 960, 1)) + { + me->CastSpell(me, SPIRIT_BOND_PET, true); + SetSpellCooldown(SPIRIT_BOND_PET, uint32(-1)); + } + if (GetSpell(KINDRED_SPIRITS_PET) && IsSpellReady(KINDRED_SPIRITS_PET, diff, false) && + !petOwner->GetAuraEffect(SPELL_AURA_MOD_INCREASE_SPEED, SPELLFAMILY_GENERIC, 3559, 0)) + { + me->CastSpell(me, KINDRED_SPIRITS_PET, true); + SetSpellCooldown(KINDRED_SPIRITS_PET, uint32(-1)); + } + + //Ignoring pet category + + if (IsSpellReady(CARRION_FEEDER_1, diff, false) && !me->isMoving() && GetHealthPCT(me) <= 80 && + !me->IsInCombat() && !me->GetVictim() && me->getAttackers().empty() && + !me->HasAuraType(SPELL_AURA_PERIODIC_DAMAGE) && Rand() < 20) + { + WorldObject* result = nullptr; + Trinity::AnyDeadUnitSpellTargetInRangeCheck check(me, 5.f, sSpellMgr->GetSpellInfo(CARRION_FEEDER_1), TARGET_CHECK_ENEMY); + Trinity::WorldObjectSearcher searcher(me, result, check); + Cell::VisitWorldObjects(me, searcher, 5.f); + + if (result) + { + me->CastSpell(me, GetSpell(CARRION_FEEDER_1), false); + SetSpellCooldown(CARRION_FEEDER_1, 21000); + return; + } + } + + if (IsSpellReady(LICK_YOUR_WOUNDS_1, diff, false) && !me->isMoving() && !me->GetVictim() && + me->getAttackers().empty() && GetHealthPCT(me) <= 50 + 30 * me->IsInCombat() && + !me->HasAuraType(SPELL_AURA_PERIODIC_DAMAGE) && Rand() < 40) + { + me->CastSpell(me, GetSpell(LICK_YOUR_WOUNDS_1), false); + SetSpellCooldown(LICK_YOUR_WOUNDS_1, 126000); + return; + } + + if (IsSpellReady(ROAR_OF_RECOVERY_1, diff, false) && petOwner->IsInCombat() && opponent && + GetManaPCT(petOwner) < 65 && petOwner->GetDistance(me) < 40) + { + me->CastSpell(me, GetSpell(ROAR_OF_RECOVERY_1), false); + SetSpellCooldown(ROAR_OF_RECOVERY_1, 126000); + return; + } + + if (IsSpellReady(INTERVENE_1, diff, false) && GetHealthPCT(petOwner) < 95 && !petOwner->getAttackers().empty() && + me->getAttackers().size() <= petOwner->getAttackers().size()) + { + float petdist = me->GetDistance(petOwner); + if (petdist < 25 && petdist > 8) + { + me->CastSpell(petOwner, GetSpell(INTERVENE_1), false); + SetSpellCooldown(INTERVENE_1, 21000); + return; + } + } + } + + void UpdateAI(uint32 diff) override + { + if (!GlobalUpdate(diff)) + return; + + if (!me->IsInCombat()) + DoNonCombatActions(diff); + + DoPetActions(diff); + //CheckDrainMana(diff); + + if (!CheckAttackTarget()) + return; + + if (IsCasting()) + return; + + DoPetAttack(diff); + } + + void DoPetAttack(uint32 diff) + { + StartAttack(opponent, IsPetMelee()); + + Unit const* u = opponent->GetVictim(); + float dist = me->GetDistance(opponent); + uint32 focus = me->GetPower(POWER_FOCUS); + bool canDPS = petOwner->GetBotAI()->HasRole(BOT_ROLE_DPS); + + //improved + Longevity applied to cds + + if (IsSpellReady(BESTIAL_WRATH_1, diff, false) && canDPS && opponent && dist < 10 && + (opponent->GetHealth() > petOwner->GetMaxHealth()/4 * (1 + opponent->getAttackers().size()) || + opponent->GetTypeId() == TYPEID_PLAYER)) + { + if (petOwner->AddAura(GetSpell(BESTIAL_WRATH_1), me)) + { + if (GetSpell(BEAST_WITHIN_1)) + petOwner->AddAura(GetSpell(BEAST_WITHIN_1), petOwner); + + SetSpellCooldown(BESTIAL_WRATH_1, 70000); + return; + } + } + + //LAST STAND + if (IsSpellReady(LAST_STAND_1, diff, false) && + GetHealthPCT(me) < (30 + 20 * (opponent->getAttackers().size() > 1) + 10 * me->HasAuraType(SPELL_AURA_PERIODIC_DAMAGE))) + { + me->CastSpell(me, GetSpell(LAST_STAND_1), false); + SetSpellCooldown(LAST_STAND_1, 252000); + } + + if (IsSpellReady(INTIMIDATION_1, diff, false) && !CCed(opponent) && me->IsWithinMeleeRange(opponent)) + { + me->CastSpell(opponent, GetSpell(INTIMIDATION_1), false); + SetSpellCooldown(INTIMIDATION_1, 60000); + return; + } + + if (IsSpellReady(GROWL_1, diff, false) && u && u != me && focus >= 15 && me->IsWithinMeleeRange(opponent) && + opponent->CanHaveThreatList() && !CCed(opponent) && !opponent->HasAuraType(SPELL_AURA_MOD_TAUNT) && + (!IsTank(u) || (GetHealthPCT(u) < 30 && GetHealthPCT(me) > 50)) && IsInBotParty(u)) + { + me->CastSpell(opponent, GetSpell(GROWL_1), false); + SetSpellCooldown(GROWL_1, 3500); + return; + } + + if (IsSpellReady(TAUNT_1, diff, false) && u && u != me && me->IsWithinMeleeRange(opponent) && + opponent->CanHaveThreatList() && !CCed(opponent) && !opponent->HasAuraType(SPELL_AURA_MOD_TAUNT) && + (!IsTank(u) || (GetHealthPCT(u) < 30 && GetHealthPCT(me) > 50)) && IsInBotParty(u)) + { + me->CastSpell(opponent, GetSpell(TAUNT_1), false); + SetSpellCooldown(TAUNT_1, 126000); + return; + } + + if (IsSpellReady(COWER_1, diff, false) && !me->getAttackers().empty() && + me->GetDistance(*me->getAttackers().begin()) < 7 && GetHealthPCT(me) < 90) + { + me->CastSpell(me, GetSpell(COWER_1), false); + SetSpellCooldown(COWER_1, 31500); + return; + } + + if (IsSpellReady(BULLHEADED_1, diff, false) && GetHealthPCT(me) < 90 && + ((!me->getAttackers().empty() && me->GetDistance(*me->getAttackers().begin()) < 7) || + (dist > 3 && !opponent->HasInArc(float(M_PI)/2, me) && + (CCed(me, true) || me->HasAuraWithMechanic(1<CastSpell(me, GetSpell(BULLHEADED_1), false); + SetSpellCooldown(BULLHEADED_1, 31500); + return; + } + + uint32 SPRINT = IsPetTypeSpell(DASH_1) ? DASH_1 : IsPetTypeSpell(DIVE_1) ? DIVE_1 : 0; + if (SPRINT && GetSpell(SPRINT) && IsSpellReady(SPRINT, diff, false) && dist > 10 && dist < 30 && + !HasBotCommandState(BOT_COMMAND_STAY)) + { + me->CastSpell(opponent, GetSpell(SPRINT), false); + SetSpellCooldown(SPRINT, 17500); + return; + } + + if (IsSpellReady(CALL_OF_THE_WILD_1, diff, false) && canDPS && opponent && dist < 10) + { + me->CastSpell(me, GetSpell(CALL_OF_THE_WILD_1), false); + SetSpellCooldown(CALL_OF_THE_WILD_1, 210000); + return; + } + + if (IsSpellReady(RABID_1, diff, false) && canDPS && dist < 10) + { + me->CastSpell(me, GetSpell(RABID_1), false); + SetSpellCooldown(RABID_1, 31500); + return; + } + + if (IsSpellReady(THUNDERSTOMP_1, diff, false) && canDPS && focus >= 20 && + me->IsWithinMeleeRange(opponent) && me->getAttackers().size() > 1) + { + me->CastSpell(opponent, GetSpell(THUNDERSTOMP_1), false); + SetSpellCooldown(THUNDERSTOMP_1, 7000); + return; + } + + if (myType == BOT_PET_BAT) + { + if (IsSpellReady(SONIC_BLAST_1, diff, false) && canDPS && focus >= 80 && + dist < 20 && opponent->IsNonMeleeSpellCast(false, false, true)) + { + me->CastSpell(opponent, GetSpell(SONIC_BLAST_1), false); + SetSpellCooldown(SONIC_BLAST_1, 42000); + return; + } + } + else if (myType == BOT_PET_BIRDOFPREY) + { + if (IsSpellReady(SNATCH_1, diff, false) && canDPS && focus >= 20 && + me->IsWithinMeleeRange(opponent) && !opponent->HasAuraType(SPELL_AURA_MOD_DISARM) && + ((opponent->GetTypeId() == TYPEID_PLAYER) ? opponent->ToPlayer()->GetWeaponForAttack(BASE_ATTACK) != nullptr : + opponent->GetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID) != 0)) + { + me->CastSpell(opponent, GetSpell(SNATCH_1), false); + SetSpellCooldown(SNATCH_1, 42000); + return; + } + } + else if (myType == BOT_PET_CHIMAERA) + { + if (IsSpellReady(FROSTSTORM_BREATH_1, diff, false) && canDPS && focus >= 20 && dist < 30) + { + me->CastSpell(opponent, GetSpell(FROSTSTORM_BREATH_1), false); + SetSpellCooldown(FROSTSTORM_BREATH_1, 7000); + return; + } + } + else if (myType == BOT_PET_DRAGONHAWK) + { + if (IsSpellReady(FIRE_BREATH_1, diff, false) && canDPS && focus >= 20 && dist < 20) + { + me->CastSpell(opponent, GetSpell(FIRE_BREATH_1), false); + SetSpellCooldown(FIRE_BREATH_1, 7000); + return; + } + } + else if (myType == BOT_PET_NETHERRAY) + { + if (IsSpellReady(NETHER_SHOCK_1, diff, false) && canDPS && focus >= 20 && dist < 20 && + opponent->IsNonMeleeSpellCast(false, false, true)) + { + me->CastSpell(opponent, GetSpell(NETHER_SHOCK_1), false); + SetSpellCooldown(NETHER_SHOCK_1, 28000); + return; + } + } + else if (myType == BOT_PET_RAVAGER) + { + if (IsSpellReady(RAVAGE_1, diff, false) && canDPS/* && focus >= 0*/ && + me->IsWithinMeleeRange(opponent) && opponent->IsNonMeleeSpellCast(false, false, true)) + { + me->CastSpell(opponent, GetSpell(RAVAGE_1), false); + SetSpellCooldown(RAVAGE_1, 28000); + return; + } + } + else if (myType == BOT_PET_SERPENT) + { + if (IsSpellReady(POISON_SPIT_1, diff, false) && canDPS && focus >= 20 && dist < 30) + { + me->CastSpell(opponent, GetSpell(POISON_SPIT_1), false); + SetSpellCooldown(POISON_SPIT_1, 7000); + return; + } + } + else if (myType == BOT_PET_SILITHID) + { + if (IsSpellReady(VENOM_WEB_SPRAY_1, diff, false) && canDPS/* && focus >= 0*/ && dist < 30 && + !CCed(opponent, true)) + { + me->CastSpell(opponent, GetSpell(VENOM_WEB_SPRAY_1), false); + SetSpellCooldown(VENOM_WEB_SPRAY_1, 28000); + return; + } + } + else if (myType == BOT_PET_SPIDER) + { + if (IsSpellReady(WEB_1, diff, false)/* && focus >= 0*/ && dist < 30 && !CCed(opponent, true)) + { + me->CastSpell(opponent, GetSpell(WEB_1), false); + SetSpellCooldown(WEB_1, 28000); + return; + } + } + else if (myType == BOT_PET_SPOREBAT) + { + if (IsSpellReady(SPORE_CLOUD_1, diff, false) && canDPS && focus >= 20 && dist < 5) + { + me->CastSpell(opponent, GetSpell(SPORE_CLOUD_1), false); + SetSpellCooldown(SPORE_CLOUD_1, 7000); + return; + } + } + else if (myType == BOT_PET_WINDSERPENT) + { + if (IsSpellReady(LIGHTNING_BREATH_1, diff, false) && canDPS && focus >= 20 && dist < 20) + { + me->CastSpell(opponent, GetSpell(LIGHTNING_BREATH_1), false); + SetSpellCooldown(LIGHTNING_BREATH_1, 7000); + return; + } + } + else if (myType == BOT_PET_CARRIONBIRD) + { + if (IsSpellReady(DEMORALIZING_SCREECH_1, diff, false) && canDPS && focus >= 20 && + me->IsWithinMeleeRange(opponent)) + { + me->CastSpell(opponent, GetSpell(DEMORALIZING_SCREECH_1), false); + SetSpellCooldown(DEMORALIZING_SCREECH_1, 7000); + return; + } + } + else if (myType == BOT_PET_CAT) + { + if (IsSpellReady(PROWL_1, diff, false) && !me->IsInCombat() && !me->HasStealthAura() && + /*focus >= 0 && */dist < 20 && dist > 5) + { + me->CastSpell(opponent, GetSpell(PROWL_1), false); + SetSpellCooldown(PROWL_1, 7000); + return; + } + } + else if (myType == BOT_PET_COREHOUND) + { + if (IsSpellReady(LAVA_BREATH_1, diff, false) && canDPS && focus >= 20 && dist < 30) + { + me->CastSpell(opponent, GetSpell(LAVA_BREATH_1), false); + SetSpellCooldown(LAVA_BREATH_1, 7000); + return; + } + } + else if (myType == BOT_PET_DEVILSAUR) + { + if (IsSpellReady(MONSTROUS_BITE_1, diff, false) && canDPS && focus >= 20 && + me->IsWithinMeleeRange(opponent)) + { + me->CastSpell(opponent, GetSpell(MONSTROUS_BITE_1), false); + SetSpellCooldown(MONSTROUS_BITE_1, 7000); + return; + } + } + else if (myType == BOT_PET_HYENA) + { + if (IsSpellReady(TENDON_RIP_1, diff, false) && canDPS && focus >= 20 && + me->IsWithinMeleeRange(opponent)) + { + me->CastSpell(opponent, GetSpell(TENDON_RIP_1), false); + SetSpellCooldown(TENDON_RIP_1, 14000); + return; + } + } + else if (myType == BOT_PET_TEROMOTH) + { + if (IsSpellReady(SERENITY_DUST_1, diff, false) && (canDPS || GetHealthPCT(me) < 90) && + /*focus >= 0 && */dist < 7) + { + me->CastSpell(me, GetSpell(SERENITY_DUST_1), false); + SetSpellCooldown(SERENITY_DUST_1, 42000); + return; + } + } + else if (myType == BOT_PET_RAPTOR) + { + if (IsSpellReady(SAVAGE_REND_1, diff, false) && canDPS && focus >= 20 && + me->IsWithinMeleeRange(opponent)) + { + me->CastSpell(opponent, GetSpell(SAVAGE_REND_1), false); + SetSpellCooldown(SAVAGE_REND_1, 42000); + return; + } + } + else if (myType == BOT_PET_SPIRITBEAST) + { + if (IsSpellReady(PROWL_1, diff, false) && !me->IsInCombat() && !me->HasStealthAura() && + /*focus >= 0 && */dist < 30 && dist > 5) + { + me->CastSpell(opponent, GetSpell(PROWL_1), false); + SetSpellCooldown(PROWL_1, 10000); //custom + return; + } + if (IsSpellReady(SPIRIT_STRIKE_1, diff, false) && canDPS && !me->HasStealthAura() && + focus >= 20 && dist < 30) + { + me->CastSpell(opponent, GetSpell(SPIRIT_STRIKE_1), false); + SetSpellCooldown(SPIRIT_STRIKE_1, 7000); + return; + } + } + else if (myType == BOT_PET_TALLSTRIDER) + { + if (IsSpellReady(DUST_CLOUD_1, diff, false) && focus >= 20 && me->IsWithinMeleeRange(opponent)) + { + me->CastSpell(opponent, GetSpell(DUST_CLOUD_1), false); + SetSpellCooldown(DUST_CLOUD_1, 28000); + return; + } + } + else if (myType == BOT_PET_WASP) + { + if (IsSpellReady(STING_1, diff, false) && canDPS && focus >= 20 && + me->IsWithinMeleeRange(opponent)) + { + me->CastSpell(opponent, GetSpell(STING_1), false); + SetSpellCooldown(STING_1, 4000); + return; + } + } + else if (myType == BOT_PET_WOLF) + { + if (IsSpellReady(FURIOUS_HOWL_1, diff, false) && canDPS && focus >= 20) + { + me->CastSpell(me, GetSpell(FURIOUS_HOWL_1), false); + SetSpellCooldown(FURIOUS_HOWL_1, 28000); + return; + } + } + else if (myType == BOT_PET_BEAR) + { + if (IsSpellReady(SWIPE_1, diff, false) && canDPS && focus >= 20 && + me->IsWithinMeleeRange(opponent)) + { + me->CastSpell(opponent, GetSpell(SWIPE_1), false); + SetSpellCooldown(SWIPE_1, 3500); + return; + } + } + else if (myType == BOT_PET_BOAR) + { + if (IsSpellReady(GORE_1, diff, false) && canDPS && focus >= 20 && + me->IsWithinMeleeRange(opponent)) + { + me->CastSpell(opponent, GetSpell(GORE_1), false); + SetSpellCooldown(GORE_1, 7000); + return; + } + } + else if (myType == BOT_PET_CRAB) + { + if (IsSpellReady(PIN_1, diff, false) && canDPS/* && focus >= 0*/ && + me->IsWithinMeleeRange(opponent) && !CCed(opponent, true)) + { + me->CastSpell(opponent, GetSpell(PIN_1), false); + SetSpellCooldown(PIN_1, 28000); + return; + } + } + else if (myType == BOT_PET_GORILLA) + { + if (IsSpellReady(PUMMEL_1, diff, false) && focus >= 20 && me->IsWithinMeleeRange(opponent) && + opponent->IsNonMeleeSpellCast(false,false,true)) + { + me->CastSpell(opponent, GetSpell(PUMMEL_1), false); + SetSpellCooldown(PUMMEL_1, 21000); + return; + } + } + else if (myType == BOT_PET_RHINO) + { + if (IsSpellReady(STAMPEDE_1, diff, false) && canDPS/* && focus >= 0*/ && + me->IsWithinMeleeRange(opponent)) + { + me->CastSpell(opponent, GetSpell(STAMPEDE_1), false); + SetSpellCooldown(STAMPEDE_1, 42000); + return; + } + } + else if (myType == BOT_PET_SCORPID) + { + if (IsSpellReady(SCORPID_POISON_1, diff, false) && canDPS && focus >= 20 && + me->IsWithinMeleeRange(opponent)) + { + me->CastSpell(opponent, GetSpell(SCORPID_POISON_1), false); + SetSpellCooldown(SCORPID_POISON_1, 7000); + return; + } + } + else if (myType == BOT_PET_TURTLE) + { + if (IsSpellReady(SHELL_SHIELD_1, diff, false)/* && focus >= 0*/ && dist < 7 && + !me->getAttackers().empty()) + { + me->CastSpell(me, GetSpell(SHELL_SHIELD_1), false); + SetSpellCooldown(SHELL_SHIELD_1, 42000); + return; + } + } + else if (myType == BOT_PET_WARPSTALKER) + { + if (IsSpellReady(WARP_1, diff, false)/* && focus >= 0*/ && dist < 10) + { + me->CastSpell(opponent, GetSpell(WARP_1), false); + SetSpellCooldown(WARP_1, 10500); + return; + } + } + else if (myType == BOT_PET_WORM) + { + if (IsSpellReady(ACID_SPIT_1, diff, false) && canDPS && focus >= 20 && dist < 30) + { + me->CastSpell(opponent, GetSpell(ACID_SPIT_1), false); + SetSpellCooldown(ACID_SPIT_1, 7000); + return; + } + } + + uint32 CHARGE = IsPetTypeSpell(SWOOP_1) ? SWOOP_1 : IsPetTypeSpell(CHARGE_1) ? CHARGE_1 : 0; + if (CHARGE && GetSpell(CHARGE) && IsSpellReady(CHARGE, diff, false) && !CCed(opponent, true) && !me->HasStealthAura() && + !(opponent->GetTypeId() == TYPEID_UNIT && opponent->ToCreature()->isWorldBoss()) && + !HasBotCommandState(BOT_COMMAND_STAY) && + dist > 8 && dist < 25) + { + me->CastSpell(opponent, GetSpell(CHARGE), false); + SetSpellCooldown(CHARGE, 17500); + return; + } + + if (GetSpell(WOLVERINE_BITE_1) && canDPS && me->IsWithinMeleeRange(opponent)) + { + me->CastSpell(opponent, GetSpell(WOLVERINE_BITE_1), false); + SetSpellCooldown(WOLVERINE_BITE_1, 7000); + return; + } + + uint32 ATTACK_1 = IsPetTypeSpell(BITE_1) ? BITE_1 : IsPetTypeSpell(CLAW_1) ? CLAW_1 : SMACK_1; + if (GetSpell(ATTACK_1) && IsSpellReady(ATTACK_1, diff) && canDPS && focus >= 25 && me->IsWithinMeleeRange(opponent)) + { + me->CastSpell(opponent, GetSpell(ATTACK_1), false); + return; + } + } + + void OnPetClassSpellGo(SpellInfo const* /*spellInfo*/) override + { + } + + void SpellHit(WorldObject* wcaster, SpellInfo const* spell) override + { + Unit* caster = wcaster->ToUnit(); + if (!caster) + return; + + OnSpellHit(caster, spell); + } + + void SpellHitTarget(WorldObject* /*wtarget*/, SpellInfo const* /*spell*/) override + { + } + + void DamageDealt(Unit* victim, uint32& damage, DamageEffectType damageType) override + { + bot_pet_ai::DamageDealt(victim, damage, damageType); + } + + void DamageTaken(Unit* u, uint32& /*damage*/, DamageEffectType /*damageType*/, SpellInfo const* /*spellInfo*/) override + { + if (!u) + return; + if (!u->IsInCombat() && !me->IsInCombat()) + return; + OnOwnerDamagedBy(u); + } + + void OwnerAttackedBy(Unit* u) override + { + if (!u) + return; + OnOwnerDamagedBy(u); + } + + void Reset() override + { + } + + void InitPetSpells() override + { + uint8 lvl = me->GetLevel(); + bool isBeas = Spec() == BOT_SPEC_HUNTER_BEASTMASTERY; + + InitSpellMap(GROWL_1); + InitSpellMap(COWER_1); + + InitSpellMap(BITE_1); + InitSpellMap(CLAW_1); + InitSpellMap(SMACK_1); + + //talents + /*Talent*/IsPetTypeSpell(SWOOP_1) ? InitSpellMap(SWOOP_1, true) : RemoveSpell(SWOOP_1); + /*Talent*/IsPetTypeSpell(CHARGE_1) ? InitSpellMap(CHARGE_1, true) : RemoveSpell(CHARGE_1); + /*Talent*/IsPetTypeSpell(DASH_1) ? InitSpellMap(DASH_1, true) : RemoveSpell(DASH_1); + /*Talent*/IsPetTypeSpell(DIVE_1) ? InitSpellMap(DIVE_1, true) : RemoveSpell(DIVE_1); + //talents cunning + /*Talent*/lvl >= 44 ? InitSpellMap(CARRION_FEEDER_1, true) : RemoveSpell(CARRION_FEEDER_1); + /*Talent*/lvl >= 68 ? InitSpellMap(WOLVERINE_BITE_1, true) : RemoveSpell(WOLVERINE_BITE_1); + /*Talent*/lvl >= 68 ? InitSpellMap(ROAR_OF_RECOVERY_1, true) : RemoveSpell(ROAR_OF_RECOVERY_1); + /*Talent*/lvl >= 68 ? InitSpellMap(BULLHEADED_1, true) : RemoveSpell(BULLHEADED_1); + //talents ferocity + ///*Talent*/lvl >= 68 ? InitSpellMap(HEART_OF_THE_PHOENIX_1, true) : RemoveSpell(HEART_OF_THE_PHOENIX_1); + /*Talent*/lvl >= 68 ? InitSpellMap(RABID_1, true) : RemoveSpell(RABID_1); + /*Talent*/lvl >= 68 ? InitSpellMap(LICK_YOUR_WOUNDS_1, true) : RemoveSpell(LICK_YOUR_WOUNDS_1); + /*Talent*/lvl >= 68 ? InitSpellMap(CALL_OF_THE_WILD_1, true) : RemoveSpell(CALL_OF_THE_WILD_1); + //talents tenacity + /*Talent*/lvl >= 44 ? InitSpellMap(THUNDERSTOMP_1, true) : RemoveSpell(THUNDERSTOMP_1); + /*Talent*/lvl >= 68 ? InitSpellMap(LAST_STAND_1, true) : RemoveSpell(LAST_STAND_1); + /*Talent*/lvl >= 68 ? InitSpellMap(TAUNT_1, true) : RemoveSpell(TAUNT_1); + /*Talent*/lvl >= 68 ? InitSpellMap(ROAR_OF_SACRIFICE_1, true) : RemoveSpell(ROAR_OF_SACRIFICE_1); + /*Talent*/lvl >= 68 ? InitSpellMap(INTERVENE_1, true) : RemoveSpell(INTERVENE_1); + //pet-specific + InitSpellMap(SONIC_BLAST_1); + InitSpellMap(SNATCH_1); + InitSpellMap(FROSTSTORM_BREATH_1); + InitSpellMap(FIRE_BREATH_1); + InitSpellMap(NETHER_SHOCK_1); + InitSpellMap(RAVAGE_1); + InitSpellMap(POISON_SPIT_1); + InitSpellMap(VENOM_WEB_SPRAY_1); + InitSpellMap(WEB_1); + InitSpellMap(SPORE_CLOUD_1); + InitSpellMap(LIGHTNING_BREATH_1); + InitSpellMap(DEMORALIZING_SCREECH_1); + InitSpellMap(PROWL_1); + InitSpellMap(LAVA_BREATH_1); + InitSpellMap(MONSTROUS_BITE_1); + InitSpellMap(TENDON_RIP_1); + InitSpellMap(SERENITY_DUST_1); + InitSpellMap(SAVAGE_REND_1); + InitSpellMap(SPIRIT_STRIKE_1); + InitSpellMap(DUST_CLOUD_1); + InitSpellMap(STING_1); + InitSpellMap(FURIOUS_HOWL_1); + InitSpellMap(SWIPE_1); + InitSpellMap(GORE_1); + InitSpellMap(PIN_1); + InitSpellMap(PUMMEL_1); + InitSpellMap(STAMPEDE_1); + InitSpellMap(SCORPID_POISON_1); + InitSpellMap(SHELL_SHIELD_1); + InitSpellMap(WARP_1); + InitSpellMap(ACID_SPIT_1); + + /*Talent*/lvl >= 30 && isBeas ? InitSpellMap(SPIRIT_BOND_PET, true) : RemoveSpell(SPIRIT_BOND_PET); + /*Talent*/lvl >= 55 && isBeas ? InitSpellMap(KINDRED_SPIRITS_PET, true) : RemoveSpell(KINDRED_SPIRITS_PET); + /*Talent*/lvl >= 30 && isBeas ? InitSpellMap(INTIMIDATION_1, true) : RemoveSpell(INTIMIDATION_1); + /*Talent*/lvl >= 40 && isBeas ? InitSpellMap(BESTIAL_WRATH_1, true) : RemoveSpell(BESTIAL_WRATH_1); + /*Talent*/lvl >= 50 && isBeas ? InitSpellMap(BEAST_WITHIN_1, true) : RemoveSpell(BEAST_WITHIN_1); + } + + void ApplyPetPassives() const override + { + uint8 lvl = me->GetLevel(); + + RefreshAura(AVOIDANCE, lvl >= 60); + //ignore pet type + RefreshAura(COBRA_REFLEXES, lvl >= 20); + RefreshAura(BLOOD_OF_THE_RHINO, lvl >= 32); + RefreshAura(OWLS_FOCUS, lvl >= 32); + RefreshAura(CULLING_THE_HERD, lvl >= 32); + RefreshAura(GRACE_OF_THE_MANTIS, lvl >= 44); + RefreshAura(CORNERED, lvl >= 44); + RefreshAura(FEEDING_FRENZY, lvl >= 44); + RefreshAura(SILVERBACK, lvl >= 80); + + RefreshAura(HASTE_DODGE_PASSIVE, lvl >= 45); + } + + private: + bool IsPetTypeSpell(uint32 basespell) const + { + switch (basespell) + { + case BITE_1: //lvl 1 + switch (myType) + { + case BOT_PET_BAT: + case BOT_PET_BOAR: + case BOT_PET_CARRIONBIRD: + case BOT_PET_CROCOLISK: + case BOT_PET_DRAGONHAWK: + case BOT_PET_HYENA: + case BOT_PET_NETHERRAY: + case BOT_PET_RAVAGER: + case BOT_PET_SERPENT: + case BOT_PET_WOLF: + + case BOT_PET_CHIMAERA: + case BOT_PET_COREHOUND: + case BOT_PET_DEVILSAUR: + case BOT_PET_WORM: + return true; + default: + return false; + } + case CLAW_1: //lvl 1 + switch (myType) + { + case BOT_PET_BEAR: + case BOT_PET_BIRDOFPREY: + case BOT_PET_CAT: + case BOT_PET_CRAB: + case BOT_PET_RAPTOR: + case BOT_PET_SCORPID: + case BOT_PET_TALLSTRIDER: + + case BOT_PET_SILITHID: + case BOT_PET_SPIRITBEAST: + return true; + default: + return false; + } + case SMACK_1: //lvl 1 + switch (myType) + { + case BOT_PET_GORILLA: + case BOT_PET_SPOREBAT: + case BOT_PET_TEROMOTH: + case BOT_PET_WASP: + + case BOT_PET_RHINO: + return true; + default: + return false; + } + case SWOOP_1: //fliers ferocity lvl 44 + switch (myType) + { + case BOT_PET_CARRIONBIRD: + case BOT_PET_WASP: + case BOT_PET_TEROMOTH: + return me->GetLevel() >= 44; + default: + return false; + } + case CHARGE_1: //non-fliers tenacity/ferocity lvl 20/44 + switch (myType) + { + case BOT_PET_BEAR: + case BOT_PET_BOAR: + case BOT_PET_CAT: + case BOT_PET_CRAB: + case BOT_PET_CROCOLISK: + case BOT_PET_GORILLA: + case BOT_PET_HYENA: + case BOT_PET_RAPTOR: + case BOT_PET_SCORPID: + case BOT_PET_TALLSTRIDER: + case BOT_PET_TURTLE: + case BOT_PET_WARPSTALKER: + case BOT_PET_WOLF: + + case BOT_PET_COREHOUND: + case BOT_PET_DEVILSAUR: + case BOT_PET_RHINO: + case BOT_PET_SPIRITBEAST: + case BOT_PET_WORM: + return me->GetLevel() >= (IsPetCategory(PET_CATEGORY_FEROCITY) ? 44 : 20); + default: + return false; + } + case DASH_1: //non-fliers ferocity/cunning lvl 20/44 + switch (myType) + { + case BOT_PET_CAT: + case BOT_PET_HYENA: + case BOT_PET_RAPTOR: + case BOT_PET_RAVAGER: + case BOT_PET_SERPENT: + case BOT_PET_SPIDER: + case BOT_PET_TALLSTRIDER: + case BOT_PET_WARPSTALKER: + case BOT_PET_WOLF: + + case BOT_PET_COREHOUND: + case BOT_PET_DEVILSAUR: + case BOT_PET_SILITHID: + case BOT_PET_SPIRITBEAST: + return me->GetLevel() >= (IsPetCategory(PET_CATEGORY_CUNNING) ? 44 : 20); + default: + return false; + } + case DIVE_1: //fliers lvl 20 + switch (myType) + { + case BOT_PET_BAT: + case BOT_PET_BIRDOFPREY: + case BOT_PET_CARRIONBIRD: + case BOT_PET_DRAGONHAWK: + case BOT_PET_TEROMOTH: + case BOT_PET_NETHERRAY: + case BOT_PET_SPOREBAT: + case BOT_PET_WASP: + case BOT_PET_WINDSERPENT: + + case BOT_PET_CHIMAERA: + return me->GetLevel() >= 20; + default: + return false; + } + default: + return true; + } + } + + bool IsPetCategory(uint8 category) const + { + switch (myType) + { + case BOT_PET_SPIDER: + case BOT_PET_SERPENT: + case BOT_PET_BIRDOFPREY: + case BOT_PET_BAT: + case BOT_PET_WINDSERPENT: + case BOT_PET_RAVAGER: + case BOT_PET_DRAGONHAWK: + case BOT_PET_NETHERRAY: + case BOT_PET_SPOREBAT: + + case BOT_PET_SILITHID: + case BOT_PET_CHIMAERA: + return category == PET_CATEGORY_CUNNING; + case BOT_PET_CARRIONBIRD: + case BOT_PET_RAPTOR: + case BOT_PET_WOLF: + case BOT_PET_TALLSTRIDER: + case BOT_PET_CAT: + case BOT_PET_HYENA: + case BOT_PET_WASP: + case BOT_PET_TEROMOTH: + + case BOT_PET_SPIRITBEAST: + case BOT_PET_COREHOUND: + case BOT_PET_DEVILSAUR: + return category == PET_CATEGORY_FEROCITY; + case BOT_PET_SCORPID: + case BOT_PET_TURTLE: + case BOT_PET_GORILLA: + case BOT_PET_BEAR: + case BOT_PET_BOAR: + case BOT_PET_CRAB: + case BOT_PET_CROCOLISK: + case BOT_PET_WARPSTALKER: + + case BOT_PET_RHINO: + case BOT_PET_WORM: + return category == PET_CATEGORY_TENACITY; + default: + return false; + } + } + }; +}; + +void AddSC_hunter_bot_pets() +{ + new hunter_pet_bot(); +} diff --git a/src/server/game/AI/NpcBots/bpet_mage.cpp b/src/server/game/AI/NpcBots/bpet_mage.cpp new file mode 100644 index 000000000..6cd40a8a1 --- /dev/null +++ b/src/server/game/AI/NpcBots/bpet_mage.cpp @@ -0,0 +1,177 @@ +#include "bot_ai.h" +#include "bpet_ai.h" +#include "ObjectAccessor.h" +#include "ScriptMgr.h" +#include "Spell.h" +#include "SpellInfo.h" +#include "TemporarySummon.h" +/* +Mage NpcBot Pets (by Trickerer onlysuffering@gmail.com) +Complete - 100% +TODO: +*/ + +enum MagePetBaseSpells +{ + WATERBOLT_1 = 31707, + FREEZE_1 = 33395 +}; + +enum MagePetSpecial +{ + ELEMENTAL_DURATION = 45000 +}; + +class mage_pet_bot : public CreatureScript +{ +public: + mage_pet_bot() : CreatureScript("mage_pet_bot") { } + + CreatureAI* GetAI(Creature* creature) const override + { + return new water_elemental_botpetAI(creature); + } + + struct water_elemental_botpetAI : public bot_pet_ai + { + water_elemental_botpetAI(Creature* creature) : bot_pet_ai(creature) { } + + void JustEnteredCombat(Unit* u) override { bot_pet_ai::JustEnteredCombat(u); } + void KilledUnit(Unit* u) override { bot_pet_ai::KilledUnit(u); } + void EnterEvadeMode(EvadeReason why = EVADE_REASON_OTHER) override { bot_pet_ai::EnterEvadeMode(why); } + void MoveInLineOfSight(Unit* u) override { bot_pet_ai::MoveInLineOfSight(u); } + void JustDied(Unit* u) override { bot_pet_ai::JustDied(u); } + void DoNonCombatActions(uint32 /*diff*/) { } + + void StartAttack(Unit* u, bool force = false) + { + if (!bot_pet_ai::StartAttack(u, force)) + return; + GetInPosition(force, u); + } + + void DoPetActions(uint32 diff) + { + if (!petOwner->GetBotAI()->HasRole(BOT_ROLE_DPS)) + return; + + if (!IsSpellReady(FREEZE_1, diff, false) || Rand() > 40 || !IsCasting(petOwner)) + return; + + Spell const* spell = petOwner->GetCurrentSpell(CURRENT_GENERIC_SPELL); + if (!spell || !spell->GetSpellInfo()->HasEffect(SPELL_EFFECT_SCHOOL_DAMAGE) || + !(spell->GetTimer() < spell->GetCastTime() / 2)) + return; + + Unit* target = ObjectAccessor::GetUnit(*me, spell->m_targets.GetObjectTargetGUID()); + if (!target || target->IsFrozen() || target->GetDistance(me) - target->GetCombatReach() > 25.f || + !me->IsValidAttackTarget(target)) + return; + + me->InterruptNonMeleeSpells(false); + me->CastSpell(target, GetSpell(FREEZE_1), false); + return; + } + + void UpdateAI(uint32 diff) override + { + if ((liveTimer += diff) >= ELEMENTAL_DURATION * (IAmFree() ? 80u : 1u)) + { + canUpdate = false; + me->ToTempSummon()->UnSummon(1); + return; + } + + if (!GlobalUpdate(diff)) + return; + + if (!me->IsInCombat()) + DoNonCombatActions(diff); + + DoPetActions(diff); + + if (!CheckAttackTarget()) + return; + + if (IsCasting()) + return; + + DoPetAttack(diff); + } + + void DoPetAttack(uint32 diff) + { + StartAttack(opponent, IsPetMelee()); + + if (!petOwner->GetBotAI()->HasRole(BOT_ROLE_DPS)) + return; + + if (IsSpellReady(WATERBOLT_1, diff) && me->GetDistance(opponent) < 45) + { + me->CastSpell(opponent, GetSpell(WATERBOLT_1), false); + return; + } + } + + void OnPetClassSpellGo(SpellInfo const* /*spellInfo*/) override + { + } + + void SpellHit(WorldObject* wcaster, SpellInfo const* spell) override + { + Unit* caster = wcaster->ToUnit(); + if (!caster) + return; + + OnSpellHit(caster, spell); + } + + void SpellHitTarget(WorldObject* /*wtarget*/, SpellInfo const* /*spell*/) override + { + } + + void DamageDealt(Unit* victim, uint32& damage, DamageEffectType damageType) override + { + bot_pet_ai::DamageDealt(victim, damage, damageType); + } + + void DamageTaken(Unit* u, uint32& /*damage*/, DamageEffectType /*damageType*/, SpellInfo const* /*spellInfo*/) override + { + if (!u) + return; + if (!u->IsInCombat() && !me->IsInCombat()) + return; + OnOwnerDamagedBy(u); + } + + void OwnerAttackedBy(Unit* u) override + { + if (!u) + return; + OnOwnerDamagedBy(u); + } + + void Reset() override + { + liveTimer = 0; + } + + void InitPetSpells() override + { + InitSpellMap(WATERBOLT_1); + InitSpellMap(FREEZE_1); + } + + void ApplyPetPassives() const override + { + } + + private: + uint32 liveTimer; + }; +}; + +void AddSC_mage_bot_pets() +{ + new mage_pet_bot(); +} diff --git a/src/server/game/AI/NpcBots/bpet_necromancer.cpp b/src/server/game/AI/NpcBots/bpet_necromancer.cpp new file mode 100644 index 000000000..c4f53ae86 --- /dev/null +++ b/src/server/game/AI/NpcBots/bpet_necromancer.cpp @@ -0,0 +1,191 @@ +#include "bot_ai.h" +#include "bpet_ai.h" +#include "Player.h" +#include "ScriptMgr.h" +/* +Necromancer NpcBot Pets (by Trickerer onlysuffering@gmail.com) +Notes: +Extra abilities. For the sake of defending the owner added Taunt. For self defense added Blocking (block value unchanged) +Both abilities are one-time use +Complete - 100% +TODO: +*/ + +enum NecromancerPetBaseSpells +{ + BLOCKING_1 = 3248, + TAUNT_1 = 37548 +}; +enum NecromancerPetPassives +{ +}; +enum NecromancerPetSpecial +{ + THREAT_BASE = 5, + MINION_DURATION = 65000 +}; + +class necromancer_pet_bot : public CreatureScript +{ +public: + necromancer_pet_bot() : CreatureScript("necromancer_pet_bot") { } + + CreatureAI* GetAI(Creature* creature) const override + { + return new necromancer_botpetAI(creature); + } + + struct necromancer_botpetAI : public bot_pet_ai + { + necromancer_botpetAI(Creature* creature) : bot_pet_ai(creature) { } + + void JustEnteredCombat(Unit* u) override { bot_pet_ai::JustEnteredCombat(u); } + void KilledUnit(Unit* u) override { bot_pet_ai::KilledUnit(u); } + void EnterEvadeMode(EvadeReason why = EVADE_REASON_OTHER) override { bot_pet_ai::EnterEvadeMode(why); } + void MoveInLineOfSight(Unit* u) override { bot_pet_ai::MoveInLineOfSight(u); } + void JustDied(Unit* u) override { bot_pet_ai::JustDied(u); } + void DoNonCombatActions(uint32 /*diff*/) { } + + void StartAttack(Unit* u, bool force = false) + { + if (!bot_pet_ai::StartAttack(u, force)) + return; + GetInPosition(force, u); + } + + void UpdateAI(uint32 diff) override + { + if ((liveTimer += diff) >= MINION_DURATION * (IAmFree() ? 5u : 1u)) + { + canUpdate = false; + me->setDeathState(JUST_DIED); + return; + } + + if (!GlobalUpdate(diff)) + return; + + if (!me->IsInCombat()) + DoNonCombatActions(diff); + + if (!CheckAttackTarget()) + return; + + if (IsCasting()) + return; + + DoPetAttack(diff); + } + + void DoPetAttack(uint32 diff) + { + StartAttack(opponent, IsPetMelee()); + + if (IsSpellReady(TAUNT_1, diff, false) && Rand() < 50 && + ((opponent->GetVictim() == petOwner && !IsTank(petOwner)) || + (opponent->GetVictim() == petOwner->GetBotOwner() && !IsTank(petOwner->GetBotOwner()))) && + !opponent->HasAuraType(SPELL_AURA_MOD_TAUNT) && me->IsWithinMeleeRange(opponent)) + { + me->CastSpell(opponent, GetSpell(TAUNT_1), false); + SetSpellCooldown(TAUNT_1, std::numeric_limits::max()); + return; + } + + if (IsSpellReady(BLOCKING_1, diff) && !me->getAttackers().empty() && Rand() < 25) + { + me->CastSpell(me, GetSpell(BLOCKING_1), true); + SetSpellCooldown(BLOCKING_1, std::numeric_limits::max()); + return; + } + } + + void OnPetClassSpellGo(SpellInfo const* /*spellInfo*/) override + { + } + + void SpellHit(WorldObject* wcaster, SpellInfo const* spell) override + { + Unit* caster = wcaster->ToUnit(); + if (!caster) + return; + + OnSpellHit(caster, spell); + } + + void SpellHitTarget(WorldObject* /*wtarget*/, SpellInfo const* /*spell*/) override + { + } + + void DamageDealt(Unit* victim, uint32& damage, DamageEffectType damageType) override + { + bot_pet_ai::DamageDealt(victim, damage, damageType); + } + + void DamageTaken(Unit* u, uint32& /*damage*/, DamageEffectType /*damageType*/, SpellInfo const* /*spellInfo*/) override + { + if (!u) + return; + if (!u->IsInCombat() && !me->IsInCombat()) + return; + OnOwnerDamagedBy(u); + } + + void OwnerAttackedBy(Unit* u) override + { + if (!u) + return; + OnOwnerDamagedBy(u); + } + + uint32 GetData(uint32 data) const override + { + switch (data) + { + case BOTPETAI_MISC_DURATION: + return liveTimer; + case BOTPETAI_MISC_MAXLEVEL: + return maxlevel; + default: + return bot_pet_ai::GetData(data); + } + } + + void SetData(uint32 data, uint32 value) override + { + switch (data) + { + case BOTPETAI_MISC_MAXLEVEL: + maxlevel = uint8(value); + SetPetStats(true); + break; + default: + break; + } + } + + void Reset() override + { + liveTimer = 0; + maxlevel = 1; + } + + void InitPetSpells() override + { + InitSpellMap(TAUNT_1, true, false); + InitSpellMap(BLOCKING_1, true, false); + } + + void ApplyPetPassives() const override + { + } + + private: + uint32 liveTimer; + uint8 maxlevel; + }; +}; + +void AddSC_necromancer_bot_pets() +{ + new necromancer_pet_bot(); +} diff --git a/src/server/game/AI/NpcBots/bpet_priest.cpp b/src/server/game/AI/NpcBots/bpet_priest.cpp new file mode 100644 index 000000000..5963320f7 --- /dev/null +++ b/src/server/game/AI/NpcBots/bpet_priest.cpp @@ -0,0 +1,172 @@ +#include "bot_ai.h" +#include "bpet_ai.h" +#include "ScriptMgr.h" +#include "TemporarySummon.h" +/* +Priest NpcBot Pets (by Trickerer onlysuffering@gmail.com) +Complete - 100% +TODO: +*/ + +enum PriestPetBaseSpells +{ + SHADOWCRAWL_1 = 63619 +}; + +enum PriestPetPassives +{ + MANA_LEECH = 28305, + AVOIDANCE = 63623 +}; + +enum PriestPetSpecial +{ + GLYPH_SHADOWFIEND_PROC = 58227, + + SHADOWFIEND_DURATION = 15000 +}; + +class priest_pet_bot : public CreatureScript +{ +public: + priest_pet_bot() : CreatureScript("priest_pet_bot") { } + + CreatureAI* GetAI(Creature* creature) const override + { + return new priest_botpetAI(creature); + } + + struct priest_botpetAI : public bot_pet_ai + { + priest_botpetAI(Creature* creature) : bot_pet_ai(creature) { } + + void JustEnteredCombat(Unit* u) override { bot_pet_ai::JustEnteredCombat(u); } + void KilledUnit(Unit* u) override { bot_pet_ai::KilledUnit(u); } + void EnterEvadeMode(EvadeReason why = EVADE_REASON_OTHER) override { bot_pet_ai::EnterEvadeMode(why); } + void MoveInLineOfSight(Unit* u) override { bot_pet_ai::MoveInLineOfSight(u); } + void JustDied(Unit* u) override { bot_pet_ai::JustDied(u); } + void DoNonCombatActions(uint32 /*diff*/) { } + + void StartAttack(Unit* u, bool force = false) + { + if (!bot_pet_ai::StartAttack(u, force)) + return; + GetInPosition(force, u); + } + + void DoPetActions(uint32 /*diff*/) + { + } + + void UpdateAI(uint32 diff) override + { + if ((liveTimer += diff) >= SHADOWFIEND_DURATION) + { + canUpdate = false; + me->ToTempSummon()->UnSummon(1); + return; + } + + if (!GlobalUpdate(diff)) + return; + + if (!me->IsInCombat()) + DoNonCombatActions(diff); + + DoPetActions(diff); + //CheckDrainMana(diff); + + if (!CheckAttackTarget()) + return; + + if (IsCasting()) + return; + + DoPetAttack(diff); + } + + void DoPetAttack(uint32 diff) + { + StartAttack(opponent, IsPetMelee()); + + float dist = me->GetDistance(opponent); + bool canDPS = petOwner->GetBotAI()->HasRole(BOT_ROLE_DPS); + + if (IsSpellReady(SHADOWCRAWL_1, diff) && canDPS && dist < 30) + { + me->CastSpell(opponent, GetSpell(SHADOWCRAWL_1), false); + SetSpellCooldown(SHADOWCRAWL_1, 6000); + return; + } + } + + void OnPetClassSpellGo(SpellInfo const* /*spellInfo*/) override + { + } + + void SpellHit(WorldObject* wcaster, SpellInfo const* spell) override + { + Unit* caster = wcaster->ToUnit(); + if (!caster) + return; + + OnSpellHit(caster, spell); + } + + void SpellHitTarget(WorldObject* /*wtarget*/, SpellInfo const* /*spell*/) override + { + } + + void DamageDealt(Unit* victim, uint32& damage, DamageEffectType damageType) override + { + //Handled by spell scripts + //if (damage && victim && damageType == DIRECT_DAMAGE) + // victim->CastSpell(petOwner, MANA_LEECH_PROC, true); + + bot_pet_ai::DamageDealt(victim, damage, damageType); + } + + void DamageTaken(Unit* u, uint32& damage, DamageEffectType /*damageType*/, SpellInfo const* /*spellInfo*/) override + { + if (damage >= me->GetHealth()) + petOwner->CastSpell(petOwner, GLYPH_SHADOWFIEND_PROC, true); + + if (!u) + return; + if (!u->IsInCombat() && !me->IsInCombat()) + return; + OnOwnerDamagedBy(u); + } + + void OwnerAttackedBy(Unit* u) override + { + if (!u) + return; + OnOwnerDamagedBy(u); + } + + void Reset() override + { + liveTimer = 0; + } + + void InitPetSpells() override + { + InitSpellMap(SHADOWCRAWL_1); + } + + void ApplyPetPassives() const override + { + RefreshAura(MANA_LEECH); + RefreshAura(AVOIDANCE); + } + + private: + uint32 liveTimer; + }; +}; + +void AddSC_priest_bot_pets() +{ + new priest_pet_bot(); +} diff --git a/src/server/game/AI/NpcBots/bpet_sea_witch.cpp b/src/server/game/AI/NpcBots/bpet_sea_witch.cpp new file mode 100644 index 000000000..c20d79ab1 --- /dev/null +++ b/src/server/game/AI/NpcBots/bpet_sea_witch.cpp @@ -0,0 +1,251 @@ +#include "bot_ai.h" +#include "botspell.h" +#include "bpet_ai.h" +#include "Containers.h" +#include "MotionMaster.h" +#include "ScriptMgr.h" +#include "SpellAuras.h" +#include "SpellAuraEffects.h" +#include "TemporarySummon.h" +/* +Sea Witch NpcBot Pets (by Trickerer onlysuffering@gmail.com) +Complete - 100% +TODO: +*/ + +enum SeaWitchPetBaseSpells +{ + ENVELOP_1 = SPELL_TORNADO_EFFECT, + LIGHTNING_1 = SPELL_TORNADO_EFFECT2, +}; + +enum SeaWitchPetSpecial +{ + TORNADO_DURATION = 40000, + TORNADO_MOVE_RESET_TIMER = 1500, + TORNADO_GROWTH_TIMER = 2500, + TORNADO_DISSIPATE_TIMER = 5000, + + PERIODIC_LIGHTNING_VISUAL = 45869, + CAMERA_SHAKE_VISUAL = 12816, + TARGET_LIGHTNING_VISUAL = 39381, + TARGET_LIGHTNING_VISUAL2 = 45935, + SPELL_GROWTH = 55948,//+10% size, +10% damage + SPELL_SLOW_AURA = SPELL_TORNADO_EFFECT3 +}; + +class sea_witch_pet_bot : public CreatureScript +{ +public: + sea_witch_pet_bot() : CreatureScript("sea_witch_pet_bot") { } + + CreatureAI* GetAI(Creature* creature) const override + { + return new tornado_botpetAI(creature); + } + + struct tornado_botpetAI : public bot_pet_ai + { + tornado_botpetAI(Creature* creature) : bot_pet_ai(creature) { } + + void JustEnteredCombat(Unit* u) override { bot_pet_ai::JustEnteredCombat(u); } + void KilledUnit(Unit* u) override { bot_pet_ai::KilledUnit(u); } + void EnterEvadeMode(EvadeReason why = EVADE_REASON_OTHER) override { bot_pet_ai::EnterEvadeMode(why); } + void MoveInLineOfSight(Unit* u) override { bot_pet_ai::MoveInLineOfSight(u); } + void JustDied(Unit* u) override { bot_pet_ai::JustDied(u); } + + void DoPetActions(uint32 diff) + { + //Envelop random target: every 3 sec + if (IsSpellReady(ENVELOP_1, diff, false)) + { + std::list targets; + petOwner->GetBotAI()->HelpGetNearbyTargetsList(targets, 10.f, 1, me); + if (targets.size() > 2) + Trinity::Containers::RandomResize(targets, 2); + for (Unit* u : targets) + me->CastSpell(u, GetSpell(ENVELOP_1), true); + SetSpellCooldown(ENVELOP_1, 3000); + } + + if (IsSpellReady(LIGHTNING_1, diff, false)) + { + std::list targets; + petOwner->GetBotAI()->HelpGetNearbyTargetsList(targets, 15.f, 0, me); + if (!targets.empty()) + me->CastSpell(me, CAMERA_SHAKE_VISUAL, true); + for (Unit* u : targets) + { + me->CastSpell(u, GetSpell(LIGHTNING_1), true); + u->CastSpell(u, TARGET_LIGHTNING_VISUAL, true); + u->CastSpell(u, TARGET_LIGHTNING_VISUAL2, true); + } + SetSpellCooldown(LIGHTNING_1, 4500); + } + } + + void UpdateAI(uint32 diff) override + { + if (((liveTimer += diff) >= TORNADO_DURATION) || !petOwner->GetBotAI()->HasRole(BOT_ROLE_DPS)) + { + canUpdate = false; + me->ToTempSummon()->UnSummon(1); + return; + } + else if ((IsIndoors() && !me->IsOutdoors()) && (isIndoorsTimer += diff) >= TORNADO_DISSIPATE_TIMER) + { + canUpdate = false; + me->SetObjectScale(me->GetNativeObjectScale() / 2.f); + me->ToTempSummon()->UnSummon(2000); + return; + } + + if ((growthTimer += diff) > TORNADO_GROWTH_TIMER) + { + growthTimer %= TORNADO_GROWTH_TIMER; + me->CastSpell(me, SPELL_GROWTH, true); + //me->SetObjectScale(me->GetObjectScale() * 1.1f); + } + + if (!GlobalUpdate(diff)) + return; + + DoPetActions(diff); + + if (IsCasting()) + return; + + if (!CheckAttackTarget()) + return; + + DoPetAttack(diff); + } + + void DoPetAttack(uint32 diff) + { + if ((moveResetTimer += diff) > TORNADO_MOVE_RESET_TIMER || opponent->GetGUID() != me->GetTarget()) + { + moveResetTimer %= TORNADO_MOVE_RESET_TIMER; + SetBotCommandState(BOT_COMMAND_ATTACK); + me->SetTarget(opponent->GetGUID()); + Position pos = opponent->GetNearPosition(frand(3.f, 5.f + opponent->GetCombatReach()), opponent->GetAbsoluteAngle(petOwner) + frand(float(-M_PI) / 2.f, float(M_PI) / 2.f)); + me->GetMotionMaster()->MovePoint(me->GetMapId(), pos.GetPositionX(), pos.GetPositionY(), opponent->GetPositionZ(), false); + //me->GetMotionMaster()->MoveChase(opponent, frand(3.f, 10.f), opponent->GetAbsoluteAngle(petOwner) + frand(-M_PI / 2, M_PI / 2)); + } + } + + void ApplyBotPetSpellRadiusMods(SpellInfo const* spellInfo, float& radius) const override + { + uint32 baseId = spellInfo->GetFirstRankSpell()->Id; + //SpellSchool school = GetFirstSchoolInMask(spellInfo->GetSchoolMask()); + //uint8 lvl = me->GetLevel(); + float flatbonus = 0.0f; + float pctbonus = 0.0f; + + ////pct mods + ////Increased Area (AhnQ set bonus?) 23549 + //if (lvl >= 60 && (spellInfo->SpellFamilyFlags[0] & 0x1084)) + // pctbonus += 0.25f; + + //flat mods + //Slow Aura growth + if (baseId == SPELL_SLOW_AURA) + flatbonus += me->GetCombatReach(); + + radius = radius * (1.0f + pctbonus) + flatbonus; + } + + void OnPetClassSpellGo(SpellInfo const* /*spellInfo*/) override {} + + void SpellHit(WorldObject* wcaster, SpellInfo const* spell) override + { + Unit* caster = wcaster->ToUnit(); + if (!caster) + return; + + OnSpellHit(caster, spell); + } + + void SpellHitTarget(WorldObject* wtarget, SpellInfo const* spell) override + { + Unit* target = wtarget->ToUnit(); + if (!target) + return; + + if (target == me) + return; + + uint32 baseId = spell->GetFirstRankSpell()->Id; + //uint8 lvl = me->GetLevel(); + + if (baseId == ENVELOP_1) + { + if (target->IsControlledByPlayer()) + { + if (Aura* enve = target->GetAura(GetSpell(baseId), me->GetGUID())) + { + int32 dur = std::max(enve->GetDuration() - 6000, 0); + enve->SetDuration(dur); + enve->SetMaxDuration(dur); + } + } + } + } + + void DamageDealt(Unit* victim, uint32& damage, DamageEffectType damageType) override + { + bot_pet_ai::DamageDealt(victim, damage, damageType); + } + + void DamageTaken(Unit* u, uint32& /*damage*/, DamageEffectType /*damageType*/, SpellInfo const* /*spellInfo*/) override + { + if (!u) + return; + if (!u->IsInCombat() && !me->IsInCombat()) + return; + OnOwnerDamagedBy(u); + } + + void OwnerAttackedBy(Unit* u) override + { + if (!u) + return; + OnOwnerDamagedBy(u); + } + + void CheckAttackState() override + { + } + + void Reset() override + { + liveTimer = 0; + moveResetTimer = 0; + growthTimer = 0; + isIndoorsTimer = 0; + } + + void InitPetSpells() override + { + InitSpellMap(ENVELOP_1, true, false); + InitSpellMap(LIGHTNING_1, true, false); + } + + void ApplyPetPassives() const override + { + RefreshAura(PERIODIC_LIGHTNING_VISUAL); + RefreshAura(SPELL_SLOW_AURA); + } + + private: + uint32 liveTimer; + uint32 moveResetTimer; + uint32 growthTimer; + uint32 isIndoorsTimer; + }; +}; + +void AddSC_sea_witch_bot_pets() +{ + new sea_witch_pet_bot(); +} diff --git a/src/server/game/AI/NpcBots/bpet_shaman.cpp b/src/server/game/AI/NpcBots/bpet_shaman.cpp new file mode 100644 index 000000000..4d45cbd71 --- /dev/null +++ b/src/server/game/AI/NpcBots/bpet_shaman.cpp @@ -0,0 +1,191 @@ +#include "bot_ai.h" +#include "bpet_ai.h" +#include "ScriptMgr.h" +#include "TemporarySummon.h" +/* +Shaman NpcBot Pets (by Trickerer onlysuffering@gmail.com) +Complete - 100% +TODO: +*/ + +enum ShamanPetBaseSpells +{ + BASH_1 = 58861, //r5 cd45 + LEAP_1 = 58867, //r5-30 cd20 + TWIN_HOWL_1 = 58857, //r10 cd15 + SPIRIT_WALK_1 = 58875 //r25 cd32 +}; + +enum ShamanPetPassives +{ + SPIRIT_HUNT = 58877 +}; + +enum ShamanPetSpecial +{ + SPIRITWOLF_DURATION = 45000 +}; + +class shaman_pet_bot : public CreatureScript +{ +public: + shaman_pet_bot() : CreatureScript("shaman_pet_bot") { } + + CreatureAI* GetAI(Creature* creature) const override + { + return new shaman_botpetAI(creature); + } + + struct shaman_botpetAI : public bot_pet_ai + { + shaman_botpetAI(Creature* creature) : bot_pet_ai(creature) { } + + void JustEnteredCombat(Unit* u) override { bot_pet_ai::JustEnteredCombat(u); } + void KilledUnit(Unit* u) override { bot_pet_ai::KilledUnit(u); } + void EnterEvadeMode(EvadeReason why = EVADE_REASON_OTHER) override { bot_pet_ai::EnterEvadeMode(why); } + void MoveInLineOfSight(Unit* u) override { bot_pet_ai::MoveInLineOfSight(u); } + void JustDied(Unit* u) override { bot_pet_ai::JustDied(u); } + void DoNonCombatActions(uint32 /*diff*/) { } + + void StartAttack(Unit* u, bool force = false) + { + if (!bot_pet_ai::StartAttack(u, force)) + return; + GetInPosition(force, u); + } + + void DoPetActions(uint32 diff) + { + if (IsSpellReady(SPIRIT_WALK_1, diff) && (me->GetVictim() || petOwner->GetVictim()) && + !HasBotCommandState(BOT_COMMAND_STAY) && + me->GetDistance(petOwner) < 25) + { + me->CastSpell(me, GetSpell(SPIRIT_WALK_1), false); + return; + } + } + + void UpdateAI(uint32 diff) override + { + if ((liveTimer += diff) >= SPIRITWOLF_DURATION) + { + canUpdate = false; + me->ToTempSummon()->UnSummon(1); + return; + } + + if (!GlobalUpdate(diff)) + return; + + if (!me->IsInCombat()) + DoNonCombatActions(diff); + + DoPetActions(diff); + + if (!CheckAttackTarget()) + return; + + if (IsCasting()) + return; + + DoPetAttack(diff); + } + + void DoPetAttack(uint32 diff) + { + StartAttack(opponent, IsPetMelee()); + + float dist = me->GetDistance(opponent); + Unit const* u = opponent->GetVictim(); + //bool canDPS = petOwner->GetBotAI()->HasRole(BOT_ROLE_DPS); + + if (IsSpellReady(LEAP_1, diff) && + !HasBotCommandState(BOT_COMMAND_STAY) && + !(opponent->GetTypeId() == TYPEID_UNIT && opponent->ToCreature()->isWorldBoss()) && + dist > 5 && dist < 30) + { + me->CastSpell(opponent, GetSpell(LEAP_1), false); + return; + } + + if (IsSpellReady(BASH_1, diff) && !CCed(opponent) && + me->IsWithinMeleeRange(opponent)) + { + me->CastSpell(opponent, GetSpell(BASH_1), false); + return; + } + + if (IsSpellReady(TWIN_HOWL_1, diff, false) && u && u != me && me->IsWithinMeleeRange(opponent) && + !CCed(opponent) && !opponent->HasAuraType(SPELL_AURA_MOD_TAUNT) && + (!IsTank(u) || (GetHealthPCT(u) < 30 && GetHealthPCT(me) > 50)) && IsInBotParty(u)) + { + me->CastSpell(opponent, GetSpell(TWIN_HOWL_1), false); + return; + } + } + + void OnPetClassSpellGo(SpellInfo const* /*spellInfo*/) override + { + } + + void SpellHit(WorldObject* wcaster, SpellInfo const* spell) override + { + Unit* caster = wcaster->ToUnit(); + if (!caster) + return; + + OnSpellHit(caster, spell); + } + + void SpellHitTarget(WorldObject* /*wtarget*/, SpellInfo const* /*spell*/) override + { + } + + void DamageDealt(Unit* victim, uint32& damage, DamageEffectType damageType) override + { + bot_pet_ai::DamageDealt(victim, damage, damageType); + } + + void DamageTaken(Unit* u, uint32& /*damage*/, DamageEffectType /*damageType*/, SpellInfo const* /*spellInfo*/) override + { + if (!u) + return; + if (!u->IsInCombat() && !me->IsInCombat()) + return; + OnOwnerDamagedBy(u); + } + + void OwnerAttackedBy(Unit* u) override + { + if (!u) + return; + OnOwnerDamagedBy(u); + } + + void Reset() override + { + liveTimer = 0; + } + + void InitPetSpells() override + { + InitSpellMap(BASH_1); + InitSpellMap(LEAP_1); + InitSpellMap(TWIN_HOWL_1); + InitSpellMap(SPIRIT_WALK_1); + } + + void ApplyPetPassives() const override + { + RefreshAura(SPIRIT_HUNT); + } + + private: + uint32 liveTimer; + }; +}; + +void AddSC_shaman_bot_pets() +{ + new shaman_pet_bot(); +} diff --git a/src/server/game/AI/NpcBots/bpet_warlock.cpp b/src/server/game/AI/NpcBots/bpet_warlock.cpp new file mode 100644 index 000000000..84f96f4ed --- /dev/null +++ b/src/server/game/AI/NpcBots/bpet_warlock.cpp @@ -0,0 +1,408 @@ +#include "bot_ai.h" +#include "bpet_ai.h" +#include "botmgr.h" +#include "ScriptMgr.h" +#include "Spell.h" +#include "SpellAuraEffects.h" +#include "SpellMgr.h" +#include "Player.h" +/* +Warlock NpcBot Pets (by Trickerer onlysuffering@gmail.com) +Complete - 100% +TODO: +*/ + +enum WarlockPetBaseSpells +{ + //imp + FIREBOLT_1 = 3110,//1 + BLOOD_PACT_1 = 6307,//4 + PHASE_SHIFT_1 = 4511,//12 + FIRE_SHIELD_1 = 2947,//14 unused + //voidwalker + TORMENT_1 = 3716,//10 + SACRIFICE_1 = 7812,//16 + CONSUME_SHADOWS_1 = 17767,//18 + SUFFERING_1 = 17735,//24 + //succubus + LASH_OF_PAIN_1 = 7814,//20 + SOOTHING_KISS_1 = 6360,//22 + SEDUCTION_1 = 6358,//26 + LESSER_INVISIBILITY_1 = 7870,//32 + //felhunter + DEVOUR_MAGIC_1 = 19505,//30 + FEL_INTELLIGENCE_1 = 54424,//32 + SPELL_LOCK_1 = 19244,//36 + SHADOW_BITE_1 = 54049,//42 + //felguard + ANGUISH_1 = 33698,//50 + CLEAVE_1 = 30213,//50 + INTERCEPT_1 = 30151 //52 +}; + +enum WarlockPetPassives +{ + AVOIDANCE = 32233, + DEMONIC_FRENZY = 32850 +}; + +enum WarlockPetSpecial +{ + SOUL_LINK_PET = 25228//split effect lvl 20 req +}; + +class warlock_pet_bot : public CreatureScript +{ +public: + warlock_pet_bot() : CreatureScript("warlock_pet_bot") { } + + CreatureAI* GetAI(Creature* creature) const override + { + return new warlock_botpetAI(creature); + } + + struct warlock_botpetAI : public bot_pet_ai + { + warlock_botpetAI(Creature* creature) : bot_pet_ai(creature) { } + + void JustEnteredCombat(Unit* u) override { bot_pet_ai::JustEnteredCombat(u); } + void KilledUnit(Unit* u) override { bot_pet_ai::KilledUnit(u); } + void EnterEvadeMode(EvadeReason why = EVADE_REASON_OTHER) override { bot_pet_ai::EnterEvadeMode(why); } + void MoveInLineOfSight(Unit* u) override { bot_pet_ai::MoveInLineOfSight(u); } + void JustDied(Unit* u) override { bot_pet_ai::JustDied(u); } + void DoNonCombatActions(uint32 /*diff*/) { } + + void StartAttack(Unit* u, bool force = false) + { + if (!bot_pet_ai::StartAttack(u, force)) + return; + GetInPosition(force, u); + } + + void DoPetActions(uint32 diff) + { + if (GetSpell(SOUL_LINK_PET) && !petOwner->HasAuraTypeWithCaster(SPELL_AURA_SPLIT_DAMAGE_PCT, me->GetGUID())) + { + me->CastSpell(me, SOUL_LINK_PET, false); + return; + } + if (myType == BOT_PET_IMP) + { + //hacked - confilct with soul link due to ownerGuid mismatch + if (IsSpellReady(BLOOD_PACT_1, diff, false) && !IAmFree() && (!me->HasAuraType(SPELL_AURA_230) || + me->GetAuraEffectsByType(SPELL_AURA_230).front()->GetAmount() < sSpellMgr->GetSpellInfo(GetSpell(BLOOD_PACT_1))->_effects[0].CalcValue())) + { + me->CastSpell(me, GetSpell(BLOOD_PACT_1), false); + //CastSpellExtraArgs args(true); + //args.SetOriginalCaster(me->GetGUID()); + //petOwner->CastSpell(petOwner, GetSpell(BLOOD_PACT_1), args); + SetSpellCooldown(BLOOD_PACT_1, uint32(-1)); + } + + if (IsSpellReady(PHASE_SHIFT_1, diff, false) && !me->GetVictim() && + (!petOwner->GetBotAI()->HasRole(BOT_ROLE_DPS) || !me->IsInCombat()) && + !me->HasAuraType(SPELL_AURA_MOD_UNATTACKABLE)) + { + me->CastSpell(me, GetSpell(PHASE_SHIFT_1), false); + return; + } + } + else if (myType == BOT_PET_VOIDWALKER) + { + if (GetSpell(CONSUME_SHADOWS_1) && !me->IsInCombat() && !me->isMoving() && + me->GetDistance(me) < 10 && GetHealthPCT(me) < 80) + { + me->CastSpell(me, GetSpell(CONSUME_SHADOWS_1), false); + return; + } + + if (IsSpellReady(SACRIFICE_1, diff, false) && + (!petOwner->getAttackers().empty() || petOwner->HasAuraType(SPELL_AURA_PERIODIC_DAMAGE)) && + me->GetHealth() > me->GetCreateHealth() / 4) //hp cost 25% + { + me->CastSpell(me, GetSpell(SACRIFICE_1), false); + return; + } + } + else if (myType == BOT_PET_SUCCUBUS) + { + if (IsSpellReady(SOOTHING_KISS_1, diff, false) && + !me->getAttackers().empty() && me->GetDistance(*(me->getAttackers().begin())) < 10) + { + me->CastSpell(*(me->getAttackers().begin()), GetSpell(SOOTHING_KISS_1), false); + SetSpellCooldown(SOOTHING_KISS_1, 4000); + return; + } + + if (GetSpell(SEDUCTION_1) && Rand() < 20 && !IsCasting()) + { + Unit* target = petOwner->GetBotAI()->HelpFindStunTarget(30); + if (target && target->GetDiminishing(DIMINISHING_FEAR) <= DIMINISHING_LEVEL_2 + 1 * (target->IsNonMeleeSpellCast(false, false, true))) + { + me->CastSpell(target, GetSpell(SEDUCTION_1), false); + return; + } + } + + if (IsSpellReady(LESSER_INVISIBILITY_1, diff, false) && !me->GetVictim() && !me->IsInCombat() && + !me->HasAuraType(SPELL_AURA_MOD_INVISIBILITY)) + { + me->CastSpell(me, GetSpell(LESSER_INVISIBILITY_1), false); + return; + } + } + else if (myType == BOT_PET_FELHUNTER) + { + //hacked - confilct with soul link due to ownerGuid mismatch + if (IsSpellReady(FEL_INTELLIGENCE_1, diff, false) && !IAmFree() && + !petOwner->GetBotOwner()->GetBotMgr()->HasBotClass(BOT_CLASS_MAGE) && + !petOwner->GetBotOwner()->GetBotMgr()->HasBotClass(BOT_CLASS_PRIEST)) + { + me->CastSpell(me, GetSpell(FEL_INTELLIGENCE_1), false); + //CastSpellExtraArgs args(true); + //args.SetOriginalCaster(me->GetGUID()); + //petOwner->CastSpell(petOwner, GetSpell(FEL_INTELLIGENCE_1), args); + SetSpellCooldown(FEL_INTELLIGENCE_1, uint32(-1)); + } + + if (IsSpellReady(SPELL_LOCK_1, diff, false)) + { + if (Unit* target = petOwner->GetBotAI()->HelpFindCastingTarget(30, 0, SPELL_LOCK_1)) + me->CastSpell(target, GetSpell(SPELL_LOCK_1), false); + } + + CureGroup(GetSpell(DEVOUR_MAGIC_1), diff); + } + } + + void UpdateAI(uint32 diff) override + { + if (!GlobalUpdate(diff)) + return; + + if (Spell const* spell = me->GetCurrentSpell(CURRENT_GENERIC_SPELL)) + { + if (myType == BOT_PET_SUCCUBUS) + { + //Seduction interrupt + if (spell->GetSpellInfo()->GetFirstRankSpell()->Id == SEDUCTION_1) + { + Unit const* target = ObjectAccessor::GetUnit(*me, spell->m_targets.GetObjectTargetGUID()); + if (target && CCed(target)) + me->InterruptSpell(CURRENT_GENERIC_SPELL); + } + } + } + + if (!me->IsInCombat()) + DoNonCombatActions(diff); + + DoPetActions(diff); + //CheckDrainMana(diff); + + if (!CheckAttackTarget()) + return; + + if (IsCasting()) + return; + + DoPetAttack(diff); + } + + void DoPetAttack(uint32 diff) + { + StartAttack(opponent, IsPetMelee()); + + float dist = me->GetDistance(opponent); + Unit const* u = opponent->GetVictim(); + bool canDPS = petOwner->GetBotAI()->HasRole(BOT_ROLE_DPS); + + if (myType == BOT_PET_IMP) + { + if (GetSpell(FIREBOLT_1) && canDPS && dist < 30) + { + me->CastSpell(opponent, GetSpell(FIREBOLT_1), false); + return; + } + } + else if (myType == BOT_PET_VOIDWALKER) + { + if (IsSpellReady(TORMENT_1, diff, false) && u && u != me && me->IsWithinMeleeRange(opponent) && + opponent->CanHaveThreatList() && !CCed(opponent) && !opponent->HasAuraType(SPELL_AURA_MOD_TAUNT) && + (!IsTank(u) || (GetHealthPCT(u) < 30 && GetHealthPCT(me) > 20)) && IsInBotParty(u)) + { + me->CastSpell(opponent, GetSpell(TORMENT_1), false); + SetSpellCooldown(TORMENT_1, 5000); + return; + } + + if (IsSpellReady(SUFFERING_1, diff) && + !(u == me && opponent->GetTypeId() == TYPEID_UNIT && + (opponent->ToCreature()->IsDungeonBoss() || opponent->ToCreature()->isWorldBoss()))) + { + std::list targets; + petOwner->GetBotAI()->HelpGetNearbyTargetsList(targets, 9.f, 1, me); + uint8 count = 0; + for (std::list::const_iterator itr = targets.begin(); itr != targets.end(); ++itr) + { + if (!((*itr)->GetVictim() && IsTank((*itr)->GetVictim()))) + if (++count > 1) + break; + } + if (count < 2 && u && u != me && !IsSpellReady(TORMENT_1, diff, false) && !IsTank(u) && !CCed(opponent) && dist < 8 && + IsInBotParty(u)) + { + count += 2; + } + + if (count > 1) + { + me->CastSpell(me, GetSpell(SUFFERING_1), false); + SetSpellCooldown(SUFFERING_1, 120000); + return; + } + } + } + else if (myType == BOT_PET_SUCCUBUS) + { + if (IsSpellReady(LASH_OF_PAIN_1, diff, false) && canDPS && me->IsWithinMeleeRange(opponent)) + { + me->CastSpell(opponent, GetSpell(LASH_OF_PAIN_1), false); + //this could have worked if cast was triggered + //SetSpellCooldown(LASH_OF_PAIN_1, 6000); //Demonic Power part 1 + return; + } + } + else if (myType == BOT_PET_FELHUNTER) + { + if (IsSpellReady(SHADOW_BITE_1, diff, false) && canDPS && me->IsWithinMeleeRange(opponent)) + { + me->CastSpell(opponent, GetSpell(SHADOW_BITE_1), false); + SetSpellCooldown(SHADOW_BITE_1, me->GetLevel() >= 35 ? 2000 : 6000); //improved felhunter part 2 + return; + } + } + else if (myType == BOT_PET_FELGUARD) + { + if (IsSpellReady(INTERCEPT_1, diff, false) && canDPS && + !HasBotCommandState(BOT_COMMAND_STAY) && + !(opponent->GetTypeId() == TYPEID_UNIT && opponent->ToCreature()->isWorldBoss()) && + dist > 8 && dist < 25 && !CCed(opponent)) + { + me->CastSpell(opponent, GetSpell(INTERCEPT_1), false); + SetSpellCooldown(INTERCEPT_1, 30000); + return; + } + + if (IsSpellReady(ANGUISH_1, diff, false) && u && u != me && me->IsWithinMeleeRange(opponent) && + opponent->CanHaveThreatList() && !CCed(opponent) && !opponent->HasAuraType(SPELL_AURA_MOD_TAUNT) && + (!IsTank(u) || (GetHealthPCT(u) < 30 && GetHealthPCT(me) > 20)) && IsInBotParty(u)) + { + me->CastSpell(opponent, GetSpell(ANGUISH_1), false); + SetSpellCooldown(ANGUISH_1, 5000); + return; + } + + if (IsSpellReady(CLEAVE_1, diff, false) && canDPS && me->IsWithinMeleeRange(opponent)) + { + me->CastSpell(opponent, GetSpell(CLEAVE_1), false); + SetSpellCooldown(CLEAVE_1, 6000); + return; + } + } + } + + void OnPetClassSpellGo(SpellInfo const* /*spellInfo*/) override + { + } + + void SpellHit(WorldObject* wcaster, SpellInfo const* spell) override + { + Unit* caster = wcaster->ToUnit(); + if (!caster) + return; + + OnSpellHit(caster, spell); + } + + void SpellHitTarget(WorldObject* /*wtarget*/, SpellInfo const* /*spell*/) override + { + } + + void DamageDealt(Unit* victim, uint32& damage, DamageEffectType damageType) override + { + bot_pet_ai::DamageDealt(victim, damage, damageType); + } + + void DamageTaken(Unit* u, uint32& /*damage*/, DamageEffectType /*damageType*/, SpellInfo const* /*spellInfo*/) override + { + if (!u) + return; + if (!u->IsInCombat() && !me->IsInCombat()) + return; + OnOwnerDamagedBy(u); + } + + void OwnerAttackedBy(Unit* u) override + { + if (!u) + return; + OnOwnerDamagedBy(u); + } + + void Reset() override + { + } + + void InitPetSpells() override + { + InitSpellMap(FIREBOLT_1); + InitSpellMap(BLOOD_PACT_1); + InitSpellMap(PHASE_SHIFT_1); + //InitSpellMap(FIRE_SHIELD_1); + + InitSpellMap(TORMENT_1); + InitSpellMap(SACRIFICE_1); + InitSpellMap(CONSUME_SHADOWS_1); + InitSpellMap(SUFFERING_1); + + InitSpellMap(LASH_OF_PAIN_1); + InitSpellMap(SOOTHING_KISS_1); + InitSpellMap(SEDUCTION_1); + InitSpellMap(LESSER_INVISIBILITY_1); + + InitSpellMap(DEVOUR_MAGIC_1); + InitSpellMap(FEL_INTELLIGENCE_1); + InitSpellMap(SPELL_LOCK_1); + InitSpellMap(SHADOW_BITE_1); + + InitSpellMap(ANGUISH_1); + InitSpellMap(CLEAVE_1); + InitSpellMap(INTERCEPT_1); + + InitSpellMap(SOUL_LINK_PET); + } + + void ApplyPetPassives() const override + { + uint8 lvl = me->GetLevel(); + switch (myType) + { + case BOT_PET_FELGUARD: + RefreshAura(DEMONIC_FRENZY); + break; + default: + break; + } + + RefreshAura(AVOIDANCE, lvl >= 60 ? 1 : 0); + } + + private: + }; +}; + +void AddSC_warlock_bot_pets() +{ + new warlock_pet_bot(); +} diff --git a/src/server/game/AI/NpcBots/lib/botlogtraits.h b/src/server/game/AI/NpcBots/lib/botlogtraits.h new file mode 100644 index 000000000..3bcbe4d81 --- /dev/null +++ b/src/server/game/AI/NpcBots/lib/botlogtraits.h @@ -0,0 +1,46 @@ +#ifndef BOT_LOG_TRAITS_H_ +#define BOT_LOG_TRAITS_H_ + +#include "botcommon.h" + +#include "StringConvert.h" + +namespace NPCBots +{ + +namespace StringConvert +{ + template + static std::enable_if_t || std::is_floating_point_v, std::string> + ToString(T t) + { + return Trinity::Impl::StringConvertImpl::For::ToString(t); + } + + template + static std::enable_if_t || std::is_same_v, std::string> + ToString(T t) + { + return std::string{ t }; + } + + template + static std::enable_if_t && !std::is_same_v && std::is_constructible_v, std::string> + ToString(T t) + { + return std::string(t); + } +} + +template +concept Stringable = requires(T t) { StringConvert::ToString(t); }; + +template +concept LoggableCount = (sizeof...(Ts) <= MAX_BOT_LOG_PARAMS); + +template +concept LoggableArguments = LoggableCount && (Stringable && ...); + +} + +#endif diff --git a/src/server/game/AI/NpcBots/lib/bottraits.h b/src/server/game/AI/NpcBots/lib/bottraits.h new file mode 100644 index 000000000..a80cef6d2 --- /dev/null +++ b/src/server/game/AI/NpcBots/lib/bottraits.h @@ -0,0 +1,156 @@ +#ifndef BOT_TRAITS_H +#define BOT_TRAITS_H + +#include "botcommon.h" + +#include "Creature.h" +#include "Log.h" +#include "SpellAuraEffects.h" + +#include +#include + +namespace NPCBots +{ + +template +constexpr auto fixed_tuple_helper(std::index_sequence const&) -> decltype(std::make_tuple(((void)Is, std::declval())...)); + +template +struct fixed_tuple { + using tuple_type = decltype(fixed_tuple_helper(std::make_index_sequence{})); +}; + +template +typename fixed_tuple::tuple_type to_tuple_helper(std::array&& arr, std::index_sequence&&) +{ + return std::make_tuple(arr[Is]...); +} + +template +typename fixed_tuple::tuple_type to_tuple(std::array&& arr) +{ + return to_tuple_helper(std::forward>(arr), std::make_index_sequence{}); +} + +template +typename fixed_tuple::tuple_type to_spell_school_affect_bool_tuple_helper(std::array&& arr, std::index_sequence&&) +{ + return std::make_tuple(arr[Is].second...); +} + +template +typename fixed_tuple::tuple_type to_spell_school_affect_bool_tuple(std::array&& arr) +{ + return to_spell_school_affect_bool_tuple_helper(std::forward>(arr), std::make_index_sequence{}); +} + +template +std::array to_spell_school_affect_bool_arr_helper(std::array&& arr, std::index_sequence&&) +{ + return std::array{ arr[Is].second... }; +} + +template +std::array to_spell_school_affect_bool_arr(std::array&& arr) +{ + return to_spell_school_affect_bool_arr_helper(std::forward>(arr), std::make_index_sequence{}); +} + +} + +template +std::enable_if_t, std::is_same...>, + bool> +all_schools_valid(School school, Schools... schools) +{ + if (school < SPELL_SCHOOL_NORMAL || school >= MAX_SPELL_SCHOOL) + return false; + + if constexpr (sizeof...(Schools) > 0) + return all_schools_valid(schools...); + return true; +} + +template +std::enable_if_t...>, std::array, sizeof...(Schools)>> +CanAffectVictimSchools(Unit const* target, Schools... schools) +{ + static_assert(sizeof...(Schools) > 0, "need at least 1 spell school to check for"); + + using arr_type = std::array, sizeof...(Schools)>; + using arr_iter_type = typename arr_type::iterator; + arr_type results{ std::pair{schools, true}... }; + + if (!all_schools_valid(schools...)) + { + TC_LOG_ERROR("entities.player", "bot_ai::CanAffectVictimSchools(): trying to check invalid spell school, first: {}", uint32(results.at(0).first)); + return results; + } + + if (Creature const* creature = target->ToCreature()) + { + if (SpellSchoolMask immune_mask = SpellSchoolMask(creature->GetCreatureTemplate()->SpellSchoolImmuneMask)) + { + for (uint8 i = SPELL_SCHOOL_NORMAL; i < MAX_SPELL_SCHOOL; ++i) + { + if (immune_mask & (1 << i)) + { + arr_iter_type ri = std::find(results.begin(), results.end(), std::pair{ SpellSchools(i), true }); + if (ri != results.end()) + ri->second = false; + } + } + } + } + + for (AuraEffect const* immune_effect : target->GetAuraEffectsByType(SPELL_AURA_SCHOOL_IMMUNITY)) + { + if (SpellSchoolMask immune_mask = SpellSchoolMask(immune_effect->GetMiscValue())) + { + for (uint8 i = SPELL_SCHOOL_NORMAL; i < MAX_SPELL_SCHOOL; ++i) + { + if (immune_mask & (1 << i)) + { + arr_iter_type ri = std::find(results.begin(), results.end(), std::pair{ SpellSchools(i), true }); + if (ri != results.end()) + ri->second = false; + } + } + } + } + return results; +} + +template +typename NPCBots::fixed_tuple::tuple_type +CanAffectVictimBools(Unit const* target, Schools... schools) +{ + return NPCBots::to_spell_school_affect_bool_tuple(CanAffectVictimSchools(target, schools...)); +} + +template +bool +CanAffectVictimAny(Unit const* target, Schools... schools) +{ + using arr_type = std::array, sizeof...(Schools)>; + using pair_type = typename arr_type::value_type; + + arr_type bools = CanAffectVictimSchools(target, schools...); + + return std::any_of(bools.cbegin(), bools.cend(), [](pair_type const& p) { return p.second; }); +} + +template +bool +CanAffectVictimAll(Unit const* target, Schools... schools) +{ + using arr_type = std::array, sizeof...(Schools)>; + using pair_type = typename arr_type::value_type; + + arr_type bools = CanAffectVictimSchools(target, schools...); + + return std::all_of(bools.cbegin(), bools.cend(), [](pair_type const& p) { return p.second; }); +} + +#endif diff --git a/src/server/game/AI/SmartScripts/SmartScript.cpp b/src/server/game/AI/SmartScripts/SmartScript.cpp index 0cfea7c0c..fb6d807a2 100644 --- a/src/server/game/AI/SmartScripts/SmartScript.cpp +++ b/src/server/game/AI/SmartScripts/SmartScript.cpp @@ -293,6 +293,9 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u talkTarget = target->ToCreature(); } else + //npcbot: prevent using bots as talkers + if (!target->ToCreature()->IsNPCBotOrPet()) + //end npcbot talker = target->ToCreature(); break; } diff --git a/src/server/game/Accounts/RBAC.h b/src/server/game/Accounts/RBAC.h index 3b6437c1b..acbcd6a2f 100644 --- a/src/server/game/Accounts/RBAC.h +++ b/src/server/game/Accounts/RBAC.h @@ -752,6 +752,45 @@ enum RBACPermissions // IF YOU ADD NEW PERMISSIONS, ADD THEM IN MASTER BRANCH AS WELL! // // custom permissions 1000+ + //NPCBot + RBAC_PERM_COMMAND_NPCBOT = 70001, + RBAC_PERM_COMMAND_NPCBOT_ADD = 70002, + RBAC_PERM_COMMAND_NPCBOT_REMOVE = 70003, + RBAC_PERM_COMMAND_NPCBOT_SPAWN = 70004, + RBAC_PERM_COMMAND_NPCBOT_MOVE = 70005, + RBAC_PERM_COMMAND_NPCBOT_DELETE = 70006, + RBAC_PERM_COMMAND_NPCBOT_LOOKUP = 70007, + RBAC_PERM_COMMAND_NPCBOT_REVIVE = 70008, + RBAC_PERM_COMMAND_NPCBOT_RELOADCONFIG = 70009, + RBAC_PERM_COMMAND_NPCBOT_INFO = 70010, + RBAC_PERM_COMMAND_NPCBOT_HIDE = 70011, + RBAC_PERM_COMMAND_NPCBOT_UNHIDE = 70012, + RBAC_PERM_COMMAND_NPCBOT_RECALL = 70013, + RBAC_PERM_COMMAND_NPCBOT_KILL = 70014, + RBAC_PERM_COMMAND_NPCBOT_DEBUG_RAID = 70015, + RBAC_PERM_COMMAND_NPCBOT_DEBUG_MOUNT = 70016, + RBAC_PERM_COMMAND_NPCBOT_DEBUG_VISUAL = 70017, + RBAC_PERM_COMMAND_NPCBOT_DEBUG_STATES = 70018, + RBAC_PERM_COMMAND_NPCBOT_TOGGLE_FLAGS = 70019, + RBAC_PERM_COMMAND_NPCBOT_SET_FACTION = 70020, + RBAC_PERM_COMMAND_NPCBOT_SET_OWNER = 70021, + RBAC_PERM_COMMAND_NPCBOT_SET_SPEC = 70022, + RBAC_PERM_COMMAND_NPCBOT_COMMAND_STANDSTILL = 70023, + RBAC_PERM_COMMAND_NPCBOT_COMMAND_STOPFULLY = 70024, + RBAC_PERM_COMMAND_NPCBOT_COMMAND_FOLLOW = 70025, + RBAC_PERM_COMMAND_NPCBOT_ATTDISTANCE_SHORT = 70026, + RBAC_PERM_COMMAND_NPCBOT_ATTDISTANCE_LONG = 70027, + RBAC_PERM_COMMAND_NPCBOT_ATTDISTANCE_EXACT = 70028, + RBAC_PERM_COMMAND_NPCBOT_FOLDISTANCE_EXACT = 70029, + RBAC_PERM_COMMAND_NPCBOT_ORDER_CAST = 70030, + RBAC_PERM_COMMAND_NPCBOT_VEHICLE_EJECT = 70031, + RBAC_PERM_COMMAND_NPCBOT_DUMP_LOAD = 70032, + RBAC_PERM_COMMAND_NPCBOT_DUMP_WRITE = 70033, + RBAC_PERM_COMMAND_NPCBOT_SPAWNED = 70034, + RBAC_PERM_COMMAND_NPCBOT_COMMAND_MISC = 70035, + RBAC_PERM_COMMAND_NPCBOT_CREATENEW = 70036, + RBAC_PERM_COMMAND_NPCBOT_SEND = 70037, + //End NPCBot RBAC_PERM_MAX }; diff --git a/src/server/game/Battlegrounds/Battleground.cpp b/src/server/game/Battlegrounds/Battleground.cpp index 938ac559b..d4d930b12 100644 --- a/src/server/game/Battlegrounds/Battleground.cpp +++ b/src/server/game/Battlegrounds/Battleground.cpp @@ -42,6 +42,12 @@ #include "WorldStatePackets.h" #include +//npcbot +#include "bot_ai.h" +#include "botdatamgr.h" +#include "botmgr.h" +//end npcbot + void BattlegroundScore::AppendToPacket(WorldPacket& data) { data << uint64(PlayerGuid); @@ -158,6 +164,11 @@ Battleground::~Battleground() for (BattlegroundScoreMap::const_iterator itr = PlayerScores.begin(); itr != PlayerScores.end(); ++itr) delete itr->second; + + //npcbot + for (BattlegroundScoreMap::const_iterator itr = BotScores.begin(); itr != BotScores.end(); ++itr) + delete itr->second; + //end npcbot } void Battleground::Update(uint32 diff) @@ -165,6 +176,9 @@ void Battleground::Update(uint32 diff) if (!PreUpdateImpl(diff)) return; + //npcbot + if (m_Bots.empty()) + //end npcbot if (!GetPlayersSize()) { //BG is empty @@ -182,6 +196,17 @@ void Battleground::Update(uint32 diff) return; } + //npcbot: end BG if no real players exist + if (GetStatus() != STATUS_WAIT_LEAVE) + { + if (m_Players.empty() && !m_Bots.empty()) + { + EndNow(); + return; + } + } + //end npcbot + switch (GetStatus()) { case STATUS_WAIT_JOIN: @@ -298,6 +323,28 @@ inline void Battleground::_ProcessResurrect(uint32 diff) Creature* sh = nullptr; for (GuidVector::const_iterator itr2 = (itr->second).begin(); itr2 != (itr->second).end(); ++itr2) { + //npcbot + if (itr2->IsCreature()) + { + if (Creature const* cbot = BotDataMgr::FindBot(itr2->GetEntry())) + { + Creature* bot = const_cast(cbot); + ASSERT(bot->IsInWorld()); + if (!sh) + sh = bot->GetMap()->GetCreature(itr->first); + if (sh) + { + if (bot->GetExactDist(sh) > 15.0f) + bot->NearTeleportTo(*sh); + sh->CastSpell(sh, SPELL_SPIRIT_HEAL, true); + } + bot->CastSpell(bot, SPELL_RESURRECTION_VISUAL, true); + m_ResurrectQueue.push_back(*itr2); + } + continue; + } + //end npcbot + Player* player = ObjectAccessor::FindPlayer(*itr2); if (!player) continue; @@ -329,6 +376,15 @@ inline void Battleground::_ProcessResurrect(uint32 diff) { for (GuidVector::const_iterator itr = m_ResurrectQueue.begin(); itr != m_ResurrectQueue.end(); ++itr) { + //npcbot + if (itr->IsCreature()) + { + if (Creature const* cbot = BotDataMgr::FindBot(itr->GetEntry())) + cbot->GetBotAI()->UpdateReviveTimer(std::numeric_limits::max()); + continue; + } + //end npcbot + Player* player = ObjectAccessor::FindPlayer(*itr); if (!player) continue; @@ -500,6 +556,17 @@ inline void Battleground::_ProcessJoin(uint32 diff) if (sWorld->getBoolConfig(CONFIG_BATTLEGROUND_QUEUE_ANNOUNCER_ENABLE)) sWorld->SendWorldText(LANG_BG_STARTED_ANNOUNCE_WORLD, GetName().c_str(), GetMinLevel(), GetMaxLevel()); } + + //npcbot: activate bots + for (auto const& kv : m_Bots) + { + if (Creature const* bot = BotDataMgr::FindBot(kv.first.GetEntry())) + { + if (bot->IsNPCBot() && bot->IsWandererBot()) + bot->GetBotAI()->RemoveBotCommandState(BOT_COMMAND_STAY); + } + } + //end npcbot } } @@ -513,6 +580,15 @@ inline void Battleground::_ProcessLeave(uint32 diff) if (m_EndTime <= 0) { m_EndTime = 0; + //npcbot + BattlegroundBotMap::iterator bitr, bnext; + for (bitr = m_Bots.begin(); bitr != m_Bots.end(); bitr = bnext) + { + bnext = bitr; + ++bnext; + RemoveBotAtLeave(bitr->first); + } + //end npcbot BattlegroundPlayerMap::iterator itr, next; for (itr = m_Players.begin(); itr != m_Players.end(); itr = next) { @@ -528,6 +604,12 @@ inline void Battleground::_ProcessLeave(uint32 diff) Player* Battleground::_GetPlayer(ObjectGuid guid, bool offlineRemove, char const* context) const { Player* player = nullptr; + + //npcbot + if (guid.IsCreature()) + return player; + //end npcbot + if (!offlineRemove) { // should this be ObjectAccessor::FindConnectedPlayer() to return players teleporting ? @@ -621,6 +703,12 @@ void Battleground::CastSpellOnTeam(uint32 SpellID, uint32 TeamID) for (BattlegroundPlayerMap::const_iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr) if (Player* player = _GetPlayerForTeam(TeamID, itr, "CastSpellOnTeam")) player->CastSpell(player, SpellID, true); + //npcbot + for (auto const& kv : m_Bots) + if (kv.second.Team == TeamID) + if (Creature* bot = GetBgMap()->GetCreature(kv.first)) + bot->CastSpell(bot, SpellID, true); + //end npcbot } void Battleground::RemoveAuraOnTeam(uint32 SpellID, uint32 TeamID) @@ -721,6 +809,31 @@ void Battleground::EndBattleground(uint32 winner) BattlegroundQueueTypeId bgQueueTypeId = BattlegroundMgr::BGQueueTypeId(GetTypeID(), GetArenaType()); + //npcbot: despawn generated bots immediately + BattlegroundBotMap::iterator bitr, bnext; + for (bitr = m_Bots.begin(); bitr != m_Bots.end(); bitr = bnext) + { + bnext = bitr; + ++bnext; + if (bitr->first.IsCreature()) + { + if (Creature const* bot = BotDataMgr::FindBot(bitr->first.GetEntry())) + { + if (!bot->IsAlive()) + BotMgr::ReviveBot(const_cast(bot)); + else + { + bot->GetBotAI()->UnsummonAll(false); + const_cast(bot)->InterruptNonMeleeSpells(true); + const_cast(bot)->RemoveAllControlled(); + const_cast(bot)->SetUnitFlag(UNIT_FLAG_IMMUNE); + const_cast(bot)->AddUnitState(UNIT_STATE_STUNNED); + } + } + } + } + //end npcbot + for (BattlegroundPlayerMap::iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr) { uint32 team = itr->second.Team; @@ -854,6 +967,15 @@ void Battleground::RemovePlayerAtLeave(ObjectGuid guid, bool Transport, bool Sen player->ResurrectPlayer(1.0f); player->SpawnCorpseBones(); } + + //npcbot + if (player->HaveBot()) + { + BotMap const* map = player->GetBotMgr()->GetBotMap(); + for (BotMap::const_iterator itr = map->begin(); itr != map->end(); ++itr) + RemoveBotAtLeave(itr->first); + } + //end npcbot } else { @@ -931,6 +1053,83 @@ void Battleground::RemovePlayerAtLeave(ObjectGuid guid, bool Transport, bool Sen //battleground object will be deleted next Battleground::Update() call } +//npcbot +void Battleground::RemoveBotAtLeave(ObjectGuid guid) +{ + uint32 team = GetBotTeam(guid); + + // Remove from lists/maps + bool participant = false; + BattlegroundBotMap::iterator itr = m_Bots.find(guid); + if (itr != m_Bots.end()) + { + UpdatePlayersCountByTeam(team, true); // -1 player + m_Bots.erase(itr); + participant = true; + } + + // delete player score if exists + auto const& itr2 = BotScores.find(guid.GetEntry()); + if (itr2 != BotScores.end()) + { + delete itr2->second; + BotScores.erase(itr2); + } + + RemoveBotFromResurrectQueue(guid); + + // BG subclass specific code + RemoveBot(guid); + + if (participant) // if the player was a match participant, remove auras, calc rating, update queue + { + // remove from raid group if player is member + if (Group* group = GetBgRaid(team)) + { + if (group->IsMember(guid)) + { + if (!group->RemoveMember(guid)) // group was disbanded + SetBgRaid(team, nullptr); + } + } + + // Let others know + WorldPacket data; + sBattlegroundMgr->BuildPlayerLeftBattlegroundPacket(&data, guid); + SendPacketToTeam(team, &data, nullptr, false); + + DecreaseInvitedCount(team); + + //we should update battleground queue, but only if bg isn't ending + if (isBattleground() && GetStatus() < STATUS_WAIT_LEAVE) + { + BattlegroundTypeId bgTypeId = GetTypeID(); + BattlegroundQueueTypeId bgQueueTypeId = BattlegroundMgr::BGQueueTypeId(bgTypeId, GetArenaType()); + + // a player has left the battleground, so there are free slots -> add to queue + AddToBGFreeSlotQueue(); + sBattlegroundMgr->ScheduleQueueUpdate(0, 0, bgQueueTypeId, bgTypeId, GetBracketId()); + } + } + + if (Creature const* bot = BotDataMgr::FindBot(guid.GetEntry())) + { + if (bot->HasAuraType(SPELL_AURA_SPIRIT_OF_REDEMPTION)) + const_cast(bot)->RemoveAurasByType(SPELL_AURA_MOD_SHAPESHIFT); + const_cast(bot)->RemoveAurasByType(SPELL_AURA_MOUNTED); + const_cast(bot)->RemoveUnitFlag(UNIT_FLAG_IMMUNE); + const_cast(bot)->ClearUnitState(UNIT_STATE_STUNNED); + + bot->GetBotAI()->SetBG(nullptr); + if (bot->IsWandererBot()) + { + bot->GetBotAI()->canUpdate = false; + BotDataMgr::DespawnWandererBot(guid.GetEntry()); + } + } +} +//end npcbot + // this method is called when no players remains in battleground void Battleground::Reset() { @@ -950,11 +1149,18 @@ void Battleground::Reset() m_InBGFreeSlotQueue = false; m_Players.clear(); + m_Bots.clear(); for (BattlegroundScoreMap::const_iterator itr = PlayerScores.begin(); itr != PlayerScores.end(); ++itr) delete itr->second; PlayerScores.clear(); + //npcbot + for (auto const& itr2 : BotScores) + delete itr2.second; + BotScores.clear(); + //end npcbot + for (uint8 i = 0; i < PVP_TEAMS_COUNT; ++i) _arenaTeamScores[i].Reset(); @@ -998,6 +1204,19 @@ void Battleground::AddPlayer(Player* player) if (!isInBattleground) UpdatePlayersCountByTeam(team, false); // +1 player + //npcbot + if (player->GetGroup() && player->HaveBot()) + { + BotMap const* map = player->GetBotMgr()->GetBotMap(); + for (BotMap::const_iterator itr = map->begin(); itr != map->end(); ++itr) + { + Creature* bot = itr->second; + if (bot && player->GetGroup()->IsMember(itr->first)) + AddBot(bot); + } + } + //end npcbot + WorldPacket data; sBattlegroundMgr->BuildPlayerJoinedBattlegroundPacket(&data, player); SendPacketToTeam(team, &data, player, false); @@ -1032,6 +1251,30 @@ void Battleground::AddPlayer(Player* player) AddOrSetPlayerToCorrectBgGroup(player, team); } +//npcbot +void Battleground::AddBot(Creature* bot) +{ + ObjectGuid guid = bot->GetGUID(); + uint32 team = (BotDataMgr::GetTeamIdForFaction(bot->GetFaction()) == TEAM_ALLIANCE) ? ALLIANCE : HORDE; + + // Add to list/maps + BattlegroundBot bb; + bb.Team = team; + m_Bots[guid] = bb; + + UpdatePlayersCountByTeam(team, false); // +1 player + + WorldPacket data; + sBattlegroundMgr->BuildPlayerJoinedBattlegroundPacket(&data, (Player*)bot); + SendPacketToTeam(team, &data, nullptr, false); + + AddOrSetBotToCorrectBgGroup(bot, team); + + bot->GetBotAI()->SetBG(this); + bot->GetBotAI()->OnBotEnterBattleground(); +} +//end npcbot + // this method adds player to his team's bg group, or sets his correct group if player is already in bg group void Battleground::AddOrSetPlayerToCorrectBgGroup(Player* player, uint32 team) { @@ -1064,6 +1307,30 @@ void Battleground::AddOrSetPlayerToCorrectBgGroup(Player* player, uint32 team) } } +//npcbot +void Battleground::AddOrSetBotToCorrectBgGroup(Creature* bot, uint32 team) +{ + ObjectGuid botGuid = bot->GetGUID(); + Group* group = GetBgRaid(team); + if (!group) // first player joined + { + group = new Group; + SetBgRaid(team, group); + group->Create(bot); + } + else // raid already exist + { + if (group->IsMember(botGuid)) + { + uint8 subgroup = group->GetMemberGroup(botGuid); + bot->SetBattlegroundOrBattlefieldRaid(group, subgroup); + } + else + group->AddMember(bot); + } +} +//end npcbot + // This method should be called when player logs into running battleground void Battleground::EventPlayerLoggedIn(Player* player) { @@ -1194,14 +1461,27 @@ uint32 Battleground::GetFreeSlotsForTeam(uint32 Team) const bool Battleground::HasFreeSlots() const { + //npcbot + /* + //end npcbot return GetPlayersSize() < GetMaxPlayers(); + //npcbot + */ + return GetPlayersSize() + uint32(GetBots().size()) < GetMaxPlayers(); + //end npcbot } void Battleground::BuildPvPLogDataPacket(WorldPacket& data) { uint8 type = (isArena() ? 1 : 0); + //npcbot + /* data.Initialize(MSG_PVP_LOG_DATA, 1 + 1 + 4 + 40 * GetPlayerScoresSize()); + */ + data.Initialize(MSG_PVP_LOG_DATA, 1 + 1 + 4 + 40 * (GetPlayerScoresSize() + GetBotScoresSize())); + //end npcbot + data << uint8(type); // type (battleground = 0 / arena = 1) if (type) // arena @@ -1221,7 +1501,15 @@ void Battleground::BuildPvPLogDataPacket(WorldPacket& data) else data << uint8(0); // bg not ended + //npcbot + /* data << uint32(GetPlayerScoresSize()); + */ + data << uint32(GetPlayerScoresSize() + GetBotScoresSize()); + for (auto const& bscore : BotScores) + bscore.second->AppendToPacket(data); + //end npcbot + for (auto const& score : PlayerScores) score.second->AppendToPacket(data); } @@ -1240,6 +1528,18 @@ bool Battleground::UpdatePlayerScore(Player* player, uint32 type, uint32 value, return true; } +//npcbot +bool Battleground::UpdateBotScore(Creature const* bot, uint32 type, uint32 value, bool /*doAddHonor*/) +{ + BattlegroundScoreMap::const_iterator itr = BotScores.find(bot->GetEntry()); + if (itr == BotScores.end()) // bot not found... + return false; + + itr->second->UpdateScore(type, value); + return true; +} +//end npcbot + void Battleground::AddPlayerToResurrectQueue(ObjectGuid npc_guid, ObjectGuid player_guid) { m_ReviveQueue[npc_guid].push_back(player_guid); @@ -1268,6 +1568,23 @@ void Battleground::RemovePlayerFromResurrectQueue(ObjectGuid player_guid) } } +//npcbot +void Battleground::RemoveBotFromResurrectQueue(ObjectGuid guid) +{ + for (auto& kv : m_ReviveQueue) + { + for (GuidVector::iterator itr2 = kv.second.begin(); itr2 != kv.second.end(); ++itr2) + { + if (*itr2 == guid) + { + kv.second.erase(itr2); + return; + } + } + } +} +//end npcbot + void Battleground::RelocateDeadPlayers(ObjectGuid guideGuid) { // Those who are waiting to resurrect at this node are taken to the closest own node's graveyard @@ -1275,8 +1592,25 @@ void Battleground::RelocateDeadPlayers(ObjectGuid guideGuid) if (!ghostList.empty()) { WorldSafeLocsEntry const* closestGrave = nullptr; + //npcbot + WorldSafeLocsEntry const* closestBotGrave = nullptr; + //end npcbot for (GuidVector::const_iterator itr = ghostList.begin(); itr != ghostList.end(); ++itr) { + //npcbot + if (itr->IsCreature()) + { + if (Creature const* bot = BotDataMgr::FindBot(itr->GetEntry())) + { + if (!closestBotGrave) + closestBotGrave = GetClosestGraveyardForBot(*bot, GetBotTeam(*itr)); + if (closestBotGrave) + const_cast(bot)->NearTeleportTo(Position(closestBotGrave->Loc.X, closestBotGrave->Loc.Y, closestBotGrave->Loc.Z)); + } + continue; + } + //end npcbot + Player* player = ObjectAccessor::FindPlayer(*itr); if (!player) continue; @@ -1688,6 +2022,55 @@ void Battleground::HandleKillPlayer(Player* victim, Player* killer) if (creditedPlayer->GetTeam() == killer->GetTeam() && creditedPlayer->IsAtGroupRewardDistance(victim)) UpdatePlayerScore(creditedPlayer, SCORE_HONORABLE_KILLS, 1); } + + //npcbot + uint32 team = killer->GetTeam(); + for (auto const& kv : m_Bots) + { + if (kv.second.Team != team || kv.first == killer->GetGUID()) + continue; + Creature const* teamedBot = BotDataMgr::FindBot(kv.first.GetEntry()); + if (teamedBot && teamedBot->GetDistance(victim) <= sWorld->getFloatConfig(CONFIG_GROUP_XP_DISTANCE)) + UpdateBotScore(teamedBot, SCORE_HONORABLE_KILLS, 1); + } + //end npcbot + } + + if (!isArena()) + { + // To be able to remove insignia -- ONLY IN Battlegrounds + victim->SetUnitFlag(UNIT_FLAG_SKINNABLE); + RewardXPAtKill(killer, victim); + } +} + +//npcbot +void Battleground::HandleBotKillPlayer(Creature* killer, Player* victim) +{ + UpdatePlayerScore(victim, SCORE_DEATHS, 1); + + if (killer) + { + uint32 team = GetBotTeam(killer->GetGUID()); + + UpdateBotScore(killer, SCORE_HONORABLE_KILLS, 1); + UpdateBotScore(killer, SCORE_KILLING_BLOWS, 1); + + for (BattlegroundPlayerMap::const_iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr) + { + Player* creditedPlayer = ObjectAccessor::FindPlayer(itr->first); + if (creditedPlayer && creditedPlayer->GetTeam() == team && creditedPlayer->IsAtGroupRewardDistance(victim)) + UpdatePlayerScore(creditedPlayer, SCORE_HONORABLE_KILLS, 1); + } + + for (auto const& kv : m_Bots) + { + if (kv.second.Team != team || kv.first == killer->GetGUID()) + continue; + Creature const* teamedBot = BotDataMgr::FindBot(kv.first.GetEntry()); + if (teamedBot && teamedBot->GetDistance(victim) <= sWorld->getFloatConfig(CONFIG_GROUP_XP_DISTANCE)) + UpdateBotScore(teamedBot, SCORE_HONORABLE_KILLS, 1); + } } if (!isArena()) @@ -1697,6 +2080,100 @@ void Battleground::HandleKillPlayer(Player* victim, Player* killer) RewardXPAtKill(killer, victim); } } +void Battleground::HandleBotKillBot(Creature* killer, Creature* victim) +{ + UpdateBotScore(victim, SCORE_DEATHS, 1); + // Add +1 kills to group and +1 killing_blows to killer + if (killer) + { + uint32 team = GetBotTeam(killer->GetGUID()); + + UpdateBotScore(killer, SCORE_HONORABLE_KILLS, 1); + UpdateBotScore(killer, SCORE_KILLING_BLOWS, 1); + + for (BattlegroundPlayerMap::const_iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr) + { + Player* creditedPlayer = ObjectAccessor::FindPlayer(itr->first); + if (creditedPlayer && creditedPlayer->GetTeam() == team && creditedPlayer->IsAtGroupRewardDistance(victim)) + UpdatePlayerScore(creditedPlayer, SCORE_HONORABLE_KILLS, 1); + } + + for (auto const& kv : m_Bots) + { + if (kv.second.Team != team || kv.first == killer->GetGUID()) + continue; + Creature const* teamedBot = BotDataMgr::FindBot(kv.first.GetEntry()); + if (teamedBot && teamedBot->GetDistance(victim) <= sWorld->getFloatConfig(CONFIG_GROUP_XP_DISTANCE)) + UpdateBotScore(teamedBot, SCORE_HONORABLE_KILLS, 1); + } + } + if (!isArena() && !victim->GetLootRecipient()) // Prevent double reward (AI->KilledUnit (killing blow) and Unit::Kill (recipient)) + RewardXPAtKill(killer, victim); +} +void Battleground::HandlePlayerKillBot(Creature* victim, Player* killer) +{ + UpdateBotScore(victim, SCORE_DEATHS, 1); + // Add +1 kills to group and +1 killing_blows to killer + if (killer) + { + uint32 team = killer->GetTeam(); + + UpdatePlayerScore(killer, SCORE_HONORABLE_KILLS, 1); + UpdatePlayerScore(killer, SCORE_KILLING_BLOWS, 1); + + for (BattlegroundPlayerMap::const_iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr) + { + Player* creditedPlayer = ObjectAccessor::FindPlayer(itr->first); + if (!creditedPlayer || creditedPlayer == killer) + continue; + + if (creditedPlayer->GetTeam() == killer->GetTeam() && creditedPlayer->IsAtGroupRewardDistance(victim)) + UpdatePlayerScore(creditedPlayer, SCORE_HONORABLE_KILLS, 1); + } + + for (auto const& kv : m_Bots) + { + if (kv.second.Team != team || kv.first == killer->GetGUID()) + continue; + Creature const* teamedBot = BotDataMgr::FindBot(kv.first.GetEntry()); + if (teamedBot && teamedBot->GetDistance(victim) <= sWorld->getFloatConfig(CONFIG_GROUP_XP_DISTANCE)) + UpdateBotScore(teamedBot, SCORE_HONORABLE_KILLS, 1); + } + } + if (!isArena()) + RewardXPAtKill(killer, victim); +} + +TeamId Battleground::GetOtherTeamId(TeamId teamId) const +{ + return (teamId == TEAM_ALLIANCE) ? TEAM_HORDE : (teamId == TEAM_HORDE) ? TEAM_ALLIANCE : teamId; +} + +TeamId Battleground::GetBotTeamId(ObjectGuid guid) const +{ + uint32 team = GetBotTeam(guid); + switch (team) + { + case ALLIANCE: + return TEAM_ALLIANCE; + case HORDE: + return TEAM_HORDE; + case TEAM_ALLIANCE: + case TEAM_HORDE: + return TeamId(team); + default: + return TEAM_NEUTRAL; + } +} + +uint32 Battleground::GetBotTeam(ObjectGuid guid) const +{ + BattlegroundBotMap::const_iterator itr = m_Bots.find(guid); + if (itr != m_Bots.end()) + return itr->second.Team; + return 0; +} +//end npcbot // Return the player's team based on battlegroundplayer info // Used in same faction arena matches mainly @@ -1715,6 +2192,14 @@ uint32 Battleground::GetOtherTeam(uint32 teamId) const bool Battleground::IsPlayerInBattleground(ObjectGuid guid) const { + //npcbot + if (guid.IsCreature()) + { + BattlegroundBotMap::const_iterator bitr = m_Bots.find(guid); + if (bitr != m_Bots.end()) + return true; + } + //end npcbot BattlegroundPlayerMap::const_iterator itr = m_Players.find(guid); if (itr != m_Players.end()) return true; @@ -1741,6 +2226,17 @@ void Battleground::PlayerAddedToBGCheckIfBGIsRunning(Player* player) uint32 Battleground::GetAlivePlayersCountByTeam(uint32 Team) const { int count = 0; + //npcbot + for (BattlegroundBotMap::const_iterator itr = m_Bots.begin(); itr != m_Bots.end(); ++itr) + { + if (GetBotTeam(itr->first) == Team) + { + Creature const* bot = BotDataMgr::FindBot(itr->first.GetEntry()); + if (bot && bot->IsAlive() && bot->GetShapeshiftForm() != FORM_SPIRITOFREDEMPTION) + ++count; + } + } + //end npcbot for (BattlegroundPlayerMap::const_iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr) { if (itr->second.Team == Team) @@ -1783,6 +2279,13 @@ WorldSafeLocsEntry const* Battleground::GetClosestGraveyard(Player* player) return sObjectMgr->GetClosestGraveyard(player->GetPositionX(), player->GetPositionY(), player->GetPositionZ(), player->GetMapId(), player->GetTeam()); } +//npcbot +WorldSafeLocsEntry const* Battleground::GetClosestGraveyardForBot(WorldLocation const& curPos, uint32 team) const +{ + return sObjectMgr->GetClosestGraveyard(curPos.GetPositionX(), curPos.GetPositionY(), curPos.GetPositionZ(), curPos.GetMapId(), team); +} +//end npcbot + void Battleground::StartTimedAchievement(AchievementCriteriaTimedTypes type, uint32 entry) { for (BattlegroundPlayerMap::const_iterator itr = GetPlayers().begin(); itr != GetPlayers().end(); ++itr) @@ -1802,6 +2305,74 @@ void Battleground::RewardXPAtKill(Player* killer, Player* victim) killer->RewardPlayerAndGroupAtKill(victim, true); } +//npcbot +void Battleground::RewardXPAtKill(Player* killer, Creature* victim) +{ + if (sWorld->getBoolConfig(CONFIG_BG_XP_FOR_KILL) && killer && victim) + killer->RewardPlayerAndGroupAtKill(victim, true); +} + +void Battleground::RewardXPAtKill(Creature* killer, Player* victim) +{ + if (sWorld->getBoolConfig(CONFIG_BG_XP_FOR_KILL) && killer && victim) + { + Player* pkiller = killer->IsFreeBot() ? nullptr : killer->GetBotOwner(); + if (!pkiller) + { + uint32 team = (BotDataMgr::GetTeamIdForFaction(killer->GetFaction()) == TEAM_ALLIANCE) ? ALLIANCE : HORDE; + if (Group const* group = GetBgRaid(team)) + { + float mindist = SIZE_OF_GRIDS; + for (GroupReference const* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + if (Player* gPlayer = itr->GetSource()) + { + float dist = gPlayer->GetExactDist2d(victim); + if (dist < mindist) + { + mindist = dist; + pkiller = gPlayer; + } + } + } + } + } + if (pkiller && pkiller->IsAtGroupRewardDistance(victim)) + pkiller->RewardPlayerAndGroupAtKill(victim, true); + } +} + +void Battleground::RewardXPAtKill(Creature* killer, Creature* victim) +{ + if (sWorld->getBoolConfig(CONFIG_BG_XP_FOR_KILL) && killer && victim) + { + Player* pkiller = killer->IsFreeBot() ? nullptr : killer->GetBotOwner(); + if (!pkiller) + { + uint32 team = (BotDataMgr::GetTeamIdForFaction(killer->GetFaction()) == TEAM_ALLIANCE) ? ALLIANCE : HORDE; + if (Group const* group = GetBgRaid(team)) + { + float mindist = SIZE_OF_GRIDS; + for (GroupReference const* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + if (Player* gPlayer = itr->GetSource()) + { + float dist = gPlayer->GetExactDist2d(victim); + if (dist < mindist) + { + mindist = dist; + pkiller = gPlayer; + } + } + } + } + } + if (pkiller && pkiller->IsAtGroupRewardDistance(victim)) + pkiller->RewardPlayerAndGroupAtKill(victim, true); + } +} +//end npcbot + uint32 Battleground::GetTeamScore(uint32 teamId) const { if (teamId == TEAM_ALLIANCE || teamId == TEAM_HORDE) diff --git a/src/server/game/Battlegrounds/Battleground.h b/src/server/game/Battlegrounds/Battleground.h index 76c98c3f6..1d203db04 100644 --- a/src/server/game/Battlegrounds/Battleground.h +++ b/src/server/game/Battlegrounds/Battleground.h @@ -190,6 +190,13 @@ struct BattlegroundPlayer uint32 Team; // Player's team }; +//npcbot +struct BattlegroundBot +{ + uint32 Team; // bot's team +}; +//end npcbot + struct BattlegroundObjectInfo { BattlegroundObjectInfo() : object(nullptr), timer(0), spellid(0) { } @@ -335,6 +342,10 @@ class TC_GAME_API Battleground bool isRated() const { return m_IsRated; } typedef std::map BattlegroundPlayerMap; + //npcbot + typedef std::map BattlegroundBotMap; + [[nodiscard]] BattlegroundBotMap const& GetBots() const { return m_Bots; } + //end npcbot BattlegroundPlayerMap const& GetPlayers() const { return m_Players; } uint32 GetPlayersSize() const { return m_Players.size(); } @@ -429,6 +440,30 @@ class TC_GAME_API Battleground virtual void HandleKillPlayer(Player* player, Player* killer); virtual void HandleKillUnit(Creature* /*creature*/, Player* /*killer*/) { } + //npcbot + uint32 GetBotScoresSize() const { return BotScores.size(); } + void RemoveBotFromResurrectQueue(ObjectGuid guid); + uint32 GetBotTeam(ObjectGuid guid) const; + TeamId GetBotTeamId(ObjectGuid guid) const; + TeamId GetOtherTeamId(TeamId teamId) const; + void AddOrSetBotToCorrectBgGroup(Creature* bot, uint32 team); + void RewardXPAtKill(Player* killer, Creature* victim); + void RewardXPAtKill(Creature* killer, Player* victim); + void RewardXPAtKill(Creature* killer, Creature* victim); + virtual void AddBot(Creature* bot); + virtual void RemoveBotAtLeave(ObjectGuid guid); + virtual WorldSafeLocsEntry const* GetClosestGraveyardForBot(WorldLocation const& curPos, uint32 team) const; + virtual bool UpdateBotScore(Creature const* bot, uint32 type, uint32 value, bool doAddHonor = true); + virtual void RemoveBot(ObjectGuid /*guid*/) {} + virtual void HandleBotKillPlayer(Creature* killer, Player* victim); + virtual void HandleBotKillBot(Creature* killer, Creature* victim); + virtual void HandlePlayerKillBot(Creature* victim, Player* killer); + virtual void HandleBotKillUnit(Creature* /*killer*/, Creature* /*victim*/) { } + virtual void EventBotDroppedFlag(Creature* /*bot*/) { } + virtual void EventBotClickedOnFlag(Creature* /*bot*/, GameObject* /*target_obj*/) { } + virtual void HandleBotAreaTrigger(Creature* /*bot*/, uint32 /*trigger*/) { } + //end npcbot + // Battleground events virtual void EventPlayerDroppedFlag(Player* /*player*/) { } virtual void EventPlayerClickedOnFlag(Player* /*player*/, GameObject* /*target_obj*/) { } @@ -519,6 +554,10 @@ class TC_GAME_API Battleground // Scorekeeping BattlegroundScoreMap PlayerScores; // Player scores + //npcbot + BattlegroundScoreMap BotScores; + BattlegroundBotMap m_Bots; + //end npcbot // must be implemented in BG subclass virtual void RemovePlayer(Player* /*player*/, ObjectGuid /*guid*/, uint32 /*team*/) { } diff --git a/src/server/game/Battlegrounds/BattlegroundMgr.h b/src/server/game/Battlegrounds/BattlegroundMgr.h index 939d3ef87..3729833d8 100644 --- a/src/server/game/Battlegrounds/BattlegroundMgr.h +++ b/src/server/game/Battlegrounds/BattlegroundMgr.h @@ -179,6 +179,11 @@ class TC_GAME_API BattlegroundMgr typedef std::map BattlegroundMapTemplateContainer; BattlegroundTemplateMap _battlegroundTemplates; BattlegroundMapTemplateContainer _battlegroundMapTemplates; + + //npcbot +public: + BattlegroundDataContainer const& GetBgDataStore() const { return bgDataStore; } + //end npcbot }; #define sBattlegroundMgr BattlegroundMgr::instance() diff --git a/src/server/game/Battlegrounds/BattlegroundQueue.cpp b/src/server/game/Battlegrounds/BattlegroundQueue.cpp index 6ac695a46..86c3d55af 100644 --- a/src/server/game/Battlegrounds/BattlegroundQueue.cpp +++ b/src/server/game/Battlegrounds/BattlegroundQueue.cpp @@ -30,6 +30,13 @@ #include "Player.h" #include "World.h" +//npcbot +//non-PCH +#include "Creature.h" +#include "botmgr.h" +#include "botdatamgr.h" +//end npcbot + /*********************************************************/ /*** BATTLEGROUND QUEUE SYSTEM ***/ /*********************************************************/ @@ -184,6 +191,22 @@ GroupQueueInfo* BattlegroundQueue::AddGroup(Player* leader, Group* grp, Battlegr // add the pinfo to ginfo's list ginfo->Players[member->GetGUID()] = &pl_info; } + + //npcbot: queue bots (bg only) + if (!arenateamid) + { + for (GroupBotReference* itr = grp->GetFirstBotMember(); itr != nullptr; itr = itr->next()) + { + Creature const* bot = itr->GetSource(); + if (!bot) + continue; + PlayerQueueInfo& pl_info = m_QueuedPlayers[bot->GetGUID()]; + pl_info.LastOnlineTime = lastOnlineTime; + pl_info.GroupInfo = ginfo; + ginfo->Players[bot->GetGUID()] = &pl_info; + } + } + //end npcbot } else { @@ -232,8 +255,101 @@ GroupQueueInfo* BattlegroundQueue::AddGroup(Player* leader, Group* grp, Battlegr //release mutex } + //npcbot: try to queue wandering bots + if (!isRated && !ArenaType && !arenateamid && !sBattlegroundMgr->isTesting()) + { + if (!BotDataMgr::GenerateBattlegroundBots(leader, grp, this, bracketEntry, ginfo)) + { + TC_LOG_WARN("npcbots", "Did NOT generate bots for BG {} for leader {} ({} members)", + BgTypeId, leader->GetDebugInfo().c_str(), grp ? grp->GetMembersCount() : 0u); + } + } + //end npcbot + + return ginfo; +} + +//npcbot +GroupQueueInfo* BattlegroundQueue::AddBotAsGroup(ObjectGuid guid, uint32 team, BattlegroundTypeId BgTypeId, PvPDifficultyEntry const* bracketEntry, uint8 ArenaType, bool isRated, uint32 ArenaRating, uint32 MatchmakerRating, uint32 arenateamid, uint32 PreviousOpponentsArenaTeamId) +{ + ASSERT(guid.IsCreature()); + + BattlegroundBracketId bracketId = bracketEntry->GetBracketId(); + + // create new ginfo + GroupQueueInfo* ginfo = new GroupQueueInfo; + ginfo->BgTypeId = BgTypeId; + ginfo->ArenaType = ArenaType; + ginfo->ArenaTeamId = arenateamid; + ginfo->IsRated = isRated; + ginfo->IsInvitedToBGInstanceGUID = 0; + ginfo->JoinTime = GameTime::GetGameTimeMS(); + ginfo->RemoveInviteTime = 0; + ginfo->Team = team; + ginfo->ArenaTeamRating = ArenaRating; + ginfo->ArenaMatchmakerRating = MatchmakerRating; + ginfo->PreviousOpponentsTeamId = PreviousOpponentsArenaTeamId; + ginfo->OpponentsTeamRating = 0; + ginfo->OpponentsMatchmakerRating = 0; + + ginfo->Players.clear(); + + //compute index (if group is premade or joined a rated match) to queues + uint32 index = 0; + if (!isRated) + index += PVP_TEAMS_COUNT; + if (ginfo->Team == HORDE) + index++; + + TC_LOG_DEBUG("npcbots", "Adding NPCBot {} to BattlegroundQueue bgTypeId : {}, bracket_id : {}, index : {}", guid.GetEntry(), BgTypeId, bracketId, index); + + uint32 lastOnlineTime = GameTime::GetGameTimeMS(); + + //announce world (this don't need mutex) + if (isRated && sWorld->getBoolConfig(CONFIG_ARENA_QUEUE_ANNOUNCER_ENABLE)) + { + ArenaTeam* team = sArenaTeamMgr->GetArenaTeamById(arenateamid); + if (team) + sWorld->SendWorldText(LANG_ARENA_QUEUE_ANNOUNCE_WORLD_JOIN, team->GetName().c_str(), ginfo->ArenaType, ginfo->ArenaType, ginfo->ArenaTeamRating); + } + + PlayerQueueInfo& pl_info = m_QueuedPlayers[guid]; + pl_info.LastOnlineTime = lastOnlineTime; + pl_info.GroupInfo = ginfo; + ginfo->Players[guid] = &pl_info; + + m_QueuedGroups[bracketId][index].push_back(ginfo); + + //announce to world, this code needs mutex + if (!isRated && sWorld->getBoolConfig(CONFIG_BATTLEGROUND_QUEUE_ANNOUNCER_ENABLE)) + { + if (Battleground const* bg = sBattlegroundMgr->GetBattlegroundTemplate(ginfo->BgTypeId)) + { + uint32 MinPlayers = bg->GetMinPlayersPerTeam(); + uint32 qHorde = 0; + uint32 qAlliance = 0; + uint32 q_min_level = bracketEntry->MinLevel; + uint32 q_max_level = bracketEntry->MaxLevel; + GroupsQueueType::const_iterator itr; + for (itr = m_QueuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE].begin(); itr != m_QueuedGroups[bracketId][BG_QUEUE_NORMAL_ALLIANCE].end(); ++itr) + if (!(*itr)->IsInvitedToBGInstanceGUID) + qAlliance += (*itr)->Players.size(); + for (itr = m_QueuedGroups[bracketId][BG_QUEUE_NORMAL_HORDE].begin(); itr != m_QueuedGroups[bracketId][BG_QUEUE_NORMAL_HORDE].end(); ++itr) + if (!(*itr)->IsInvitedToBGInstanceGUID) + qHorde += (*itr)->Players.size(); + + // Show queue status to player only (when joining queue) + if (!sWorld->getBoolConfig(CONFIG_BATTLEGROUND_QUEUE_ANNOUNCER_PLAYERONLY)) + { + sWorld->SendWorldText(LANG_BG_QUEUE_ANNOUNCE_WORLD, bg->GetName().c_str(), q_min_level, q_max_level, + qAlliance, (MinPlayers > qAlliance) ? MinPlayers - qAlliance : (uint32)0, qHorde, (MinPlayers > qHorde) ? MinPlayers - qHorde : (uint32)0); + } + } + } + return ginfo; } +//end npcbot void BattlegroundQueue::PlayerInvitedToBGUpdateAverageWaitTime(GroupQueueInfo* ginfo, BattlegroundBracketId bracket_id) { @@ -375,6 +491,31 @@ void BattlegroundQueue::RemovePlayer(ObjectGuid guid, bool decreaseInvitedCount) } } + //npcbot: remove player's bots + if (!group->Players.empty() && guid.IsPlayer()) + { + std::vector botguids; + botguids.reserve(BotMgr::GetMaxNpcBots(DEFAULT_MAX_LEVEL) / 2); + BotDataMgr::GetNPCBotGuidsByOwner(botguids, guid); + for (std::vector::const_iterator ci = botguids.begin(); ci != botguids.end() && !group->Players.empty(); ++ci) + { + auto bqpitr = m_QueuedPlayers.find(*ci); + if (bqpitr != m_QueuedPlayers.end()) + { + auto bgpitr = group->Players.find(*ci); + if (bgpitr != group->Players.end()) + group->Players.erase(bgpitr); + + if (decreaseInvitedCount && group->IsInvitedToBGInstanceGUID) + if (Battleground* bg = sBattlegroundMgr->GetBattleground(group->IsInvitedToBGInstanceGUID, group->BgTypeId)) + bg->DecreaseInvitedCount(group->Team); + + m_QueuedPlayers.erase(bqpitr); + } + } + } + //end npcbot + // remove group queue info if needed if (group->Players.empty()) { @@ -415,6 +556,15 @@ bool BattlegroundQueue::IsPlayerInvited(ObjectGuid pl_guid, const uint32 bgInsta && qItr->second.GroupInfo->RemoveInviteTime == removeTime); } +//npcbot +bool BattlegroundQueue::IsBotInvited(ObjectGuid guid, uint32 bgInstanceGuid) const +{ + ASSERT(guid.IsCreature()); + QueuedPlayersMap::const_iterator qItr = m_QueuedPlayers.find(guid); + return (qItr != m_QueuedPlayers.end() && qItr->second.GroupInfo->IsInvitedToBGInstanceGUID == bgInstanceGuid); +} +//end npcbot + bool BattlegroundQueue::GetPlayerGroupInfoData(ObjectGuid guid, GroupQueueInfo* ginfo) { QueuedPlayersMap::const_iterator qItr = m_QueuedPlayers.find(guid); @@ -453,6 +603,15 @@ bool BattlegroundQueue::InviteGroupToBG(GroupQueueInfo* ginfo, Battleground* bg, // loop through the players for (std::map::iterator itr = ginfo->Players.begin(); itr != ginfo->Players.end(); ++itr) { + //npcbot: invite bots + if (itr->first.IsCreature()) + { + PlayerInvitedToBGUpdateAverageWaitTime(ginfo, bracket_id); + BotMgr::InviteBotToBG(itr->first, ginfo, bg); + continue; + } + //end npcbot + // get the player Player* player = ObjectAccessor::FindConnectedPlayer(itr->first); // if offline, skip him, this should not happen - player is removed from queue when he logs out diff --git a/src/server/game/Battlegrounds/BattlegroundQueue.h b/src/server/game/Battlegrounds/BattlegroundQueue.h index b7cad6f5d..4cbaf72d4 100644 --- a/src/server/game/Battlegrounds/BattlegroundQueue.h +++ b/src/server/game/Battlegrounds/BattlegroundQueue.h @@ -86,6 +86,10 @@ class TC_GAME_API BattlegroundQueue bool CheckNormalMatch(Battleground* bg_template, BattlegroundBracketId bracket_id, uint32 minPlayers, uint32 maxPlayers); bool CheckSkirmishForSameFaction(BattlegroundBracketId bracket_id, uint32 minPlayersPerTeam); GroupQueueInfo* AddGroup(Player* leader, Group* group, BattlegroundTypeId bgTypeId, PvPDifficultyEntry const* bracketEntry, uint8 ArenaType, bool isRated, bool isPremade, uint32 ArenaRating, uint32 MatchmakerRating, uint32 ArenaTeamId = 0, uint32 OpponentsArenaTeamId = 0); + //npcbot + GroupQueueInfo* AddBotAsGroup(ObjectGuid leaderGuid, uint32 team, BattlegroundTypeId bgTypeId, PvPDifficultyEntry const* bracketEntry, uint8 ArenaType, bool isPremade, uint32 ArenaRating, uint32 MatchmakerRating, uint32 ArenaTeamId = 0, uint32 OpponentsArenaTeamId = 0); + bool IsBotInvited(ObjectGuid guid, uint32 bgInstanceGuid) const; + //end npcbot void RemovePlayer(ObjectGuid guid, bool decreaseInvitedCount); bool IsPlayerInvited(ObjectGuid pl_guid, const uint32 bgInstanceGuid, const uint32 removeTime); bool GetPlayerGroupInfoData(ObjectGuid guid, GroupQueueInfo* ginfo); diff --git a/src/server/game/Battlegrounds/Zones/BattlegroundAB.cpp b/src/server/game/Battlegrounds/Zones/BattlegroundAB.cpp index 2f0320c55..64dc66867 100644 --- a/src/server/game/Battlegrounds/Zones/BattlegroundAB.cpp +++ b/src/server/game/Battlegrounds/Zones/BattlegroundAB.cpp @@ -29,6 +29,11 @@ #include "WorldSession.h" #include "WorldStatePackets.h" +//npcbot +#include "botdatamgr.h" +#include "botmgr.h" +//end npcbot + void BattlegroundABScore::BuildObjectivesBlock(WorldPacket& data) { data << uint32(2); @@ -236,6 +241,16 @@ void BattlegroundAB::AddPlayer(Player* player) PlayerScores[player->GetGUID().GetCounter()] = new BattlegroundABScore(player->GetGUID()); } +//npcbot +void BattlegroundAB::AddBot(Creature* bot) +{ + bool const isInBattleground = IsPlayerInBattleground(bot->GetGUID()); + Battleground::AddBot(bot); + if (!isInBattleground) + BotScores[bot->GetEntry()] = new BattlegroundABScore(bot->GetGUID()); +} +//end npcbot + void BattlegroundAB::RemovePlayer(Player* /*player*/, ObjectGuid /*guid*/, uint32 /*team*/) { } @@ -539,6 +554,165 @@ void BattlegroundAB::EventPlayerClickedOnFlag(Player* source, GameObject* /*targ PlaySoundToAll(sound); } +//npcbot +void BattlegroundAB::EventBotClickedOnFlag(Creature* bot, GameObject* /*target_obj*/) +{ + if (GetStatus() != STATUS_IN_PROGRESS) + return; + + uint8 node = BG_AB_NODE_STABLES; + GameObject* obj = GetBgMap()->GetGameObject(BgObjects[node*8+7]); + while ((node < BG_AB_DYNAMIC_NODES_COUNT) && ((!obj) || (!bot->IsWithinDistInMap(obj, 10)))) + { + ++node; + obj = GetBgMap()->GetGameObject(BgObjects[node*8+BG_AB_OBJECT_AURA_CONTESTED]); + } + + if (node == BG_AB_DYNAMIC_NODES_COUNT) + { + // this means our player isn't close to any of banners - maybe cheater ?? + return; + } + + TeamId teamIndex = GetBotTeamId(bot->GetGUID()); + + // Check if player really could use this banner, not cheated + if (!(m_Nodes[node] == 0 || teamIndex == m_Nodes[node]%2)) + return; + + bot->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_ENTER_PVP_COMBAT); + uint32 sound = 0; + // If node is neutral, change to contested + if (m_Nodes[node] == BG_AB_NODE_TYPE_NEUTRAL) + { + UpdateBotScore(bot, SCORE_BASES_ASSAULTED, 1); + m_prevNodes[node] = m_Nodes[node]; + m_Nodes[node] = teamIndex + 1; + // burn current neutral banner + _DelBanner(node, BG_AB_NODE_TYPE_NEUTRAL, 0); + // create new contested banner + _CreateBanner(node, BG_AB_NODE_TYPE_CONTESTED, teamIndex, true); + _SendNodeUpdate(node); + m_NodeTimers[node] = BG_AB_FLAG_CAPTURING_TIME; + + if (teamIndex == TEAM_ALLIANCE) + SendBroadcastText(ABNodes[node].TextAllianceClaims, CHAT_MSG_BG_SYSTEM_ALLIANCE, bot); + else + SendBroadcastText(ABNodes[node].TextHordeClaims, CHAT_MSG_BG_SYSTEM_HORDE, bot); + + sound = BG_AB_SOUND_NODE_CLAIMED; + } + // If node is contested + else if ((m_Nodes[node] == BG_AB_NODE_STATUS_ALLY_CONTESTED) || (m_Nodes[node] == BG_AB_NODE_STATUS_HORDE_CONTESTED)) + { + // If last state is NOT occupied, change node to enemy-contested + if (m_prevNodes[node] < BG_AB_NODE_TYPE_OCCUPIED) + { + UpdateBotScore(bot, SCORE_BASES_ASSAULTED, 1); + m_prevNodes[node] = m_Nodes[node]; + m_Nodes[node] = uint8(teamIndex) + BG_AB_NODE_TYPE_CONTESTED; + // burn current contested banner + _DelBanner(node, BG_AB_NODE_TYPE_CONTESTED, !teamIndex); + // create new contested banner + _CreateBanner(node, BG_AB_NODE_TYPE_CONTESTED, teamIndex, true); + _SendNodeUpdate(node); + m_NodeTimers[node] = BG_AB_FLAG_CAPTURING_TIME; + + if (teamIndex == TEAM_ALLIANCE) + SendBroadcastText(ABNodes[node].TextAllianceAssaulted, CHAT_MSG_BG_SYSTEM_ALLIANCE, bot); + else + SendBroadcastText(ABNodes[node].TextHordeAssaulted, CHAT_MSG_BG_SYSTEM_HORDE, bot); + } + // If contested, change back to occupied + else + { + UpdateBotScore(bot, SCORE_BASES_DEFENDED, 1); + m_prevNodes[node] = m_Nodes[node]; + m_Nodes[node] = uint8(teamIndex) + BG_AB_NODE_TYPE_OCCUPIED; + // burn current contested banner + _DelBanner(node, BG_AB_NODE_TYPE_CONTESTED, !teamIndex); + // create new occupied banner + _CreateBanner(node, BG_AB_NODE_TYPE_OCCUPIED, teamIndex, true); + _SendNodeUpdate(node); + m_NodeTimers[node] = 0; + _NodeOccupied(node, (teamIndex == TEAM_ALLIANCE) ? ALLIANCE : HORDE); + + if (teamIndex == TEAM_ALLIANCE) + SendBroadcastText(ABNodes[node].TextAllianceDefended, CHAT_MSG_BG_SYSTEM_ALLIANCE, bot); + else + SendBroadcastText(ABNodes[node].TextHordeDefended, CHAT_MSG_BG_SYSTEM_HORDE, bot); + } + sound = (teamIndex == TEAM_ALLIANCE) ? BG_AB_SOUND_NODE_ASSAULTED_ALLIANCE : BG_AB_SOUND_NODE_ASSAULTED_HORDE; + } + // If node is occupied, change to enemy-contested + else + { + UpdateBotScore(bot, SCORE_BASES_ASSAULTED, 1); + m_prevNodes[node] = m_Nodes[node]; + m_Nodes[node] = uint8(teamIndex) + BG_AB_NODE_TYPE_CONTESTED; + // burn current occupied banner + _DelBanner(node, BG_AB_NODE_TYPE_OCCUPIED, !teamIndex); + // create new contested banner + _CreateBanner(node, BG_AB_NODE_TYPE_CONTESTED, teamIndex, true); + _SendNodeUpdate(node); + _NodeDeOccupied(node); + m_NodeTimers[node] = BG_AB_FLAG_CAPTURING_TIME; + + if (teamIndex == TEAM_ALLIANCE) + SendBroadcastText(ABNodes[node].TextAllianceAssaulted, CHAT_MSG_BG_SYSTEM_ALLIANCE, bot); + else + SendBroadcastText(ABNodes[node].TextHordeAssaulted, CHAT_MSG_BG_SYSTEM_HORDE, bot); + + sound = (teamIndex == TEAM_ALLIANCE) ? BG_AB_SOUND_NODE_ASSAULTED_ALLIANCE : BG_AB_SOUND_NODE_ASSAULTED_HORDE; + } + + // If node is occupied again, send "X has taken the Y" msg. + if (m_Nodes[node] >= BG_AB_NODE_TYPE_OCCUPIED) + { + if (teamIndex == TEAM_ALLIANCE) + SendBroadcastText(ABNodes[node].TextAllianceTaken, CHAT_MSG_BG_SYSTEM_ALLIANCE); + else + SendBroadcastText(ABNodes[node].TextHordeTaken, CHAT_MSG_BG_SYSTEM_HORDE); + } + PlaySoundToAll(sound); +} + +bool BattlegroundAB::IsNodeOccupied(uint8 node, TeamId teamId) const +{ + if (node < BG_AB_DYNAMIC_NODES_COUNT) + { + switch (teamId) + { + case TEAM_ALLIANCE: + return m_Nodes[node] == BG_AB_NODE_STATUS_ALLY_OCCUPIED; + case TEAM_HORDE: + return m_Nodes[node] == BG_AB_NODE_STATUS_HORDE_OCCUPIED; + default: + break; + } + } + + return false; +} +bool BattlegroundAB::IsNodeContested(uint8 node, TeamId teamId) const +{ + if (node < BG_AB_DYNAMIC_NODES_COUNT) + { + switch (teamId) + { + case TEAM_ALLIANCE: + return m_Nodes[node] == BG_AB_NODE_STATUS_ALLY_CONTESTED; + case TEAM_HORDE: + return m_Nodes[node] == BG_AB_NODE_STATUS_HORDE_CONTESTED; + default: + break; + } + } + + return false; +} +//end npcbot + uint32 BattlegroundAB::GetPrematureWinner() { // How many bases each team owns @@ -681,6 +855,83 @@ WorldSafeLocsEntry const* BattlegroundAB::GetClosestGraveyard(Player* player) return good_entry; } +//npcbot +WorldSafeLocsEntry const* BattlegroundAB::GetClosestGraveyardForBot(WorldLocation const& curPos, uint32 team) const +{ + TeamId teamIndex = GetTeamIndexByTeamId(team); + + // Is there any occupied node for this team? + std::vector nodes; + for (uint8 i = 0; i < BG_AB_DYNAMIC_NODES_COUNT; ++i) + if (m_Nodes[i] == teamIndex + 3) + nodes.push_back(i); + + WorldSafeLocsEntry const* good_entry = nullptr; + // If so, select the closest node to place ghost on + if (!nodes.empty()) + { + float plr_x = curPos.GetPositionX(); + float plr_y = curPos.GetPositionY(); + + float mindist = 999999.0f; + for (uint8 i = 0; i < nodes.size(); ++i) + { + WorldSafeLocsEntry const* entry = sWorldSafeLocsStore.LookupEntry(BG_AB_GraveyardIds[nodes[i]]); + if (!entry) + continue; + float dist = (entry->Loc.X - plr_x)*(entry->Loc.X - plr_x)+(entry->Loc.Y - plr_y)*(entry->Loc.Y - plr_y); + if (mindist > dist) + { + mindist = dist; + good_entry = entry; + } + } + nodes.clear(); + } + // If not, place ghost on starting location + if (!good_entry) + good_entry = sWorldSafeLocsStore.LookupEntry(BG_AB_GraveyardIds[teamIndex+5]); + + return good_entry; +} + +void BattlegroundAB::RewardKillScore(TeamId teamId, uint32 amount) +{ + // Score feature + m_TeamScores[teamId] += amount; + if (m_TeamScores[teamId] > BG_AB_MAX_TEAM_SCORE) + m_TeamScores[teamId] = BG_AB_MAX_TEAM_SCORE; + UpdateWorldState(teamId == TEAM_ALLIANCE ? BG_AB_OP_RESOURCES_ALLY : BG_AB_OP_RESOURCES_HORDE, m_TeamScores[teamId]); + if (m_TeamScores[teamId] >= BG_AB_MAX_TEAM_SCORE) + EndBattleground(teamId); +} + +void BattlegroundAB::HandleBotKillPlayer(Creature* killer, Player* victim) +{ + if (GetStatus() != STATUS_IN_PROGRESS) + return; + + Battleground::HandleBotKillPlayer(killer, victim); + //RewardKillScore(GetBotTeamId(killer->GetGUID()), BG_AB_TickPoints[1]); +} +void BattlegroundAB::HandleBotKillBot(Creature* killer, Creature* victim) +{ + if (GetStatus() != STATUS_IN_PROGRESS) + return; + + Battleground::HandleBotKillBot(killer, victim); + //RewardKillScore(GetBotTeamId(killer->GetGUID()), BG_AB_TickPoints[1]); +} +void BattlegroundAB::HandlePlayerKillBot(Creature* victim, Player* killer) +{ + if (GetStatus() != STATUS_IN_PROGRESS) + return; + + Battleground::HandlePlayerKillBot(victim, killer); + //RewardKillScore(GetBotTeamId(killer->GetGUID()), BG_AB_TickPoints[1]); +} +//end npcbot + bool BattlegroundAB::UpdatePlayerScore(Player* player, uint32 type, uint32 value, bool doAddHonor) { if (!Battleground::UpdatePlayerScore(player, type, value, doAddHonor)) diff --git a/src/server/game/Battlegrounds/Zones/BattlegroundAB.h b/src/server/game/Battlegrounds/Zones/BattlegroundAB.h index c30958d9c..edf9f12e8 100644 --- a/src/server/game/Battlegrounds/Zones/BattlegroundAB.h +++ b/src/server/game/Battlegrounds/Zones/BattlegroundAB.h @@ -313,6 +313,18 @@ class BattlegroundAB : public Battleground void EndBattleground(uint32 winner) override; WorldSafeLocsEntry const* GetClosestGraveyard(Player* player) override; + //npcbot + WorldSafeLocsEntry const* GetClosestGraveyardForBot(WorldLocation const& curPos, uint32 team) const override; + void AddBot(Creature* bot) override; + void RewardKillScore(TeamId teamId, uint32 amount); + void HandleBotKillPlayer(Creature* killer, Player* victim) override; + void HandleBotKillBot(Creature* killer, Creature* victim) override; + void HandlePlayerKillBot(Creature* victim, Player* killer) override; + void EventBotClickedOnFlag(Creature* bot, GameObject* target_obj) override; + bool IsNodeOccupied(uint8 node, TeamId teamId) const; + bool IsNodeContested(uint8 node, TeamId teamId) const; + //end npcbot + /* Scorekeeping */ bool UpdatePlayerScore(Player* player, uint32 type, uint32 value, bool doAddHonor = true) override; diff --git a/src/server/game/Battlegrounds/Zones/BattlegroundAV.cpp b/src/server/game/Battlegrounds/Zones/BattlegroundAV.cpp index 8d258d340..735893776 100644 --- a/src/server/game/Battlegrounds/Zones/BattlegroundAV.cpp +++ b/src/server/game/Battlegrounds/Zones/BattlegroundAV.cpp @@ -27,6 +27,11 @@ #include "WorldSession.h" #include "WorldStatePackets.h" +//npcbot +#include "botdatamgr.h" +#include "botmgr.h" +//end npcbot + void BattlegroundAVScore::BuildObjectivesBlock(WorldPacket& data) { data << uint32(5); // Objectives Count @@ -76,6 +81,33 @@ void BattlegroundAV::HandleKillPlayer(Player* player, Player* killer) UpdateScore(player->GetTeam(), -1); } +//npcbot +void BattlegroundAV::HandleBotKillPlayer(Creature* killer, Player* victim) +{ + if (GetStatus() != STATUS_IN_PROGRESS) + return; + + Battleground::HandleBotKillPlayer(killer, victim); + UpdateScore(victim->GetTeam(), -1); +} +void BattlegroundAV::HandleBotKillBot(Creature* killer, Creature* victim) +{ + if (GetStatus() != STATUS_IN_PROGRESS) + return; + + Battleground::HandleBotKillBot(killer, victim); + UpdateScore(GetBotTeam(victim->GetGUID()), -1); +} +void BattlegroundAV::HandlePlayerKillBot(Creature* victim, Player* killer) +{ + if (GetStatus() != STATUS_IN_PROGRESS) + return; + + Battleground::HandlePlayerKillBot(victim, killer); + UpdateScore(GetBotTeam(victim->GetGUID()), -1); +} +//end npcbot + void BattlegroundAV::HandleKillUnit(Creature* unit, Player* killer) { TC_LOG_DEBUG("bg.battleground", "bg_av HandleKillUnit {}", unit->GetEntry()); @@ -153,6 +185,75 @@ void BattlegroundAV::HandleKillUnit(Creature* unit, Player* killer) ChangeMineOwner(AV_SOUTH_MINE, killer->GetTeam()); } +//npcbot +void BattlegroundAV::HandleBotKillUnit(Creature* killer, Creature* victim) +{ + TC_LOG_DEBUG("bg.battleground", "bg_av HandleBotKillUnit {}", victim->GetEntry()); + if (GetStatus() != STATUS_IN_PROGRESS) + return; + uint32 entry = victim->GetEntry(); + + if (entry == BG_AV_CreatureInfo[AV_NPC_A_BOSS]) + { + CastSpellOnTeam(23658, HORDE); //this is a spell which finishes a quest where a player has to kill the boss + RewardReputationToTeam(729, BG_AV_REP_BOSS, HORDE); + RewardHonorToTeam(GetBonusHonorFromKill(BG_AV_KILL_BOSS), HORDE); + EndBattleground(HORDE); + DelCreature(AV_CPLACE_TRIGGER17); + } + else if (entry == BG_AV_CreatureInfo[AV_NPC_H_BOSS]) + { + CastSpellOnTeam(23658, ALLIANCE); //this is a spell which finishes a quest where a player has to kill the boss + RewardReputationToTeam(730, BG_AV_REP_BOSS, ALLIANCE); + RewardHonorToTeam(GetBonusHonorFromKill(BG_AV_KILL_BOSS), ALLIANCE); + EndBattleground(ALLIANCE); + DelCreature(AV_CPLACE_TRIGGER19); + } + else if (entry == BG_AV_CreatureInfo[AV_NPC_A_CAPTAIN]) + { + if (!m_CaptainAlive[0]) + { + TC_LOG_ERROR("bg.battleground", "Killed a Captain twice, please report this bug, if you haven't done \".respawn\""); + return; + } + m_CaptainAlive[0]=false; + RewardReputationToTeam(729, BG_AV_REP_CAPTAIN, HORDE); + RewardHonorToTeam(GetBonusHonorFromKill(BG_AV_KILL_CAPTAIN), HORDE); + UpdateScore(ALLIANCE, (-1)*BG_AV_RES_CAPTAIN); + //spawn destroyed aura + for (uint8 i=0; i <= 9; i++) + SpawnBGObject(BG_AV_OBJECT_BURN_BUILDING_ALLIANCE+i, RESPAWN_IMMEDIATELY); + DelCreature(AV_CPLACE_TRIGGER16); + + if (Creature* herold = GetBGCreature(AV_CPLACE_HERALD)) + herold->AI()->Talk(TEXT_STORMPIKE_GENERAL_DEAD); + } + else if (entry == BG_AV_CreatureInfo[AV_NPC_H_CAPTAIN]) + { + if (!m_CaptainAlive[1]) + { + TC_LOG_ERROR("bg.battleground", "Killed a Captain twice, please report this bug, if you haven't done \".respawn\""); + return; + } + m_CaptainAlive[1]=false; + RewardReputationToTeam(730, BG_AV_REP_CAPTAIN, ALLIANCE); + RewardHonorToTeam(GetBonusHonorFromKill(BG_AV_KILL_CAPTAIN), ALLIANCE); + UpdateScore(HORDE, (-1)*BG_AV_RES_CAPTAIN); + //spawn destroyed aura + for (uint8 i=0; i <= 9; i++) + SpawnBGObject(BG_AV_OBJECT_BURN_BUILDING_HORDE+i, RESPAWN_IMMEDIATELY); + DelCreature(AV_CPLACE_TRIGGER18); + + if (Creature* herold = GetBGCreature(AV_CPLACE_HERALD)) + herold->AI()->Talk(TEXT_FROSTWOLF_GENERAL_DEAD); + } + else if (entry == BG_AV_CreatureInfo[AV_NPC_N_MINE_N_4] || entry == BG_AV_CreatureInfo[AV_NPC_N_MINE_A_4] || entry == BG_AV_CreatureInfo[AV_NPC_N_MINE_H_4]) + ChangeMineOwner(AV_NORTH_MINE, GetBotTeam(killer->GetGUID())); + else if (entry == BG_AV_CreatureInfo[AV_NPC_S_MINE_N_4] || entry == BG_AV_CreatureInfo[AV_NPC_S_MINE_A_4] || entry == BG_AV_CreatureInfo[AV_NPC_S_MINE_H_4]) + ChangeMineOwner(AV_SOUTH_MINE, GetBotTeam(killer->GetGUID())); +} +//end npcbot + void BattlegroundAV::HandleQuestComplete(uint32 questid, Player* player) { if (GetStatus() != STATUS_IN_PROGRESS) @@ -451,6 +552,16 @@ void BattlegroundAV::AddPlayer(Player* player) PlayerScores[player->GetGUID().GetCounter()] = new BattlegroundAVScore(player->GetGUID()); } +//npcbot +void BattlegroundAV::AddBot(Creature* bot) +{ + bool const isInBattleground = IsPlayerInBattleground(bot->GetGUID()); + Battleground::AddBot(bot); + if (!isInBattleground) + BotScores[bot->GetEntry()] = new BattlegroundAVScore(bot->GetGUID()); +} +//end npcbot + void BattlegroundAV::EndBattleground(uint32 winner) { //calculate bonuskills for both teams: @@ -504,6 +615,18 @@ void BattlegroundAV::RemovePlayer(Player* player, ObjectGuid /*guid*/, uint32 /* player->RemoveAurasDueToSpell(AV_BUFF_H_CAPTAIN); } +//npcbot +void BattlegroundAV::RemoveBot(ObjectGuid guid) +{ + if (Creature const* bot = BotDataMgr::FindBot(guid.GetEntry())) + { + const_cast(bot)->RemoveAurasDueToSpell(AV_BUFF_ARMOR); + const_cast(bot)->RemoveAurasDueToSpell(AV_BUFF_A_CAPTAIN); + const_cast(bot)->RemoveAurasDueToSpell(AV_BUFF_H_CAPTAIN); + } +} +//end npcbot + void BattlegroundAV::HandleAreaTrigger(Player* player, uint32 trigger) { if (GetStatus() != STATUS_IN_PROGRESS) @@ -803,8 +926,18 @@ BG_AV_Nodes BattlegroundAV::GetNodeThroughObject(uint32 object) return BG_AV_Nodes(0); } +//npcbot +/* +//end npcbot uint32 BattlegroundAV::GetObjectThroughNode(BG_AV_Nodes node) +//npcbot +*/ +uint32 BattlegroundAV::GetObjectThroughNode(BG_AV_Nodes node, bool log) const +//end npcbot { //this function is the counterpart to GetNodeThroughObject() + //npcbot + if (log) + //end npcbot TC_LOG_DEBUG("bg.battleground", "bg_AV GetObjectThroughNode {}", node); if (m_Nodes[node].Owner == ALLIANCE) { @@ -871,6 +1004,36 @@ void BattlegroundAV::EventPlayerClickedOnFlag(Player* source, GameObject* target } } +//npcbot +void BattlegroundAV::EventBotClickedOnFlag(Creature* bot, GameObject* target_obj) +{ + if (GetStatus() != STATUS_IN_PROGRESS) + return; + int32 object = GetObjectType(target_obj->GetGUID()); + TC_LOG_DEBUG("bg.battleground", "BG_AV bot is using gameobject {} with type {}", target_obj->GetEntry(), object); + if (object < 0) + return; + switch (target_obj->GetEntry()) + { + case BG_AV_OBJECTID_BANNER_A: + case BG_AV_OBJECTID_BANNER_A_B: + case BG_AV_OBJECTID_BANNER_H: + case BG_AV_OBJECTID_BANNER_H_B: + case BG_AV_OBJECTID_BANNER_SNOWFALL_N: + EventBotAssaultsPoint(bot, object); + break; + case BG_AV_OBJECTID_BANNER_CONT_A: + case BG_AV_OBJECTID_BANNER_CONT_A_B: + case BG_AV_OBJECTID_BANNER_CONT_H: + case BG_AV_OBJECTID_BANNER_CONT_H_B: + EventBotDefendsPoint(bot, object); + break; + default: + break; + } +} +//end npcbot + void BattlegroundAV::EventPlayerDefendsPoint(Player* player, uint32 object) { ASSERT(GetStatus() == STATUS_IN_PROGRESS); @@ -937,6 +1100,72 @@ void BattlegroundAV::EventPlayerDefendsPoint(Player* player, uint32 object) UpdatePlayerScore(player, IsTower(node) ? SCORE_TOWERS_DEFENDED : SCORE_GRAVEYARDS_DEFENDED, 1); } +void BattlegroundAV::EventBotDefendsPoint(Creature* bot, uint32 object) +{ + ASSERT(GetStatus() == STATUS_IN_PROGRESS); + BG_AV_Nodes node = GetNodeThroughObject(object); + + uint32 owner = m_Nodes[node].Owner; //maybe should name it prevowner + uint32 team = GetBotTeam(bot->GetGUID()); + + if (owner == team || m_Nodes[node].State != POINT_ASSAULTED) + return; + if (m_Nodes[node].TotalOwner == AV_NEUTRAL_TEAM) + { //until snowfall doesn't belong to anyone it is better handled in assault-code + ASSERT(node == BG_AV_NODES_SNOWFALL_GRAVE); //currently the only neutral grave + EventBotAssaultsPoint(bot, object); + return; + } + TC_LOG_DEBUG("bg.battleground", "player defends point object: {} node: {}", object, node); + if (m_Nodes[node].PrevOwner != team) + { + TC_LOG_ERROR("bg.battleground", "BG_AV: player defends point which doesn't belong to his team {}", node); + return; + } + + //spawn new go :) + if (m_Nodes[node].Owner == ALLIANCE) + SpawnBGObject(object+22, RESPAWN_IMMEDIATELY); //spawn horde banner + else + SpawnBGObject(object-22, RESPAWN_IMMEDIATELY); //spawn alliance banner + + if (!IsTower(node)) + { + SpawnBGObject(BG_AV_OBJECT_AURA_N_FIRSTAID_STATION + 3 * node, RESPAWN_ONE_DAY); + SpawnBGObject(BG_AV_OBJECT_AURA_A_FIRSTAID_STATION + uint32(GetTeamIndexByTeamId(team)) + 3 * node, RESPAWN_IMMEDIATELY); + } + // despawn old go + SpawnBGObject(object, RESPAWN_ONE_DAY); + + DefendNode(node, team); + PopulateNode(node); + UpdateNodeWorldState(node); + + if (IsTower(node)) + { + //spawn big flag+aura on top of tower + SpawnBGObject(BG_AV_OBJECT_TAURA_A_DUNBALDAR_SOUTH+(2*(node-BG_AV_NODES_DUNBALDAR_SOUTH)), (team == ALLIANCE)? RESPAWN_IMMEDIATELY : RESPAWN_ONE_DAY); + SpawnBGObject(BG_AV_OBJECT_TAURA_H_DUNBALDAR_SOUTH+(2*(node-BG_AV_NODES_DUNBALDAR_SOUTH)), (team == HORDE)? RESPAWN_IMMEDIATELY : RESPAWN_ONE_DAY); + SpawnBGObject(BG_AV_OBJECT_TFLAG_A_DUNBALDAR_SOUTH+(2*(node-BG_AV_NODES_DUNBALDAR_SOUTH)), (team == ALLIANCE)? RESPAWN_IMMEDIATELY : RESPAWN_ONE_DAY); + SpawnBGObject(BG_AV_OBJECT_TFLAG_H_DUNBALDAR_SOUTH+(2*(node-BG_AV_NODES_DUNBALDAR_SOUTH)), (team == HORDE)? RESPAWN_IMMEDIATELY : RESPAWN_ONE_DAY); + } + else if (node == BG_AV_NODES_SNOWFALL_GRAVE) //snowfall eyecandy + { + for (uint8 i = 0; i < 4; i++) + { + SpawnBGObject(((owner == ALLIANCE)?BG_AV_OBJECT_SNOW_EYECANDY_PA : BG_AV_OBJECT_SNOW_EYECANDY_PH)+i, RESPAWN_ONE_DAY); + SpawnBGObject(((team == ALLIANCE)?BG_AV_OBJECT_SNOW_EYECANDY_A : BG_AV_OBJECT_SNOW_EYECANDY_H)+i, RESPAWN_IMMEDIATELY); + } + } + + if (StaticNodeInfo const* nodeInfo = GetStaticNodeInfo(node)) + if (Creature* herold = GetBGCreature(AV_CPLACE_HERALD)) + herold->AI()->Talk(team == ALLIANCE ? nodeInfo->TextIds.AllianceCapture : nodeInfo->TextIds.HordeCapture); + + // update the statistic for the defending player + UpdateBotScore(bot, IsTower(node) ? SCORE_TOWERS_DEFENDED : SCORE_GRAVEYARDS_DEFENDED, 1); +} + void BattlegroundAV::EventPlayerAssaultsPoint(Player* player, uint32 object) { ASSERT(GetStatus() == STATUS_IN_PROGRESS); @@ -1028,6 +1257,97 @@ void BattlegroundAV::EventPlayerAssaultsPoint(Player* player, uint32 object) UpdatePlayerScore(player, (IsTower(node)) ? SCORE_TOWERS_ASSAULTED : SCORE_GRAVEYARDS_ASSAULTED, 1); } +void BattlegroundAV::EventBotAssaultsPoint(Creature* bot, uint32 object) +{ + ASSERT(GetStatus() == STATUS_IN_PROGRESS); + + BG_AV_Nodes node = GetNodeThroughObject(object); + uint32 owner = m_Nodes[node].Owner; //maybe name it prevowner + uint32 team = GetBotTeam(bot->GetGUID()); + TC_LOG_DEBUG("bg.battleground", "bg_av: player assaults point object {} node {}", object, node); + if (owner == team || team == m_Nodes[node].TotalOwner) + return; //surely a gm used this object + + if (node == BG_AV_NODES_SNOWFALL_GRAVE) //snowfall is a bit special in capping + it gets eyecandy stuff + { + if (object == BG_AV_OBJECT_FLAG_N_SNOWFALL_GRAVE) //initial capping + { + if (!(owner == AV_NEUTRAL_TEAM && m_Nodes[node].TotalOwner == AV_NEUTRAL_TEAM)) + return; + + if (team == ALLIANCE) + SpawnBGObject(BG_AV_OBJECT_FLAG_C_A_SNOWFALL_GRAVE, RESPAWN_IMMEDIATELY); + else + SpawnBGObject(BG_AV_OBJECT_FLAG_C_H_SNOWFALL_GRAVE, RESPAWN_IMMEDIATELY); + SpawnBGObject(BG_AV_OBJECT_AURA_N_FIRSTAID_STATION+3*node, RESPAWN_IMMEDIATELY); //neutral aura spawn + } + else if (m_Nodes[node].TotalOwner == AV_NEUTRAL_TEAM) //recapping, when no team owns this node realy + { + if (!(m_Nodes[node].State != POINT_CONTROLED)) + return; + + if (team == ALLIANCE) + SpawnBGObject(object-11, RESPAWN_IMMEDIATELY); + else + SpawnBGObject(object+11, RESPAWN_IMMEDIATELY); + } + //eyecandy + uint32 spawn, despawn; + if (team == ALLIANCE) + { + despawn = (m_Nodes[node].State == POINT_ASSAULTED)?BG_AV_OBJECT_SNOW_EYECANDY_PH : BG_AV_OBJECT_SNOW_EYECANDY_H; + spawn = BG_AV_OBJECT_SNOW_EYECANDY_PA; + } + else + { + despawn = (m_Nodes[node].State == POINT_ASSAULTED)?BG_AV_OBJECT_SNOW_EYECANDY_PA : BG_AV_OBJECT_SNOW_EYECANDY_A; + spawn = BG_AV_OBJECT_SNOW_EYECANDY_PH; + } + for (uint8 i = 0; i < 4; i++) + { + SpawnBGObject(despawn+i, RESPAWN_ONE_DAY); + SpawnBGObject(spawn+i, RESPAWN_IMMEDIATELY); + } + } + + //if snowfall gots capped it can be handled like all other graveyards + if (m_Nodes[node].TotalOwner != AV_NEUTRAL_TEAM) + { + ASSERT(m_Nodes[node].Owner != AV_NEUTRAL_TEAM); + if (team == ALLIANCE) + SpawnBGObject(object-22, RESPAWN_IMMEDIATELY); + else + SpawnBGObject(object+22, RESPAWN_IMMEDIATELY); + if (IsTower(node)) + { //spawning/despawning of bigflag+aura + SpawnBGObject(BG_AV_OBJECT_TAURA_A_DUNBALDAR_SOUTH+(2*(node-BG_AV_NODES_DUNBALDAR_SOUTH)), (team == ALLIANCE)? RESPAWN_IMMEDIATELY : RESPAWN_ONE_DAY); + SpawnBGObject(BG_AV_OBJECT_TAURA_H_DUNBALDAR_SOUTH+(2*(node-BG_AV_NODES_DUNBALDAR_SOUTH)), (team == HORDE)? RESPAWN_IMMEDIATELY : RESPAWN_ONE_DAY); + SpawnBGObject(BG_AV_OBJECT_TFLAG_A_DUNBALDAR_SOUTH+(2*(node-BG_AV_NODES_DUNBALDAR_SOUTH)), (team == ALLIANCE)? RESPAWN_IMMEDIATELY : RESPAWN_ONE_DAY); + SpawnBGObject(BG_AV_OBJECT_TFLAG_H_DUNBALDAR_SOUTH+(2*(node-BG_AV_NODES_DUNBALDAR_SOUTH)), (team == HORDE)? RESPAWN_IMMEDIATELY : RESPAWN_ONE_DAY); + } + else + { + //spawning/despawning of aura + SpawnBGObject(BG_AV_OBJECT_AURA_N_FIRSTAID_STATION + 3 * node, RESPAWN_IMMEDIATELY); //neutral aura spawn + SpawnBGObject(BG_AV_OBJECT_AURA_A_FIRSTAID_STATION + uint32(GetTeamIndexByTeamId(owner)) + 3 * node, RESPAWN_ONE_DAY); //teeamaura despawn + + RelocateDeadPlayers(BgCreatures[node]); + } + DePopulateNode(node); + } + + SpawnBGObject(object, RESPAWN_ONE_DAY); //delete old banner + AssaultNode(node, team); + UpdateNodeWorldState(node); + + if (StaticNodeInfo const* nodeInfo = GetStaticNodeInfo(node)) + if (Creature* herold = GetBGCreature(AV_CPLACE_HERALD)) + herold->AI()->Talk(team == ALLIANCE ? nodeInfo->TextIds.AllianceAttack : nodeInfo->TextIds.HordeAttack); + + // update the statistic for the assaulting player + UpdateBotScore(bot, (IsTower(node)) ? SCORE_TOWERS_ASSAULTED : SCORE_GRAVEYARDS_ASSAULTED, 1); +} + void BattlegroundAV::FillInitialWorldStates(WorldPackets::WorldState::InitWorldStates& packet) { for (uint8 itr = BG_AV_NODES_FIRSTAID_STATION; itr < BG_AV_NODES_MAX; ++itr) @@ -1134,6 +1454,41 @@ WorldSafeLocsEntry const* BattlegroundAV::GetClosestGraveyard(Player* player) return pGraveyard; } +//npcbot +WorldSafeLocsEntry const* BattlegroundAV::GetClosestGraveyardForBot(WorldLocation const& curPos, uint32 team) const +{ + WorldSafeLocsEntry const* pGraveyard = nullptr; + WorldSafeLocsEntry const* entry = nullptr; + float dist = 0; + float minDist = 0; + float x, y; + + curPos.GetPosition(x, y); + + pGraveyard = sWorldSafeLocsStore.LookupEntry(BG_AV_GraveyardIds[GetTeamIndexByTeamId(team)+7]); + minDist = (pGraveyard->Loc.X - x)*(pGraveyard->Loc.X - x)+(pGraveyard->Loc.Y - y)*(pGraveyard->Loc.Y - y); + + for (uint8 i = BG_AV_NODES_FIRSTAID_STATION; i <= BG_AV_NODES_FROSTWOLF_HUT; ++i) + { + if (m_Nodes[i].Owner == team && m_Nodes[i].State == POINT_CONTROLED) + { + entry = sWorldSafeLocsStore.LookupEntry(BG_AV_GraveyardIds[i]); + if (entry) + { + dist = (entry->Loc.X - x) * (entry->Loc.X - x) + (entry->Loc.Y - y) * (entry->Loc.Y - y); + if (dist < minDist) + { + minDist = dist; + pGraveyard = entry; + } + } + } + } + + return pGraveyard; +} +//end npcbot + bool BattlegroundAV::SetupBattleground() { // Create starting objects diff --git a/src/server/game/Battlegrounds/Zones/BattlegroundAV.h b/src/server/game/Battlegrounds/Zones/BattlegroundAV.h index 249cb0dfb..521245d93 100644 --- a/src/server/game/Battlegrounds/Zones/BattlegroundAV.h +++ b/src/server/game/Battlegrounds/Zones/BattlegroundAV.h @@ -1643,6 +1643,21 @@ class BattlegroundAV : public Battleground WorldSafeLocsEntry const* GetClosestGraveyard(Player* player) override; + //npcbot + WorldSafeLocsEntry const* GetClosestGraveyardForBot(WorldLocation const& curPos, uint32 team) const override; + void AddBot(Creature* bot) override; + void RemoveBot(ObjectGuid guid) override; + void HandleBotKillPlayer(Creature* killer, Player* victim) override; + void HandleBotKillBot(Creature* killer, Creature* victim) override; + void HandlePlayerKillBot(Creature* victim, Player* killer) override; + void HandleBotKillUnit(Creature* killer, Creature* victim) override; + void EventBotClickedOnFlag(Creature* bot, GameObject* target_obj) override; + void EventBotAssaultsPoint(Creature* bot, uint32 object); + void EventBotDefendsPoint(Creature* bot, uint32 object); + BG_AV_NodeInfo const (&GetNodes() const)[BG_AV_NODES_MAX] { return m_Nodes; } + uint32 GetObjectThroughNodeForBot(BG_AV_Nodes node, bool log = false) { return GetObjectThroughNode(node, log); } + //end npcbot + // Achievement: Av perfection and Everything counts bool CheckAchievementCriteriaMeet(uint32 criteriaId, Player const* source, Unit const* target = nullptr, uint32 miscvalue1 = 0) override; @@ -1673,7 +1688,14 @@ class BattlegroundAV : public Battleground } BG_AV_Nodes GetNodeThroughObject(uint32 object); + //npcbot + /* + //end npcbot uint32 GetObjectThroughNode(BG_AV_Nodes node); + //npcbot + */ + uint32 GetObjectThroughNode(BG_AV_Nodes node, bool log = true) const; + //end npcbot bool IsTower(BG_AV_Nodes node) { return m_Nodes[node].Tower; } /*mine*/ diff --git a/src/server/game/Battlegrounds/Zones/BattlegroundWS.cpp b/src/server/game/Battlegrounds/Zones/BattlegroundWS.cpp index 7c00dd599..b38a39567 100644 --- a/src/server/game/Battlegrounds/Zones/BattlegroundWS.cpp +++ b/src/server/game/Battlegrounds/Zones/BattlegroundWS.cpp @@ -27,6 +27,11 @@ #include "WorldPacket.h" #include "WorldStatePackets.h" +//npcbot +#include "botdatamgr.h" +#include "botmgr.h" +//end npcbot + // these variables aren't used outside of this file, so declare them only here enum BG_WSG_Rewards { @@ -247,6 +252,16 @@ void BattlegroundWS::AddPlayer(Player* player) PlayerScores[player->GetGUID().GetCounter()] = new BattlegroundWGScore(player->GetGUID()); } +//npcbot +void BattlegroundWS::AddBot(Creature* bot) +{ + bool const isInBattleground = IsPlayerInBattleground(bot->GetGUID()); + Battleground::AddBot(bot); + if (!isInBattleground) + BotScores[bot->GetEntry()] = new BattlegroundWGScore(bot->GetGUID()); +} +//end npcbot + void BattlegroundWS::RespawnFlag(uint32 Team, bool captured) { if (Team == ALLIANCE) @@ -385,12 +400,113 @@ void BattlegroundWS::EventPlayerCapturedFlag(Player* player) } void BattlegroundWS::HandleFlagRoomCapturePoint(int32 team) { + //npcbot + if (GetFlagPickerGUID(team).IsCreature()) + { + Creature const* flagBotCarrier = BotDataMgr::FindBot(GetFlagPickerGUID(team).GetEntry()); + uint32 areaBotTrigger = team == TEAM_ALLIANCE ? 3647 : 3646; + if (flagBotCarrier && BotMgr::IsBotInAreaTriggerRadius(flagBotCarrier, sAreaTriggerStore.LookupEntry(areaBotTrigger))) + EventBotCapturedFlag(const_cast(flagBotCarrier)); + return; + } + //end npcbot + Player* flagCarrier = ObjectAccessor::GetPlayer(GetBgMap(), GetFlagPickerGUID(team)); uint32 areaTrigger = team == TEAM_ALLIANCE ? 3647 : 3646; if (flagCarrier && flagCarrier->IsInAreaTriggerRadius(sAreaTriggerStore.LookupEntry(areaTrigger))) EventPlayerCapturedFlag(flagCarrier); } +//npcbot +void BattlegroundWS::EventBotCapturedFlag(Creature* bot) +{ + if (GetStatus() != STATUS_IN_PROGRESS) + return; + + uint32 winner = 0; + + bot->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_ENTER_PVP_COMBAT); + if (GetBotTeam(bot->GetGUID()) == ALLIANCE) + { + if (!IsHordeFlagPickedup()) + return; + SetHordeFlagPicker(ObjectGuid::Empty); // must be before aura remove to prevent 2 events (drop+capture) at the same time + // horde flag in base (but not respawned yet) + _flagState[TEAM_HORDE] = BG_WS_FLAG_STATE_WAIT_RESPAWN; + // Drop Horde Flag from Player + bot->RemoveAurasDueToSpell(BG_WS_SPELL_WARSONG_FLAG); + if (_flagDebuffState == 1) + bot->RemoveAurasDueToSpell(WS_SPELL_FOCUSED_ASSAULT); + else if (_flagDebuffState == 2) + bot->RemoveAurasDueToSpell(WS_SPELL_BRUTAL_ASSAULT); + + if (GetTeamScore(TEAM_ALLIANCE) < BG_WS_MAX_TEAM_SCORE) + AddPoint(ALLIANCE, 1); + PlaySoundToAll(BG_WS_SOUND_FLAG_CAPTURED_ALLIANCE); + RewardReputationToTeam(890, m_ReputationCapture, ALLIANCE); + } + else + { + if (!IsAllianceFlagPickedup()) + return; + SetAllianceFlagPicker(ObjectGuid::Empty); // must be before aura remove to prevent 2 events (drop+capture) at the same time + // alliance flag in base (but not respawned yet) + _flagState[TEAM_ALLIANCE] = BG_WS_FLAG_STATE_WAIT_RESPAWN; + // Drop Alliance Flag from Player + bot->RemoveAurasDueToSpell(BG_WS_SPELL_SILVERWING_FLAG); + if (_flagDebuffState == 1) + bot->RemoveAurasDueToSpell(WS_SPELL_FOCUSED_ASSAULT); + else if (_flagDebuffState == 2) + bot->RemoveAurasDueToSpell(WS_SPELL_BRUTAL_ASSAULT); + + if (GetTeamScore(TEAM_HORDE) < BG_WS_MAX_TEAM_SCORE) + AddPoint(HORDE, 1); + PlaySoundToAll(BG_WS_SOUND_FLAG_CAPTURED_HORDE); + RewardReputationToTeam(889, m_ReputationCapture, HORDE); + } + //for flag capture is reward 2 honorable kills + RewardHonorToTeam(GetBonusHonorFromKill(2), GetBotTeam(bot->GetGUID())); + + SpawnBGObject(BG_WS_OBJECT_H_FLAG, BG_WS_FLAG_RESPAWN_TIME); + SpawnBGObject(BG_WS_OBJECT_A_FLAG, BG_WS_FLAG_RESPAWN_TIME); + + if (GetBotTeam(bot->GetGUID()) == ALLIANCE) + SendBroadcastText(BG_WS_TEXT_CAPTURED_HORDE_FLAG, CHAT_MSG_BG_SYSTEM_ALLIANCE, bot); + else + SendBroadcastText(BG_WS_TEXT_CAPTURED_ALLIANCE_FLAG, CHAT_MSG_BG_SYSTEM_HORDE, bot); + + UpdateFlagState(GetBotTeam(bot->GetGUID()), 1); // flag state none + UpdateTeamScore(GetBotTeamId(bot->GetGUID())); + // only flag capture should be updated + UpdateBotScore(bot, SCORE_FLAG_CAPTURES, 1); // +1 flag captures + + // update last flag capture to be used if teamscore is equal + SetLastFlagCapture(GetBotTeam(bot->GetGUID())); + + if (GetTeamScore(TEAM_ALLIANCE) == BG_WS_MAX_TEAM_SCORE) + winner = ALLIANCE; + + if (GetTeamScore(TEAM_HORDE) == BG_WS_MAX_TEAM_SCORE) + winner = HORDE; + + if (winner) + { + UpdateWorldState(BG_WS_FLAG_UNK_ALLIANCE, 0); + UpdateWorldState(BG_WS_FLAG_UNK_HORDE, 0); + UpdateWorldState(BG_WS_FLAG_STATE_ALLIANCE, 1); + UpdateWorldState(BG_WS_FLAG_STATE_HORDE, 1); + UpdateWorldState(BG_WS_STATE_TIMER_ACTIVE, 0); + + RewardHonorToTeam(BG_WSG_Honor[m_HonorMode][BG_WSG_WIN], winner); + EndBattleground(winner); + } + else + { + _flagsTimer[GetTeamIndexByTeamId(GetBotTeam(bot->GetGUID())) ? 0 : 1] = BG_WS_FLAG_RESPAWN_TIME; + } +} +//end npcbot + void BattlegroundWS::EventPlayerDroppedFlag(Player* player) { if (GetStatus() != STATUS_IN_PROGRESS) @@ -479,6 +595,96 @@ void BattlegroundWS::EventPlayerDroppedFlag(Player* player) } } +//npcbot +void BattlegroundWS::EventBotDroppedFlag(Creature* bot) +{ + if (GetStatus() != STATUS_IN_PROGRESS) + { + // if not running, do not cast things at the dropper player (prevent spawning the "dropped" flag), neither send unnecessary messages + // just take off the aura + if (GetBotTeam(bot->GetGUID()) == ALLIANCE) + { + if (!IsHordeFlagPickedup()) + return; + + if (GetFlagPickerGUID(TEAM_HORDE) == bot->GetGUID()) + { + SetHordeFlagPicker(ObjectGuid::Empty); + bot->RemoveAurasDueToSpell(BG_WS_SPELL_WARSONG_FLAG); + } + } + else + { + if (!IsAllianceFlagPickedup()) + return; + + if (GetFlagPickerGUID(TEAM_ALLIANCE) == bot->GetGUID()) + { + SetAllianceFlagPicker(ObjectGuid::Empty); + bot->RemoveAurasDueToSpell(BG_WS_SPELL_SILVERWING_FLAG); + } + } + return; + } + + bool set = false; + + if (GetBotTeam(bot->GetGUID()) == ALLIANCE) + { + if (!IsHordeFlagPickedup()) + return; + if (GetFlagPickerGUID(TEAM_HORDE) == bot->GetGUID()) + { + SetHordeFlagPicker(ObjectGuid::Empty); + bot->RemoveAurasDueToSpell(BG_WS_SPELL_WARSONG_FLAG); + if (_flagDebuffState == 1) + bot->RemoveAurasDueToSpell(WS_SPELL_FOCUSED_ASSAULT); + else if (_flagDebuffState == 2) + bot->RemoveAurasDueToSpell(WS_SPELL_BRUTAL_ASSAULT); + _flagState[TEAM_HORDE] = BG_WS_FLAG_STATE_ON_GROUND; + bot->CastSpell(bot, BG_WS_SPELL_WARSONG_FLAG_DROPPED, true); + set = true; + } + } + else + { + if (!IsAllianceFlagPickedup()) + return; + if (GetFlagPickerGUID(TEAM_ALLIANCE) == bot->GetGUID()) + { + SetAllianceFlagPicker(ObjectGuid::Empty); + bot->RemoveAurasDueToSpell(BG_WS_SPELL_SILVERWING_FLAG); + if (_flagDebuffState == 1) + bot->RemoveAurasDueToSpell(WS_SPELL_FOCUSED_ASSAULT); + else if (_flagDebuffState == 2) + bot->RemoveAurasDueToSpell(WS_SPELL_BRUTAL_ASSAULT); + _flagState[TEAM_ALLIANCE] = BG_WS_FLAG_STATE_ON_GROUND; + bot->CastSpell(bot, BG_WS_SPELL_SILVERWING_FLAG_DROPPED, true); + set = true; + } + } + + if (set) + { + bot->CastSpell(bot, SPELL_RECENTLY_DROPPED_FLAG, true); + UpdateFlagState(GetBotTeam(bot->GetGUID()), 1); + + if (GetBotTeam(bot->GetGUID()) == ALLIANCE) + { + SendBroadcastText(BG_WS_TEXT_HORDE_FLAG_DROPPED, CHAT_MSG_BG_SYSTEM_HORDE, bot); + UpdateWorldState(BG_WS_FLAG_UNK_HORDE, uint32(-1)); + } + else + { + SendBroadcastText(BG_WS_TEXT_ALLIANCE_FLAG_DROPPED, CHAT_MSG_BG_SYSTEM_ALLIANCE, bot); + UpdateWorldState(BG_WS_FLAG_UNK_ALLIANCE, uint32(-1)); + } + + _flagsDropTimer[GetTeamIndexByTeamId(GetBotTeam(bot->GetGUID())) ? 0 : 1] = BG_WS_FLAG_DROP_TIME; + } +} +//end npcbot + void BattlegroundWS::EventPlayerClickedOnFlag(Player* player, GameObject* target_obj) { if (GetStatus() != STATUS_IN_PROGRESS) @@ -601,6 +807,157 @@ void BattlegroundWS::EventPlayerClickedOnFlag(Player* player, GameObject* target player->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_ENTER_PVP_COMBAT); } +//npcbot +void BattlegroundWS::EventBotClickedOnFlag(Creature* bot, GameObject* target_obj) +{ + if (GetStatus() != STATUS_IN_PROGRESS) + return; + + //alliance flag picked up from base + if (GetBotTeam(bot->GetGUID()) == HORDE && GetFlagState(ALLIANCE) == BG_WS_FLAG_STATE_ON_BASE + && BgObjects[BG_WS_OBJECT_A_FLAG] == target_obj->GetGUID()) + { + SendBroadcastText(BG_WS_TEXT_ALLIANCE_FLAG_PICKED_UP, CHAT_MSG_BG_SYSTEM_HORDE, bot); + PlaySoundToAll(BG_WS_SOUND_ALLIANCE_FLAG_PICKED_UP); + SpawnBGObject(BG_WS_OBJECT_A_FLAG, RESPAWN_ONE_DAY); + SetAllianceFlagPicker(bot->GetGUID()); + _flagState[TEAM_ALLIANCE] = BG_WS_FLAG_STATE_ON_PLAYER; + //update world state to show correct flag carrier + UpdateFlagState(HORDE, BG_WS_FLAG_STATE_ON_PLAYER); + UpdateWorldState(BG_WS_FLAG_UNK_ALLIANCE, 1); + bot->CastSpell(bot, BG_WS_SPELL_SILVERWING_FLAG, true); + if (_flagState[1] == BG_WS_FLAG_STATE_ON_PLAYER) + _bothFlagsKept = true; + + if (_flagDebuffState == 1) + bot->CastSpell(bot, WS_SPELL_FOCUSED_ASSAULT, true); + else if (_flagDebuffState == 2) + bot->CastSpell(bot, WS_SPELL_BRUTAL_ASSAULT, true); + } + + //horde flag picked up from base + if (GetBotTeam(bot->GetGUID()) == ALLIANCE && GetFlagState(HORDE) == BG_WS_FLAG_STATE_ON_BASE + && BgObjects[BG_WS_OBJECT_H_FLAG] == target_obj->GetGUID()) + { + SendBroadcastText(BG_WS_TEXT_HORDE_FLAG_PICKED_UP, CHAT_MSG_BG_SYSTEM_ALLIANCE, bot); + PlaySoundToAll(BG_WS_SOUND_HORDE_FLAG_PICKED_UP); + SpawnBGObject(BG_WS_OBJECT_H_FLAG, RESPAWN_ONE_DAY); + SetHordeFlagPicker(bot->GetGUID()); + _flagState[TEAM_HORDE] = BG_WS_FLAG_STATE_ON_PLAYER; + //update world state to show correct flag carrier + UpdateFlagState(ALLIANCE, BG_WS_FLAG_STATE_ON_PLAYER); + UpdateWorldState(BG_WS_FLAG_UNK_HORDE, 1); + bot->CastSpell(bot, BG_WS_SPELL_WARSONG_FLAG, true); + if (_flagState[0] == BG_WS_FLAG_STATE_ON_PLAYER) + _bothFlagsKept = true; + + if (_flagDebuffState == 1) + bot->CastSpell(bot, WS_SPELL_FOCUSED_ASSAULT, true); + else if (_flagDebuffState == 2) + bot->CastSpell(bot, WS_SPELL_BRUTAL_ASSAULT, true); + } + + //Alliance flag on ground(not in base) (returned or picked up again from ground!) + if (GetFlagState(ALLIANCE) == BG_WS_FLAG_STATE_ON_GROUND && bot->IsWithinDistInMap(target_obj, 10) + && target_obj->GetGOInfo()->entry == BG_OBJECT_A_FLAG_GROUND_WS_ENTRY) + { + if (GetBotTeam(bot->GetGUID()) == ALLIANCE) + { + SendBroadcastText(BG_WS_TEXT_ALLIANCE_FLAG_RETURNED, CHAT_MSG_BG_SYSTEM_ALLIANCE, bot); + UpdateFlagState(HORDE, BG_WS_FLAG_STATE_WAIT_RESPAWN); + RespawnFlag(ALLIANCE, false); + SpawnBGObject(BG_WS_OBJECT_A_FLAG, RESPAWN_IMMEDIATELY); + PlaySoundToAll(BG_WS_SOUND_FLAG_RETURNED); + UpdateBotScore(bot, SCORE_FLAG_RETURNS, 1); + _bothFlagsKept = false; + HandleFlagRoomCapturePoint(TEAM_HORDE); // Check Horde flag if it is in capture zone; if so, capture it + } + else + { + SendBroadcastText(BG_WS_TEXT_ALLIANCE_FLAG_PICKED_UP, CHAT_MSG_BG_SYSTEM_HORDE, bot); + PlaySoundToAll(BG_WS_SOUND_ALLIANCE_FLAG_PICKED_UP); + SpawnBGObject(BG_WS_OBJECT_A_FLAG, RESPAWN_ONE_DAY); + SetAllianceFlagPicker(bot->GetGUID()); + bot->CastSpell(bot, BG_WS_SPELL_SILVERWING_FLAG, true); + _flagState[TEAM_ALLIANCE] = BG_WS_FLAG_STATE_ON_PLAYER; + UpdateFlagState(HORDE, BG_WS_FLAG_STATE_ON_PLAYER); + if (_flagDebuffState == 1) + bot->CastSpell(bot, WS_SPELL_FOCUSED_ASSAULT, true); + else if (_flagDebuffState == 2) + bot->CastSpell(bot, WS_SPELL_BRUTAL_ASSAULT, true); + UpdateWorldState(BG_WS_FLAG_UNK_ALLIANCE, 1); + } + //called in HandleGameObjectUseOpcode: + target_obj->Delete(); + } + + //Horde flag on ground(not in base) (returned or picked up again) + if (GetFlagState(HORDE) == BG_WS_FLAG_STATE_ON_GROUND && bot->IsWithinDistInMap(target_obj, 10) + && target_obj->GetGOInfo()->entry == BG_OBJECT_H_FLAG_GROUND_WS_ENTRY) + { + if (GetBotTeam(bot->GetGUID()) == HORDE) + { + SendBroadcastText(BG_WS_TEXT_HORDE_FLAG_RETURNED, CHAT_MSG_BG_SYSTEM_HORDE, bot); + UpdateFlagState(ALLIANCE, BG_WS_FLAG_STATE_WAIT_RESPAWN); + RespawnFlag(HORDE, false); + SpawnBGObject(BG_WS_OBJECT_H_FLAG, RESPAWN_IMMEDIATELY); + PlaySoundToAll(BG_WS_SOUND_FLAG_RETURNED); + UpdateBotScore(bot, SCORE_FLAG_RETURNS, 1); + _bothFlagsKept = false; + HandleFlagRoomCapturePoint(TEAM_ALLIANCE); // Check Alliance flag if it is in capture zone; if so, capture it + } + else + { + SendBroadcastText(BG_WS_TEXT_HORDE_FLAG_PICKED_UP, CHAT_MSG_BG_SYSTEM_ALLIANCE, bot); + PlaySoundToAll(BG_WS_SOUND_HORDE_FLAG_PICKED_UP); + SpawnBGObject(BG_WS_OBJECT_H_FLAG, RESPAWN_ONE_DAY); + SetHordeFlagPicker(bot->GetGUID()); + bot->CastSpell(bot, BG_WS_SPELL_WARSONG_FLAG, true); + _flagState[TEAM_HORDE] = BG_WS_FLAG_STATE_ON_PLAYER; + UpdateFlagState(ALLIANCE, BG_WS_FLAG_STATE_ON_PLAYER); + if (_flagDebuffState == 1) + bot->CastSpell(bot, WS_SPELL_FOCUSED_ASSAULT, true); + else if (_flagDebuffState == 2) + bot->CastSpell(bot, WS_SPELL_BRUTAL_ASSAULT, true); + UpdateWorldState(BG_WS_FLAG_UNK_HORDE, 1); + } + //called in HandleGameObjectUseOpcode: + target_obj->Delete(); + } + + bot->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_ENTER_PVP_COMBAT); +} + +void BattlegroundWS::RemoveBot(ObjectGuid guid) +{ + // sometimes flag aura not removed :( + if (IsAllianceFlagPickedup() && m_FlagKeepers[TEAM_ALLIANCE] == guid) + { + Creature const* bot = BotDataMgr::FindBot(guid.GetEntry()); + if (!bot) + { + TC_LOG_ERROR("bg.battleground", "BattlegroundWS: Removing offline bot {} who has the FLAG!!", guid.GetEntry()); + SetAllianceFlagPicker(ObjectGuid::Empty); + RespawnFlag(ALLIANCE, false); + } + else + EventBotDroppedFlag(const_cast(bot)); + } + if (IsHordeFlagPickedup() && m_FlagKeepers[TEAM_HORDE] == guid) + { + Creature const* bot = BotDataMgr::FindBot(guid.GetEntry()); + if (!bot) + { + TC_LOG_ERROR("bg.battleground", "BattlegroundWS: Removing offline bot {} who has the FLAG!!", guid.GetEntry()); + SetHordeFlagPicker(ObjectGuid::Empty); + RespawnFlag(HORDE, false); + } + else + EventBotDroppedFlag(const_cast(bot)); + } +} +//end npcbot + void BattlegroundWS::RemovePlayer(Player* player, ObjectGuid guid, uint32 /*team*/) { // sometimes flag aura not removed :( @@ -695,6 +1052,42 @@ void BattlegroundWS::HandleAreaTrigger(Player* player, uint32 trigger) // HandleTriggerBuff(buff_guid, player); } +//npcbot +void BattlegroundWS::HandleBotAreaTrigger(Creature* bot, uint32 trigger) +{ + if (GetStatus() != STATUS_IN_PROGRESS) + return; + + switch (trigger) + { + case 3686: // Alliance elixir of speed spawn. Trigger not working, because located inside other areatrigger, can be replaced by IsWithinDist(object, dist) in Battleground::Update(). + case 3687: // Horde elixir of speed spawn. Trigger not working, because located inside other areatrigger, can be replaced by IsWithinDist(object, dist) in Battleground::Update(). + case 3706: // Alliance elixir of regeneration spawn + case 3708: // Horde elixir of regeneration spawn + case 3707: // Alliance elixir of berserk spawn + case 3709: // Horde elixir of berserk spawn + case 3649: // unk1 + case 3688: // unk2 + case 4628: // unk3 + case 4629: // unk4 + break; + case 3646: // Alliance Flag spawn + if (_flagState[TEAM_HORDE] && !_flagState[TEAM_ALLIANCE]) + if (GetFlagPickerGUID(TEAM_HORDE) == bot->GetGUID()) + EventBotCapturedFlag(bot); + break; + case 3647: // Horde Flag spawn + if (_flagState[TEAM_ALLIANCE] && !_flagState[TEAM_HORDE]) + if (GetFlagPickerGUID(TEAM_ALLIANCE) == bot->GetGUID()) + EventBotCapturedFlag(bot); + break; + default: + Battleground::HandleBotAreaTrigger(bot, trigger); + break; + } +} +//end npcbot + bool BattlegroundWS::SetupBattleground() { // flags @@ -805,6 +1198,33 @@ void BattlegroundWS::HandleKillPlayer(Player* player, Player* killer) Battleground::HandleKillPlayer(player, killer); } +//npcbot +void BattlegroundWS::HandleBotKillPlayer(Creature* killer, Player* victim) +{ + if (GetStatus() != STATUS_IN_PROGRESS) + return; + + EventPlayerDroppedFlag(victim); + Battleground::HandleBotKillPlayer(killer, victim); +} +void BattlegroundWS::HandleBotKillBot(Creature* killer, Creature* victim) +{ + if (GetStatus() != STATUS_IN_PROGRESS) + return; + + EventBotDroppedFlag(victim); + Battleground::HandleBotKillBot(killer, victim); +} +void BattlegroundWS::HandlePlayerKillBot(Creature* victim, Player* killer) +{ + if (GetStatus() != STATUS_IN_PROGRESS) + return; + + EventBotDroppedFlag(victim); + Battleground::HandlePlayerKillBot(victim, killer); +} +//end npcbot + bool BattlegroundWS::UpdatePlayerScore(Player* player, uint32 type, uint32 value, bool doAddHonor) { if (!Battleground::UpdatePlayerScore(player, type, value, doAddHonor)) @@ -824,6 +1244,16 @@ bool BattlegroundWS::UpdatePlayerScore(Player* player, uint32 type, uint32 value return true; } +//npcbot +bool BattlegroundWS::UpdateBotScore(Creature const* bot, uint32 type, uint32 value, bool doAddHonor) +{ + if (!Battleground::UpdateBotScore(bot, type, value, doAddHonor)) + return false; + + return true; +} +//end npcbot + WorldSafeLocsEntry const* BattlegroundWS::GetClosestGraveyard(Player* player) { //if status in progress, it returns main graveyards with spiritguides @@ -847,6 +1277,26 @@ WorldSafeLocsEntry const* BattlegroundWS::GetClosestGraveyard(Player* player) } } +//npcbot +WorldSafeLocsEntry const* BattlegroundWS::GetClosestGraveyardForBot(WorldLocation const& /*curPos*/, uint32 team) const +{ + if (team == ALLIANCE) + { + if (GetStatus() == STATUS_IN_PROGRESS) + return sWorldSafeLocsStore.LookupEntry(WS_GRAVEYARD_MAIN_ALLIANCE); + else + return sWorldSafeLocsStore.LookupEntry(WS_GRAVEYARD_FLAGROOM_ALLIANCE); + } + else + { + if (GetStatus() == STATUS_IN_PROGRESS) + return sWorldSafeLocsStore.LookupEntry(WS_GRAVEYARD_MAIN_HORDE); + else + return sWorldSafeLocsStore.LookupEntry(WS_GRAVEYARD_FLAGROOM_HORDE); + } +} +//end npcbot + void BattlegroundWS::FillInitialWorldStates(WorldPackets::WorldState::InitWorldStates& packet) { packet.Worldstates.emplace_back(BG_WS_FLAG_CAPTURES_ALLIANCE, GetTeamScore(TEAM_ALLIANCE)); diff --git a/src/server/game/Battlegrounds/Zones/BattlegroundWS.h b/src/server/game/Battlegrounds/Zones/BattlegroundWS.h index aa120ab8f..cc07696a3 100644 --- a/src/server/game/Battlegrounds/Zones/BattlegroundWS.h +++ b/src/server/game/Battlegrounds/Zones/BattlegroundWS.h @@ -236,6 +236,20 @@ class BattlegroundWS : public Battleground void EndBattleground(uint32 winner) override; WorldSafeLocsEntry const* GetClosestGraveyard(Player* player) override; + //npcbot + WorldSafeLocsEntry const* GetClosestGraveyardForBot(WorldLocation const& curPos, uint32 team) const override; + void AddBot(Creature* bot) override; + void RemoveBot(ObjectGuid guid) override; + bool UpdateBotScore(Creature const* bot, uint32 type, uint32 value, bool doAddHonor = true) override; + void HandleBotKillPlayer(Creature* bot, Player* victim) override; + void HandleBotKillBot(Creature* bot, Creature* victim) override; + void HandlePlayerKillBot(Creature* bot, Player* killer) override; + void EventBotDroppedFlag(Creature* bot) override; + void EventBotClickedOnFlag(Creature* bot, GameObject* target_obj) override; + void HandleBotAreaTrigger(Creature* bot, uint32 trigger) override; + void EventBotCapturedFlag(Creature* bot); + //end npcbot + void UpdateFlagState(uint32 team, uint32 value); void SetLastFlagCapture(uint32 team) { _lastFlagCaptureTeam = team; } void UpdateTeamScore(uint32 team); diff --git a/src/server/game/Combat/CombatManager.cpp b/src/server/game/Combat/CombatManager.cpp index 005e0d8e6..303be7b6d 100644 --- a/src/server/game/Combat/CombatManager.cpp +++ b/src/server/game/Combat/CombatManager.cpp @@ -22,6 +22,10 @@ #include "CreatureAI.h" #include "Player.h" +//npcbot +#include "botmgr.h" +//end npcbot + /*static*/ bool CombatManager::CanBeginCombat(Unit const* a, Unit const* b) { // Checks combat validity before initial reference creation. @@ -208,12 +212,33 @@ bool CombatManager::SetInCombatWith(Unit* who, bool addSecondUnitSuppressed) CombatReference* ref; if (_owner->IsControlledByPlayer() && who->IsControlledByPlayer()) ref = new PvPCombatReference(_owner, who); + //npcbot: follow pvp rules + else if ((_owner->ToCreature() && _owner->ToCreature()->IsNPCBotOrPet() && who->IsControlledByPlayer()) || + (who->ToCreature() && who->ToCreature()->IsNPCBotOrPet() && _owner->IsControlledByPlayer()) || + (_owner->ToCreature() && _owner->ToCreature()->IsNPCBotOrPet() && + who->ToCreature() && who->ToCreature()->IsNPCBotOrPet())) + ref = new PvPCombatReference(_owner, who); + //end npcbot else ref = new CombatReference(_owner, who); if (addSecondUnitSuppressed) ref->Suppress(who); + //npcbot + /* + if (_owner->GetTypeId() == TYPEID_PLAYER && _owner->ToPlayer()->HaveBot()) + { + BotMap const* map = _owner->ToPlayer()->GetBotMgr()->GetBotMap(); + for (BotMap::const_iterator itr = map->begin(); itr != map->end(); ++itr) + { + itr->second->SetInCombatWith(who); + if (Unit* botPet = itr->second->GetBotsPet()) + botPet->SetInCombatWith(who); + } + }*/ + //end npcbot + // ...and insert it into both managers PutReference(who->GetGUID(), ref); who->GetCombatManager().PutReference(_owner->GetGUID(), ref); @@ -262,6 +287,27 @@ void CombatManager::InheritCombatStatesFrom(Unit const* who) continue; SetInCombatWith(target); } + //npcbot + for (auto& ref : mgr._pveRefs) + { + if (!IsInCombatWith(ref.first)) + { + Unit* target = ref.second->GetOther(who); + if ((_owner->IsImmuneToPC() && target->IsNPCBotOrPet()) || + (_owner->IsImmuneToNPC() && !target->IsNPCBotOrPet())) + continue; + SetInCombatWith(target); + } + } + for (auto& ref : mgr._pvpRefs) + { + Unit* target = ref.second->GetOther(who); + if ((_owner->IsImmuneToPC() && target->IsNPCBotOrPet()) || + (_owner->IsImmuneToNPC() && !target->IsNPCBotOrPet())) + continue; + SetInCombatWith(target); + } + //end npcbot } void CombatManager::EndCombatBeyondRange(float range, bool includingPvP) @@ -387,6 +433,18 @@ bool CombatManager::UpdateOwnerCombatState() const if (combatState) { + //npcbot: party combat hook + Player* playerOwner = nullptr; + if (_owner->GetTypeId() == TYPEID_PLAYER && _owner->ToPlayer()->HaveBot()) + playerOwner = _owner->ToPlayer(); + else if (_owner->GetTypeId() == TYPEID_UNIT && _owner->ToCreature()->IsNPCBotOrPet() && + !_owner->ToCreature()->IsFreeBot()) + playerOwner = _owner->ToCreature()->GetBotOwner(); + + if (playerOwner) + BotMgr::OnBotPartyEngage(playerOwner); + //end npcbot + _owner->SetUnitFlag(UNIT_FLAG_IN_COMBAT); _owner->AtEnterCombat(); if (_owner->GetTypeId() != TYPEID_UNIT) diff --git a/src/server/game/Combat/ThreatManager.cpp b/src/server/game/Combat/ThreatManager.cpp index 50ad9a8e6..e1c319bbf 100644 --- a/src/server/game/Combat/ThreatManager.cpp +++ b/src/server/game/Combat/ThreatManager.cpp @@ -34,6 +34,10 @@ #include #include +//npcbot +#include "botmgr.h" +//end npcbot + const CompareThreatLessThan ThreatManager::CompareThreat; class ThreatManager::Heap : public boost::heap::fibonacci_heap> @@ -93,6 +97,13 @@ void ThreatReference::UpdateOffline() if (b->HasUnitFlag(UNIT_FLAG_IMMUNE_TO_PC)) return false; } + //npcbot + else if (a->IsNPCBotOrPet()) + { + if (b->HasUnitFlag(UNIT_FLAG_IMMUNE_TO_PC)) + return false; + } + //end npcbot else { if (b->HasUnitFlag(UNIT_FLAG_IMMUNE_TO_NPC)) @@ -191,6 +202,11 @@ void ThreatReference::HeapNotifyDecreased() if (tWho->GetSummonerGUID().IsPlayer()) return false; + //npcbot - npcbots and their pets cannot have threatlist + if (cWho->IsNPCBot() || cWho->IsNPCBotPet()) + return false; + //end npcbot + return true; } @@ -682,6 +698,11 @@ void ThreatManager::ProcessAIUpdates() if (Player* modOwner = victim->GetSpellModOwner()) modOwner->ApplySpellMod(spell->Id, SPELLMOD_THREAT, threat); + + //npcbot: threat mods + if (victim->GetTypeId() == TYPEID_UNIT && victim->ToCreature()->GetBotAI()) + BotMgr::ApplyBotThreatMods(victim, spell, threat); + //end npcbot } // modifiers by effect school diff --git a/src/server/game/Conditions/ConditionMgr.cpp b/src/server/game/Conditions/ConditionMgr.cpp index 5894c68df..8be05d79f 100644 --- a/src/server/game/Conditions/ConditionMgr.cpp +++ b/src/server/game/Conditions/ConditionMgr.cpp @@ -33,6 +33,10 @@ #include "SpellMgr.h" #include "World.h" +//npcbot +#include "bot_ai.h" +//end npcbot + char const* const ConditionMgr::StaticSourceTypeData[CONDITION_SOURCE_TYPE_MAX] = { "None", @@ -147,6 +151,11 @@ bool Condition::Meets(ConditionSourceInfo& sourceInfo) const } case CONDITION_ITEM: { + //npcbot + if (object->IsNPCBot()) + condMeets = true; + else + //end npcbot if (Player* player = object->ToPlayer()) { // don't allow 0 items (it's checked during table load) @@ -158,6 +167,11 @@ bool Condition::Meets(ConditionSourceInfo& sourceInfo) const } case CONDITION_ITEM_EQUIPPED: { + //npcbot + if (object->IsNPCBot()) + condMeets = true; //for now + else + //end npcbot if (Player* player = object->ToPlayer()) condMeets = player->HasItemOrGemWithIdEquipped(ConditionValue1, 1); break; @@ -167,6 +181,14 @@ bool Condition::Meets(ConditionSourceInfo& sourceInfo) const break; case CONDITION_REPUTATION_RANK: { + //npcbot + if (object->IsNPCBot() && object->ToCreature()->GetBotAI() && !object->ToCreature()->IsFreeBot()) + { + if (FactionEntry const* faction = sFactionStore.LookupEntry(ConditionValue1)) + condMeets = (ConditionValue2 & (1 << object->ToCreature()->GetBotOwner()->GetReputationMgr().GetRank(faction))); + } + else + //end npcbot if (Player* player = object->ToPlayer()) { if (FactionEntry const* faction = sFactionStore.LookupEntry(ConditionValue1)) @@ -176,12 +198,22 @@ bool Condition::Meets(ConditionSourceInfo& sourceInfo) const } case CONDITION_ACHIEVEMENT: { + //npcbot + if (object->IsNPCBot()) + condMeets = true; + else + //end npcbot if (Player* player = object->ToPlayer()) condMeets = player->HasAchieved(ConditionValue1); break; } case CONDITION_TEAM: { + //npcbot + if (object->IsNPCBot() && object->ToCreature()->GetBotAI() && !object->ToCreature()->IsFreeBot()) + condMeets = object->ToCreature()->GetBotOwner()->GetTeam() == ConditionValue1; + else + //end npcbot if (Player* player = object->ToPlayer()) condMeets = player->GetTeam() == ConditionValue1; break; @@ -200,12 +232,22 @@ bool Condition::Meets(ConditionSourceInfo& sourceInfo) const } case CONDITION_GENDER: { + //npcbot + if (object->IsNPCBot()) + condMeets = object->ToCreature()->GetGender() == Gender(ConditionValue1); + else + //end npcbot if (Player* player = object->ToPlayer()) condMeets = player->GetNativeGender() == Gender(ConditionValue1); break; } case CONDITION_SKILL: { + //npcbot + if (object->IsNPCBot()) + condMeets = true; + else + //end npcbot if (Player* player = object->ToPlayer()) condMeets = player->HasSkill(ConditionValue1) && player->GetBaseSkillValue(ConditionValue1) >= ConditionValue2; break; @@ -280,6 +322,11 @@ bool Condition::Meets(ConditionSourceInfo& sourceInfo) const break; case CONDITION_SPELL: { + //npcbot + if (object->GetTypeId() == TYPEID_UNIT && object->ToCreature()->GetBotAI()) + condMeets = object->ToCreature()->GetBotAI()->HasSpell(sSpellMgr->GetSpellInfo(ConditionValue1)->GetFirstRankSpell()->Id); + else + //end npcbot if (Player* player = object->ToPlayer()) condMeets = player->HasSpell(ConditionValue1); break; diff --git a/src/server/game/DataStores/DBCStores.cpp b/src/server/game/DataStores/DBCStores.cpp index 7852f7172..d5a4908ac 100644 --- a/src/server/game/DataStores/DBCStores.cpp +++ b/src/server/game/DataStores/DBCStores.cpp @@ -115,7 +115,7 @@ DBCStorage sHolidaysStore(Holidaysfmt); DBCStorage sItemStore(Itemfmt); DBCStorage sItemBagFamilyStore(ItemBagFamilyfmt); //DBCStorage sItemCondExtCostsStore(ItemCondExtCostsEntryfmt); -//DBCStorage sItemDisplayInfoStore(ItemDisplayTemplateEntryfmt); -- not used currently +DBCStorage sItemDisplayInfoStore(ItemDisplayTemplateEntryfmt); DBCStorage sItemExtendedCostStore(ItemExtendedCostEntryfmt); DBCStorage sItemLimitCategoryStore(ItemLimitCategoryEntryfmt); DBCStorage sItemRandomPropertiesStore(ItemRandomPropertiesfmt); @@ -338,7 +338,7 @@ void LoadDBCStores(const std::string& dataPath) LOAD_DBC(sHolidaysStore, "Holidays.dbc"); LOAD_DBC(sItemStore, "Item.dbc"); LOAD_DBC(sItemBagFamilyStore, "ItemBagFamily.dbc"); - //LOAD_DBC(sItemDisplayInfoStore, "ItemDisplayInfo.dbc"); -- not used currently + LOAD_DBC(sItemDisplayInfoStore, "ItemDisplayInfo.dbc"); //LOAD_DBC(sItemCondExtCostsStore, "ItemCondExtCosts.dbc"); LOAD_DBC(sItemExtendedCostStore, "ItemExtendedCost.dbc"); LOAD_DBC(sItemLimitCategoryStore, "ItemLimitCategory.dbc"); diff --git a/src/server/game/DataStores/DBCStores.h b/src/server/game/DataStores/DBCStores.h index 0ac62982c..17ef7818d 100644 --- a/src/server/game/DataStores/DBCStores.h +++ b/src/server/game/DataStores/DBCStores.h @@ -145,7 +145,7 @@ TC_GAME_API extern DBCStorage sGtRegenMPPerSptSto TC_GAME_API extern DBCStorage sHolidaysStore; TC_GAME_API extern DBCStorage sItemStore; TC_GAME_API extern DBCStorage sItemBagFamilyStore; -//TC_GAME_API extern DBCStorage sItemDisplayInfoStore; -- not used currently +TC_GAME_API extern DBCStorage sItemDisplayInfoStore; TC_GAME_API extern DBCStorage sItemExtendedCostStore; TC_GAME_API extern DBCStorage sItemLimitCategoryStore; TC_GAME_API extern DBCStorage sItemRandomPropertiesStore; diff --git a/src/server/game/DungeonFinding/LFGMgr.cpp b/src/server/game/DungeonFinding/LFGMgr.cpp index 3e4bc2c92..ef671d2f5 100644 --- a/src/server/game/DungeonFinding/LFGMgr.cpp +++ b/src/server/game/DungeonFinding/LFGMgr.cpp @@ -40,6 +40,13 @@ #include "World.h" #include "WorldSession.h" +//npcbot +#include "botcommon.h" +#include "botmgr.h" +#include "Chat.h" +#include "Creature.h" +//end npcbot + namespace lfg { @@ -466,6 +473,46 @@ void LFGMgr::JoinLfg(Player* player, uint8 roles, LfgDungeonSet& dungeons, const joinData.result = LFG_JOIN_PARTY_NOT_MEET_REQS; ++memberCount; players.insert(plrg->GetGUID()); + + //npcbot + if (!plrg->HaveBot()) + continue; + //add npcbots + BotMap const* map = plrg->GetBotMgr()->GetBotMap(); + for (BotMap::const_iterator itr = map->begin(); itr != map->end(); ++itr) + { + if (!grp->IsMember(itr->first)) + continue; + + //disabled in config + if (!BotMgr::IsNpcBotDungeonFinderEnabled()) + { + (ChatHandler(plrg->GetSession())).SendSysMessage("Using npcbots in Dungeon Finder is restricted. Contact your administration."); + + if (plrg->GetGUID() != grp->GetLeaderGUID()) + if (Player* leader = ObjectAccessor::FindPlayer(grp->GetLeaderGUID())) + (ChatHandler(leader->GetSession())).PSendSysMessage("There is a npcbot in your group (owner: %s). Using npcbots in Dungeon Finder is restricted. Contact your administration.", + plrg->GetName().c_str()); + + joinData.result = LFG_JOIN_PARTY_NOT_MEET_REQS; + break; + } + + if (/*Creature* bot = */ObjectAccessor::GetCreature(*plrg, itr->first)) + { + //if (!(bot->GetBotRoles() & ( 1 | 2 | 4 ))) //(BOT_ROLE_TANK | BOT_ROLE_DPS | BOT_ROLE_HEAL) + //{ + // //no valid roles - reqs are not met + // (ChatHandler(plrg->GetSession())).PSendSysMessage("Your bot %s does not have any viable roles assigned.", bot->GetName().c_str()); + // joinData.result = LFG_JOIN_PARTY_NOT_MEET_REQS; + // continue; + //} + + ++memberCount; + players.insert(itr->first); + } + } + //end npcbot } } @@ -563,6 +610,9 @@ void LFGMgr::JoinLfg(Player* player, uint8 roles, LfgDungeonSet& dungeons, const SetState(gguid, LFG_STATE_ROLECHECK); // Send update to player LfgUpdateData updateData = LfgUpdateData(LFG_UPDATETYPE_JOIN_QUEUE, dungeons, comment); + //npcbot + std::map brolemap; + //end npcbot for (GroupReference* itr = grp->GetFirstMember(); itr != nullptr; itr = itr->next()) { if (Player* plrg = itr->GetSource()) @@ -576,10 +626,57 @@ void LFGMgr::JoinLfg(Player* player, uint8 roles, LfgDungeonSet& dungeons, const if (!debugNames.empty()) debugNames.append(", "); debugNames.append(plrg->GetName()); + + //npcbot + if (!plrg->HaveBot()) + continue; + //add npcbots + BotMap const* map = plrg->GetBotMgr()->GetBotMap(); + for (BotMap::const_iterator itr = map->begin(); itr != map->end(); ++itr) + { + ObjectGuid bguid = itr->first; + if (players.find(bguid) == players.end() || !grp->IsMember(bguid)) + continue; + + Creature* bot = ObjectAccessor::GetCreature(*plrg, bguid); + if (!bot) + continue; + + SetState(bguid, LFG_STATE_ROLECHECK); + if (!isContinue) + SetSelectedDungeons(bguid, dungeons); + roleCheck.roles[bguid] = 0; + if (!debugNames.empty()) + debugNames.append(", "); + debugNames.append(bot->GetName()); + + //fill possible roles (as if player selected all roles possible for class) + uint8 broles = PLAYER_ROLE_DAMAGE; + if (bot->GetBotClass() == CLASS_WARRIOR || bot->GetBotClass() == CLASS_PALADIN || + bot->GetBotClass() == CLASS_DEATH_KNIGHT || bot->GetBotClass() == CLASS_DRUID || + (bot->GetBotRoles() & BOT_ROLE_TANK)) + broles |= PLAYER_ROLE_TANK; + if (bot->GetBotClass() == CLASS_PRIEST || bot->GetBotClass() == CLASS_DRUID || + bot->GetBotClass() == CLASS_SHAMAN || bot->GetBotClass() == CLASS_PALADIN || + (bot->GetBotRoles() & BOT_ROLE_HEAL)) + broles |= PLAYER_ROLE_HEALER; + //remove unneeded / occupied roles so players can go with role they choose + if (roles & PLAYER_ROLE_TANK) + broles &= ~PLAYER_ROLE_TANK; + if (roles & PLAYER_ROLE_HEALER) + broles &= ~PLAYER_ROLE_HEALER; + + brolemap[bguid] = broles; + } + //end npcbot } } // Update leader role UpdateRoleCheck(gguid, guid, roles); + //npcbot - update bots' roles + for (std::map::const_iterator it = brolemap.begin(); it != brolemap.end(); ++it) + UpdateRoleCheck(gguid, it->first, it->second); + //end npcbot } else // Add player to queue { @@ -716,6 +813,9 @@ void LFGMgr::UpdateRoleCheck(ObjectGuid gguid, ObjectGuid guid /* = ObjectGuid:: if (Player* player = ObjectAccessor::FindPlayer(guid)) roles = FilterClassRoles(player, roles); else + //npcbot: allow bots to pass through, bot roles are checked elsewhere + if (guid.IsPlayer()) + //end npcbot return; } @@ -967,6 +1067,63 @@ void LFGMgr::MakeNewGroup(LfgProposal const& proposal) if (!player) continue; + //npcbot - handle player's bots + if (player->HaveBot()) + { + Group* group = player->GetGroup(); + if (group && group != grp) + Player::RemoveFromGroup(group, pguid); + + if (!grp) + { + grp = new Group(); + grp->ConvertToLFG(); + grp->Create(player); + ObjectGuid gguid = grp->GetGUID(); + SetState(gguid, LFG_STATE_PROPOSAL); + sGroupMgr->AddGroup(grp); + } + else if (group != grp) + grp->AddMember(player); + + grp->SetLfgRoles(pguid, proposal.players.find(pguid)->second.role); + + // Add the cooldown spell if queued for a random dungeon + if (dungeon->type == LFG_TYPE_RANDOM) + player->CastSpell(player, LFG_SPELL_DUNGEON_COOLDOWN, false); + + for (GuidList::const_iterator itr2 = players.begin(); itr2 != players.end(); ++itr2) + { + ObjectGuid bguid = (*itr2); + if (bguid.IsPlayer()) + continue; + Creature* bot = player->GetBotMgr()->GetBot(bguid); + if (!bot) + continue; + + player->GetBotMgr()->AddBotToGroup(bot); + grp->SetLfgRoles(bguid, proposal.players.find(bguid)->second.role); + } + + if (grp->GetMembersCount() >= 5) + { + uint8 pcount = 0; + for (GroupReference const* gitr = grp->GetFirstMember(); gitr != nullptr; gitr = gitr->next()) + if (gitr->GetSource()) + ++pcount; + if (pcount <= 1) + { + //only one player in group + ChatHandler ch(player->GetSession()); + ch.SendSysMessage("You are the only player in your group, loot method set to Free For All"); + grp->SetLootMethod(FREE_FOR_ALL); + } + } + + continue; + } + //end npcbot + Group* group = player->GetGroup(); if (group && group != grp) group->RemoveMember(player->GetGUID()); @@ -1041,6 +1198,29 @@ void LFGMgr::UpdateProposal(uint32 proposalId, ObjectGuid guid, bool accept) if (itProposalPlayer == proposal.players.end()) return; + //npcbot - player accepted proposal + //make its bots accept too + if (accept && guid.IsPlayer()) + { + if (Player* player = ObjectAccessor::FindConnectedPlayer(guid)) + { + if (player->HaveBot()) + { + for (LfgProposalPlayerContainer::iterator itPlayers = proposal.players.begin(); itPlayers != proposal.players.end(); ++itPlayers) + { + ObjectGuid bguid = itPlayers->first; + if (bguid.IsPlayer()) + continue; + if (!player->GetBotMgr()->GetBot(bguid)) + continue; + + itPlayers->second.accept = LfgAnswer(accept); + } + } + } + } + //end npcbot + LfgProposalPlayer& player = itProposalPlayer->second; player.accept = LfgAnswer(accept); diff --git a/src/server/game/DungeonFinding/LFGScripts.cpp b/src/server/game/DungeonFinding/LFGScripts.cpp index c01dbc967..406782644 100644 --- a/src/server/game/DungeonFinding/LFGScripts.cpp +++ b/src/server/game/DungeonFinding/LFGScripts.cpp @@ -99,6 +99,12 @@ void LFGPlayerScript::OnMapChanged(Player* player) if (Player* member = itr->GetSource()) player->GetSession()->SendNameQueryOpcode(member->GetGUID()); + //npcbot + for (GroupBotReference* itr = group->GetFirstBotMember(); itr != nullptr; itr = itr->next()) + if (Creature* member = itr->GetSource()) + player->GetSession()->SendNameQueryOpcode(member->GetGUID()); + //end npcbot + if (sLFGMgr->selectedRandomLfgDungeon(player->GetGUID())) player->CastSpell(player, LFG_SPELL_LUCK_OF_THE_DRAW, true); } @@ -106,6 +112,9 @@ void LFGPlayerScript::OnMapChanged(Player* player) { Group* group = player->GetGroup(); if (group && group->GetMembersCount() == 1) + //npcbot + if (!player->GetSession()->PlayerLoading()) + //end npcbot { sLFGMgr->LeaveLfg(group->GetGUID()); group->Disband(); diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index 5f730c9c9..68b450b9c 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -57,6 +57,13 @@ CreatureMovementData::CreatureMovementData() : Ground(CreatureGroundMovementType::Run), Flight(CreatureFlightMovementType::None), Swim(true), Rooted(false), Chase(CreatureChaseMovementType::Run), Random(CreatureRandomMovementType::Walk), InteractionPauseTimer(sWorld->getIntConfig(CONFIG_CREATURE_STOP_FOR_PLAYER)) { } +//npcbot +#include "bot_ai.h" +#include "botdatamgr.h" +#include "botmgr.h" +#include "bpet_ai.h" +//end npcbot + std::string CreatureMovementData::ToString() const { char const* const GroundStates[] = { "None", "Run", "Hover" }; @@ -270,6 +277,11 @@ Creature::Creature(bool isWorldObject): Unit(isWorldObject), MapObject(), m_grou ResetLootMode(); // restore default loot mode m_isTempWorldObject = false; + + //npcbot + bot_AI = nullptr; + bot_pet_AI = nullptr; + //end npcbot } void Creature::AddToWorld() @@ -364,6 +376,11 @@ bool Creature::IsFormationLeaderMoveAllowed() const void Creature::RemoveCorpse(bool setSpawnTime, bool destroyForNearbyPlayers) { + //npcbot + if (IsNPCBotOrPet()) + return; + //end npcbot + if (getDeathState() != CORPSE) return; @@ -658,6 +675,29 @@ void Creature::SetPhaseMask(uint32 newPhaseMask, bool update) void Creature::Update(uint32 diff) { + //npcbot: update helper + if (bot_AI) + { + if (!bot_AI->canUpdate) + { + return; + } + + bot_AI->CommonTimers(diff); + } + else if (bot_pet_AI) + { + if (!bot_pet_AI->canUpdate) + { + //needed for delayed unsummon + m_Events.Update(diff); + return; + } + + bot_pet_AI->CommonTimers(diff); + } + //end npcbot + if (IsAIEnabled() && m_triggerJustAppeared && m_deathState != DEAD) { if (m_respawnCompatibilityMode && m_vehicleKit) @@ -680,6 +720,10 @@ void Creature::Update(uint32 diff) break; case DEAD: { + //npcbot + if (bot_AI || bot_pet_AI) + break; + //end npcbot if (!m_respawnCompatibilityMode) { TC_LOG_ERROR("entities.unit", "Creature {} in wrong state: DEAD (3)", GetGUID().ToString()); @@ -744,8 +788,21 @@ void Creature::Update(uint32 diff) } else m_groupLootTimer -= diff; } + //npcbot: update dead bots + else if (bot_AI) + { + bot_AI->UpdateDeadAI(diff); + break; + } + else if (bot_pet_AI) + break; + //end npcbot else if (m_corpseRemoveTime <= GameTime::GetGameTime()) { + //npcbot: do not remove corpse + if (IsNPCBotOrPet()) + break; + //end npcbot RemoveCorpse(false); TC_LOG_DEBUG("entities.unit", "Removing corpse... {} ", GetEntry()); } @@ -757,6 +814,9 @@ void Creature::Update(uint32 diff) // creature can be dead after Unit::Update call // CORPSE/DEAD state will processed at next tick (in other case death timer will be updated unexpectedly) + //npcbot - skip dead state for bots (handled by AI) + if (!bot_AI && !bot_pet_AI) + //end npcbot if (!IsAlive()) break; @@ -810,6 +870,11 @@ void Creature::Update(uint32 diff) Unit::AIUpdateTick(diff); + //npcbot: skip regeneration + if (bot_AI || bot_pet_AI) + break; + //end npcbot + // creature can be dead after UpdateAI call // CORPSE/DEAD state will processed at next tick (in other case death timer will be updated unexpectedly) if (!IsAlive()) @@ -995,6 +1060,11 @@ bool Creature::AIM_Create(CreatureAI* ai /*= nullptr*/) { Motion_Initialize(); + //npcbot: prevent overriding bot_AI + if (bot_AI || bot_pet_AI) + return false; + //end npcbot + SetAI(ai ? ai : FactorySelector::SelectAI(this)); return true; @@ -1267,7 +1337,16 @@ void Creature::SetLootRecipient(Unit* unit, bool withGroup) if (unit->GetTypeId() != TYPEID_PLAYER && !unit->IsVehicle()) return; + /* Player* player = unit->GetCharmerOrOwnerPlayerOrPlayerItself(); + */ + //npcbot - loot recipient of bot's vehicle is owner + Player* player = nullptr; + if (unit->IsVehicle() && unit->GetCharmerGUID().IsCreature() && unit->GetCreator() && unit->GetCreator()->IsPlayer()) + player = unit->GetCreator()->ToPlayer(); + else + player = unit->GetCharmerOrOwnerPlayerOrPlayerItself(); + //end npcbot if (!player) // normal creature, no player involved return; @@ -1298,6 +1377,11 @@ bool Creature::isTappedBy(Player const* player) const void Creature::SaveToDB() { + //npcbot: disallow saving generated bots + if (IsNPCBot() && GetBotAI() && GetBotAI()->IsWanderer()) + return; + //end npcbot + // this should only be used when the creature has already been loaded // preferably after adding to map, because mapid may not be valid otherwise CreatureData const* data = sObjectMgr->GetCreatureData(m_spawnId); @@ -1313,6 +1397,11 @@ void Creature::SaveToDB() void Creature::SaveToDB(uint32 mapid, uint8 spawnMask, uint32 phaseMask) { + //npcbot: disallow saving generated bots + if (IsNPCBot() && GetBotAI() && GetBotAI()->IsWanderer()) + return; + //end npcbot + // update in loaded data if (!m_spawnId) m_spawnId = sObjectMgr->GenerateCreatureSpawnId(); @@ -1623,6 +1712,11 @@ bool Creature::LoadFromDB(ObjectGuid::LowType spawnId, Map* map, bool addToMap, return false; } + //npcbot + if (BotDataMgr::SelectNpcBotData(data->id)) + return false; + //end npcbot + m_spawnId = spawnId; m_respawnCompatibilityMode = ((data->spawnGroupData->flags & SPAWNGROUP_FLAG_COMPATIBILITY_MODE) != 0); @@ -1638,6 +1732,11 @@ bool Creature::LoadFromDB(ObjectGuid::LowType spawnId, Map* map, bool addToMap, m_deathState = ALIVE; + //npcbot: remove respawn time if any + if (IsNPCBotOrPet()) + map->RemoveRespawnTime(SPAWN_TYPE_CREATURE, spawnId, nullptr, true); + //end npcbot + m_respawnTime = GetMap()->GetCreatureRespawnTime(m_spawnId); if (!m_respawnTime && !map->IsSpawnGroupActive(data->spawnGroupData->groupId)) @@ -1685,6 +1784,24 @@ bool Creature::LoadFromDB(ObjectGuid::LowType spawnId, Map* map, bool addToMap, // checked at creature_template loading m_defaultMovementType = MovementGeneratorType(data->movementType); + //npcbot + if (IsNPCBot()) + { + //prevent loading npcbot twice (grid unload/load case) + if (sWorld->GetMaxPlayerCount() > 0) + return false; + + TC_LOG_INFO("entities.unit", "Creature: loading npcbot {} (id: {})", GetName(), GetEntry()); + ASSERT(!IsInWorld()); + + //don't allow removing dead bot's corpse + m_respawnCompatibilityMode = true; + m_corpseDelay = 0; + m_respawnDelay = 0; + setActive(true); + } + //end npcbot + if (addToMap && !GetMap()->AddToMap(this)) return false; return true; @@ -1698,6 +1815,11 @@ void Creature::SetCanDualWield(bool value) void Creature::LoadEquipment(int8 id, bool force /*= true*/) { + //npcbot: prevent loading equipment for bots + if (IsNPCBot()) + return; + //end npcbot + if (id == 0) { if (force) @@ -1844,6 +1966,11 @@ bool Creature::IsInvisibleDueToDespawn() const if (IsAlive() || isDying() || m_corpseRemoveTime > GameTime::GetGameTime()) return false; + //npcbot + if (bot_AI || bot_pet_AI) + return false; + //end npcbot + return true; } @@ -1861,9 +1988,18 @@ bool Creature::CanStartAttack(Unit const* who, bool force) const return false; // This set of checks is should be done only for creatures + //npcbot + /* + //end npcbot if ((IsImmuneToNPC() && !who->HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED)) || (IsImmuneToPC() && who->HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED))) return false; + //npcbot + */ + if ((IsImmuneToNPC() && !(who->HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED) || who->IsNPCBotOrPet())) || + (IsImmuneToPC() && (who->HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED) || who->IsNPCBotOrPet()))) + return false; + //end npcbot // Do not attack non-combat pets if (who->GetTypeId() == TYPEID_UNIT && who->GetCreatureType() == CREATURE_TYPE_NON_COMBAT_PET) @@ -2049,6 +2185,11 @@ void Creature::setDeathState(DeathState s) void Creature::Respawn(bool force) { + //npcbot + if (IsNPCBotOrPet()) + return; + //end npcbot + if (force) { if (IsAlive()) @@ -2112,6 +2253,11 @@ void Creature::Respawn(bool force) void Creature::ForcedDespawn(uint32 timeMSToDespawn, Seconds forceRespawnTimer) { + //npcbot + if (IsNPCBotOrPet()) + return; + //end npcbot + if (timeMSToDespawn) { m_Events.AddEvent(new ForcedDespawnDelayEvent(*this, forceRespawnTimer), m_Events.CalculateTime(Milliseconds(timeMSToDespawn))); @@ -2379,6 +2525,11 @@ bool Creature::CanAssistTo(Unit const* u, Unit const* enemy, bool checkfaction / if (GetCharmerOrOwnerGUID()) return false; + //npcbot + if (IsNPCBotOrPet()) + return false; + //end npcbot + // only from same creature faction if (checkfaction) { @@ -2438,6 +2589,12 @@ void Creature::SaveRespawnTime(uint32 forceDelay) ri.type = SPAWN_TYPE_CREATURE; ri.spawnId = m_spawnId; ri.respawnTime = m_respawnTime; + + //npcbot: save entry for checks + if (IsNPCBot()) + ri.entry = GetEntry(); + //end npcbot + GetMap()->SaveRespawnInfoDB(ri); return; } @@ -2587,6 +2744,16 @@ void Creature::SendZoneUnderAttackMessage(Player* attacker) uint32 Creature::GetShieldBlockValue() const //dunno mob block value { + //npcbot - bot block value is fully calculated inside botAI + if (bot_AI) + { + uint32 blockValue = bot_AI->GetShieldBlockValue(); + blockValue += GetTotalAuraModifier(SPELL_AURA_MOD_SHIELD_BLOCKVALUE); + blockValue *= GetTotalAuraMultiplier(SPELL_AURA_MOD_SHIELD_BLOCKVALUE_PCT); + return uint32(blockValue); + } + //end npcbot + return (GetLevel()/2 + uint32(GetStat(STAT_STRENGTH)/20)); } @@ -2645,6 +2812,17 @@ void Creature::UpdateMovementFlags() if (IsMovedByClient()) return; + //npcbot: do not update movement flags for vehicles controlled by npcbots + if (GetCharmerGUID().IsCreature()) + { + if (CreatureTemplate const* bot_template = sObjectMgr->GetCreatureTemplate(GetCharmerGUID().GetEntry())) + { + if (bot_template->IsNPCBot()) + return; + } + } + //end npcbot + // Creatures with CREATURE_FLAG_EXTRA_NO_MOVE_FLAGS_UPDATE should control MovementFlags in your own scripts if (GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_NO_MOVE_FLAGS_UPDATE) return; @@ -2719,6 +2897,11 @@ void Creature::RefreshCanSwimFlag(bool recheck) void Creature::AllLootRemovedFromCorpse() { + //npcbot + if (IsNPCBotOrPet()) + return; + //end npcbot + if (loot.loot_type != LOOT_SKINNING && !IsPet() && GetCreatureTemplate()->SkinLootId && hasLootRecipient()) if (LootTemplates_Skinning.HaveLootFor(GetCreatureTemplate()->SkinLootId)) SetUnitFlag(UNIT_FLAG_SKINNABLE); @@ -3110,6 +3293,15 @@ void Creature::SetDisplayId(uint32 modelId) SetBoundingRadius((IsPet() ? 1.0f : minfo->bounding_radius) * GetObjectScale()); SetCombatReach((IsPet() ? DEFAULT_PLAYER_COMBAT_REACH : minfo->combat_reach) * GetObjectScale()); } + + //npcbot: send group update for bot pet + if (IsNPCBotPet()) + { + if (Creature const* botPetOwner = GetBotPetAI() ? GetBotPetAI()->GetPetsOwner() : nullptr) + if (botPetOwner->GetBotAI()->GetGroup()) + BotMgr::SetBotGroupUpdateFlag(botPetOwner, GROUP_UPDATE_FLAG_PET_MODEL_ID); + } + //end npcbot } void Creature::SetTarget(ObjectGuid guid) @@ -3209,6 +3401,13 @@ void Creature::ReleaseSpellFocus(Spell const* focusSpell, bool withDelay) if (!HasUnitFlag2(UNIT_FLAG2_CANNOT_TURN)) ReacquireSpellFocusTarget(); } + //npcbot: bots and botpets do not use delay + else if (IsNPCBot() || IsNPCBotPet()) + { + if (!HasUnitFlag2(UNIT_FLAG2_CANNOT_TURN)) + ReacquireSpellFocusTarget(); + } + //end npcbot else // don't allow re-target right away to prevent visual bugs _spellFocusInfo.Delay = withDelay ? 1000 : 1; @@ -3384,3 +3583,383 @@ void Creature::ExitVehicle(Position const* /*exitPosition*/) if (isInVehicle && IsAlive()) SetHomePosition(GetPosition()); } + +//NPCBOT +bool Creature::LoadBotCreatureFromDB(ObjectGuid::LowType spawnId, Map* map, bool addToMap, bool generated, uint32 entry, Position const* pos) +{ + CreatureData const* data = generated ? nullptr : sObjectMgr->GetCreatureData(spawnId); + if (!data) + { + if (!generated) + { + TC_LOG_ERROR("sql.sql", "Bot creature (GUID: {}) not found in table `creature`, can't load. ", spawnId); + return false; + } + else + { + ASSERT(entry != 0); + ASSERT_NOTNULL(pos); + } + } + + m_spawnId = spawnId; + ASSERT(map->GetInstanceId() == 0); + + m_respawnCompatibilityMode = true; + m_creatureData = data; + m_wanderDistance = data ? data->wander_distance : 0.f; + + if (!Create(map->GenerateLowGuid(), map, + data ? data->phaseMask : PHASEMASK_NORMAL, + data ? data->id : entry, data ? data->spawnPoint : *pos, + data, 0U, !m_respawnCompatibilityMode)) + return false; + + //We should set first home position, because then AI calls home movement + SetHomePosition(*this); + + m_deathState = ALIVE; + m_respawnTime = 0; + + SetSpawnHealth(); + + // checked at creature_template loading + m_defaultMovementType = data ? MovementGeneratorType(data->movementType) : IDLE_MOTION_TYPE; + + TC_LOG_INFO("entities.unit", "Creature: loading npcbot {} (id: {}, gen: {})", GetName(), GetEntry(), uint32(generated)); + ASSERT(!IsInWorld()); + + m_corpseDelay = 0; + m_respawnDelay = 0; + setActive(true); + + if (addToMap && !GetMap()->AddToMap(this)) + return false; + + return true; +} + +uint8 Creature::GetBotClass() const +{ + return bot_AI ? bot_AI->GetBotClass() : GetClass(); +} + +Player* Creature::GetBotOwner() const +{ + return bot_AI ? bot_AI->GetBotOwner() : bot_pet_AI ? bot_pet_AI->GetPetsOwner()->GetBotOwner() : nullptr; +} +Unit* Creature::GetBotsPet() const +{ + return bot_AI ? bot_AI->GetBotsPet() : nullptr; +} + +bool Creature::IsNPCBot() const +{ + return GetCreatureTemplate()->IsNPCBot(); +} + +bool Creature::IsNPCBotPet() const +{ + return GetCreatureTemplate()->IsNPCBotPet(); +} + +bool Creature::IsNPCBotOrPet() const +{ + return GetCreatureTemplate()->IsNPCBotOrPet(); +} + +bool Creature::IsFreeBot() const +{ + return bot_AI ? bot_AI->IAmFree() : bot_pet_AI ? bot_pet_AI->IAmFree() : false; +} + +bool Creature::IsWandererBot() const +{ + return bot_AI ? bot_AI->IsWanderer() : bot_pet_AI ? bot_pet_AI->IsWanderer() : false; +} + +Group* Creature::GetBotGroup() const +{ + return bot_AI ? bot_AI->GetGroup() : nullptr; +} +void Creature::SetBotGroup(Group* group, int8 subgroup) +{ + if (bot_AI) + bot_AI->SetGroup(group, subgroup); +} +uint8 Creature::GetSubGroup() const +{ + return bot_AI ? bot_AI->GetSubGroup() : 0; +} +void Creature::SetSubGroup(uint8 subgroup) +{ + if (bot_AI) + bot_AI->SetSubGroup(subgroup); +} + +void Creature::SetBattlegroundOrBattlefieldRaid(Group* group, int8 subgroup) +{ + if (bot_AI) + bot_AI->SetBattlegroundOrBattlefieldRaid(group, subgroup); +} +void Creature::RemoveFromBattlegroundOrBattlefieldRaid() +{ + if (bot_AI) + bot_AI->RemoveFromBattlegroundOrBattlefieldRaid(); +} +Group* Creature::GetOriginalGroup() const +{ + return bot_AI ? bot_AI->GetOriginalGroup() : nullptr; +} +void Creature::SetOriginalGroup(Group* group, int8 subgroup) +{ + if (bot_AI) + bot_AI->SetOriginalGroup(group, subgroup); +} +uint8 Creature::GetOriginalSubGroup() const +{ + return bot_AI ? bot_AI->GetOriginalSubGroup() : 0; +} +void Creature::SetOriginalSubGroup(uint8 subgroup) +{ + if (bot_AI) + bot_AI->SetOriginalSubGroup(subgroup); +} + +Battleground* Creature::GetBotBG() const +{ + return bot_AI ? bot_AI->GetBG() : nullptr; +} + +uint32 Creature::GetBotRoles() const +{ + return bot_AI ? bot_AI->GetBotRoles() : 0; +} +//Bot damage mods +void Creature::ApplyBotDamageMultiplierMelee(uint32& damage, CalcDamageInfo& damageinfo) const +{ + if (bot_AI) + bot_AI->ApplyBotDamageMultiplierMelee(damage, damageinfo); +} +void Creature::ApplyBotDamageMultiplierMelee(int32& damage, SpellNonMeleeDamage& damageinfo, SpellInfo const* spellInfo, WeaponAttackType attackType, bool crit) const +{ + if (bot_AI) + bot_AI->ApplyBotDamageMultiplierMelee(damage, damageinfo, spellInfo, attackType, crit); +} +void Creature::ApplyBotDamageMultiplierSpell(int32& damage, SpellNonMeleeDamage& damageinfo, SpellInfo const* spellInfo, WeaponAttackType attackType, bool crit) const +{ + if (bot_AI) + bot_AI->ApplyBotDamageMultiplierSpell(damage, damageinfo, spellInfo, attackType, crit); + else if (bot_pet_AI) + bot_pet_AI->ApplyBotDamageMultiplierSpell(damage, damageinfo, spellInfo, attackType, crit); +} +void Creature::ApplyBotDamageMultiplierHeal(Unit const* victim, float& heal, SpellInfo const* spellInfo, DamageEffectType damagetype, uint32 stack) const +{ + if (bot_AI) + bot_AI->ApplyBotDamageMultiplierHeal(victim, heal, spellInfo, damagetype, stack); +} +void Creature::ApplyBotCritMultiplierAll(Unit const* victim, float& crit_chance, SpellInfo const* spellInfo, SpellSchoolMask schoolMask, WeaponAttackType attackType) const +{ + if (bot_AI) + bot_AI->ApplyBotCritMultiplierAll(victim, crit_chance, spellInfo, schoolMask, attackType); +} +void Creature::ApplyCreatureSpellCostMods(SpellInfo const* spellInfo, int32& cost) const +{ + if (bot_AI) + bot_AI->ApplyBotSpellCostMods(spellInfo, cost); +} +void Creature::ApplyCreatureSpellCastTimeMods(SpellInfo const* spellInfo, int32& casttime) const +{ + if (bot_AI) + bot_AI->ApplyBotSpellCastTimeMods(spellInfo, casttime); +} +void Creature::ApplyCreatureSpellRadiusMods(SpellInfo const* spellInfo, float& radius) const +{ + if (bot_AI) + bot_AI->ApplyBotSpellRadiusMods(spellInfo, radius); + else if (bot_pet_AI) + bot_pet_AI->ApplyBotPetSpellRadiusMods(spellInfo, radius); +} +void Creature::ApplyCreatureSpellRangeMods(SpellInfo const* spellInfo, float& maxrange) const +{ + if (bot_AI) + bot_AI->ApplyBotSpellRangeMods(spellInfo, maxrange); +} +void Creature::ApplyCreatureSpellMaxTargetsMods(SpellInfo const* spellInfo, uint32& targets) const +{ + if (bot_AI) + bot_AI->ApplyBotSpellMaxTargetsMods(spellInfo, targets); +} +void Creature::ApplyCreatureSpellChanceOfSuccessMods(SpellInfo const* spellInfo, float& chance) const +{ + if (bot_AI) + bot_AI->ApplyBotSpellChanceOfSuccessMods(spellInfo, chance); +} + +void Creature::ApplyCreatureEffectMods(SpellInfo const* spellInfo, uint8 effIndex, float& value) const +{ + if (bot_AI) + bot_AI->ApplyBotEffectMods(spellInfo, effIndex, value); +} + +void Creature::OnBotSummon(Creature* summon) +{ + if (bot_AI) + bot_AI->OnBotSummon(summon); +} +void Creature::OnBotDespawn(Creature* summon) +{ + if (bot_AI) + bot_AI->OnBotDespawn(summon); +} + +void Creature::BotStopMovement() +{ + if (IsInWorld()) + { + GetMotionMaster()->Clear(); + GetMotionMaster()->MoveIdle(); + } + StopMoving(); + DisableSpline(); +} + +bool Creature::CanParry() const +{ + return bot_AI ? bot_AI->CanParry() : true; +} + +bool Creature::CanDodge() const +{ + return bot_AI ? bot_AI->CanDodge() : true; +} +//unused +bool Creature::CanBlock() const +{ + return bot_AI ? bot_AI->CanBlock() : true; +} +//unused +bool Creature::CanCrit() const +{ + return bot_AI ? bot_AI->CanCrit() : true; +} +bool Creature::CanMiss() const +{ + return bot_AI ? bot_AI->CanMiss() : true; +} + +float Creature::GetCreatureParryChance() const +{ + return bot_AI ? bot_AI->GetBotParryChance() : 5.0f; +} +float Creature::GetCreatureDodgeChance() const +{ + return bot_AI ? bot_AI->GetBotDodgeChance() : 5.0f; +} +float Creature::GetCreatureBlockChance() const +{ + return bot_AI ? bot_AI->GetBotBlockChance() : 5.0f; +} +float Creature::GetCreatureCritChance() const +{ + return bot_AI ? bot_AI->GetBotCritChance() : 0.0f; +} +float Creature::GetCreatureMissChance() const +{ + return bot_AI ? bot_AI->GetBotMissChance() : 5.0f; +} +float Creature::GetCreatureArmorPenetrationCoef() const +{ + return bot_AI ? bot_AI->GetBotArmorPenetrationCoef() : 0.0f; +} +uint32 Creature::GetCreatureExpertise() const +{ + return bot_AI ? bot_AI->GetBotExpertise() : 0; +} +uint32 Creature::GetCreatureSpellPenetration() const +{ + return bot_AI ? bot_AI->GetBotSpellPenetration() : 0; +} +uint32 Creature::GetCreatureSpellPower() const +{ + return bot_AI ? bot_AI->GetBotSpellPower() : 0; +} +uint32 Creature::GetCreatureDefense() const +{ + return bot_AI ? bot_AI->GetBotDefense() : GetMaxSkillValueForLevel(); +} +int32 Creature::GetCreatureResistanceBonus(SpellSchoolMask mask) const +{ + return bot_AI ? bot_AI->GetBotResistanceBonus(mask) : 0; +} + +uint8 Creature::GetCreatureComboPoints() const +{ + return bot_AI ? bot_AI->GetBotComboPoints() : 0; +} + +float Creature::GetCreatureAmmoDPS() const +{ + return bot_AI ? bot_AI->GetBotAmmoDPS() : 0.0f; +} + +bool Creature::IsTempBot() const +{ + return bot_AI && bot_AI->IsTempBot(); +} + +MeleeHitOutcome Creature::BotRollMeleeOutcomeAgainst(Unit const* victim, WeaponAttackType attType) const +{ + return bot_AI ? bot_AI->BotRollCustomMeleeOutcomeAgainst(victim, attType) : RollMeleeOutcomeAgainst(victim, attType); +} + +void Creature::CastCreatureItemCombatSpell(DamageInfo const& damageInfo) +{ + if (bot_AI) + bot_AI->CastBotItemCombatSpell(damageInfo); +} + +bool Creature::HasSpellCooldown(uint32 spell_id) const +{ + if (bot_AI) + return !bot_AI->IsSpellReady(sSpellMgr->GetSpellInfo(spell_id)->GetFirstRankSpell()->Id, bot_AI->GetLastDiff(), false); + else if (bot_pet_AI) + return !bot_pet_AI->IsSpellReady(sSpellMgr->GetSpellInfo(spell_id)->GetFirstRankSpell()->Id, bot_pet_AI->GetLastDiff(), false); + + return false; +} +void Creature::AddBotSpellCooldown(uint32 spellId, uint32 cooldown) +{ + if (bot_AI) + bot_AI->SetSpellCooldown(sSpellMgr->GetSpellInfo(spellId)->GetFirstRankSpell()->Id, cooldown); + else if (bot_pet_AI) + bot_pet_AI->SetSpellCooldown(sSpellMgr->GetSpellInfo(spellId)->GetFirstRankSpell()->Id, cooldown); +} +void Creature::ReleaseBotSpellCooldown(uint32 spellId) +{ + if (bot_AI) + bot_AI->ReleaseSpellCooldown(sSpellMgr->GetSpellInfo(spellId)->GetFirstRankSpell()->Id); + else if (bot_pet_AI) + bot_pet_AI->ReleaseSpellCooldown(sSpellMgr->GetSpellInfo(spellId)->GetFirstRankSpell()->Id); +} + +void Creature::SpendBotRunes(SpellInfo const* spellInfo, bool didHit) +{ + if (bot_AI) + bot_AI->SpendRunes(spellInfo, didHit); +} + +//equips +Item* Creature::GetBotEquips(uint8 slot) const +{ + return bot_AI ? bot_AI->GetEquips(slot) : nullptr; +} +Item* Creature::GetBotEquipsByGuid(ObjectGuid itemGuid) const +{ + return bot_AI ? bot_AI->GetEquipsByGuid(itemGuid) : nullptr; +} +float Creature::GetBotAverageItemLevel() const +{ + return bot_AI ? bot_AI->GetAverageItemLevel() : 0.0f; +} +//END NPCBOT diff --git a/src/server/game/Entities/Creature/Creature.h b/src/server/game/Entities/Creature/Creature.h index 8a65ca537..549046b70 100644 --- a/src/server/game/Entities/Creature/Creature.h +++ b/src/server/game/Entities/Creature/Creature.h @@ -28,6 +28,12 @@ #include "MapObject.h" #include +// npcbot +class bot_ai; +class bot_pet_ai; +class Battleground; +//end npcbot + class CreatureAI; class CreatureGroup; class Group; @@ -374,6 +380,85 @@ class TC_GAME_API Creature : public Unit, public GridObject, public Ma void ExitVehicle(Position const* exitPosition = nullptr) override; + //NPCBots + bool LoadBotCreatureFromDB(ObjectGuid::LowType guid, Map* map, bool addToMap = true, bool generated = false, uint32 entry = 0, Position const* pos = nullptr); + Player* GetBotOwner() const; + Unit* GetBotsPet() const; + bool IsNPCBot() const override; + bool IsNPCBotPet() const override; + bool IsNPCBotOrPet() const override; + bool IsFreeBot() const; + bool IsWandererBot() const; + Group* GetBotGroup() const; + void SetBotGroup(Group* group, int8 subgroup = -1); + uint8 GetSubGroup() const; + void SetSubGroup(uint8 subgroup); + void SetBattlegroundOrBattlefieldRaid(Group* group, int8 subgroup = -1); + void RemoveFromBattlegroundOrBattlefieldRaid(); + Group* GetOriginalGroup() const; + void SetOriginalGroup(Group* group, int8 subgroup = -1); + uint8 GetOriginalSubGroup() const; + void SetOriginalSubGroup(uint8 subgroup); + Battleground* GetBotBG() const; + uint8 GetBotClass() const; + uint32 GetBotRoles() const; + bot_ai* GetBotAI() const { return bot_AI; } + bot_pet_ai* GetBotPetAI() const { return bot_pet_AI; } + void SetBotAI(bot_ai* ai) { bot_AI = ai; } + void SetBotPetAI(bot_pet_ai* ai) { bot_pet_AI = ai; } + void ApplyBotDamageMultiplierMelee(uint32& damage, CalcDamageInfo& damageinfo) const; + void ApplyBotDamageMultiplierMelee(int32& damage, SpellNonMeleeDamage& damageinfo, SpellInfo const* spellInfo, WeaponAttackType attackType, bool crit) const; + void ApplyBotDamageMultiplierSpell(int32& damage, SpellNonMeleeDamage& damageinfo, SpellInfo const* spellInfo, WeaponAttackType attackType, bool crit) const; + void ApplyBotDamageMultiplierHeal(Unit const* victim, float& heal, SpellInfo const* spellInfo, DamageEffectType damagetype, uint32 stack) const; + void ApplyBotCritMultiplierAll(Unit const* victim, float& crit_chance, SpellInfo const* spellInfo, SpellSchoolMask schoolMask, WeaponAttackType attackType) const; + void ApplyCreatureSpellCostMods(SpellInfo const* spellInfo, int32& cost) const; + void ApplyCreatureSpellCastTimeMods(SpellInfo const* spellInfo, int32& casttime) const; + void ApplyCreatureSpellRadiusMods(SpellInfo const* spellInfo, float& radius) const; + void ApplyCreatureSpellRangeMods(SpellInfo const* spellInfo, float& maxrange) const; + void ApplyCreatureSpellMaxTargetsMods(SpellInfo const* spellInfo, uint32& targets) const; + void ApplyCreatureSpellChanceOfSuccessMods(SpellInfo const* spellInfo, float& chance) const; + void ApplyCreatureEffectMods(SpellInfo const* spellInfo, uint8 effIndex, float& value) const; + void OnBotSummon(Creature* summon); + void OnBotDespawn(Creature* summon); + void BotStopMovement(); + + bool CanParry() const; + bool CanDodge() const; + bool CanBlock() const; + bool CanCrit() const; + bool CanMiss() const; + + float GetCreatureParryChance() const; + float GetCreatureDodgeChance() const; + float GetCreatureBlockChance() const; + float GetCreatureCritChance() const; + float GetCreatureMissChance() const; + float GetCreatureArmorPenetrationCoef() const; + uint32 GetCreatureExpertise() const; + uint32 GetCreatureSpellPenetration() const; + uint32 GetCreatureSpellPower() const; + uint32 GetCreatureDefense() const; + int32 GetCreatureResistanceBonus(SpellSchoolMask mask) const; + uint8 GetCreatureComboPoints() const; + float GetCreatureAmmoDPS() const; + + bool IsTempBot() const; + + MeleeHitOutcome BotRollMeleeOutcomeAgainst(Unit const* victim, WeaponAttackType attType) const; + + void CastCreatureItemCombatSpell(DamageInfo const& damageInfo); + + bool HasSpellCooldown(uint32 spellId) const; + void AddBotSpellCooldown(uint32 spellId, uint32 cooldown); + void ReleaseBotSpellCooldown(uint32 spellId); + + void SpendBotRunes(SpellInfo const* spellInfo, bool didHit); + + Item* GetBotEquips(uint8 slot) const; + Item* GetBotEquipsByGuid(ObjectGuid itemGuid) const; + float GetBotAverageItemLevel() const; + //End NPCBots + protected: bool CreateFromProto(ObjectGuid::LowType guidlow, uint32 entry, CreatureData const* data = nullptr, uint32 vehId = 0); bool InitEntry(uint32 entry, CreatureData const* data = nullptr); @@ -428,6 +513,11 @@ class TC_GAME_API Creature : public Unit, public GridObject, public Ma bool CanAlwaysSee(WorldObject const* obj) const override; private: + //bot system + bot_ai* bot_AI; + bot_pet_ai* bot_pet_AI; + //end bot system + void ForcedDespawn(uint32 timeMSToDespawn = 0, Seconds forceRespawnTimer = 0s); bool CheckNoGrayAggroConfig(uint32 playerLevel, uint32 creatureLevel) const; // No aggro from gray creatures diff --git a/src/server/game/Entities/Creature/CreatureData.h b/src/server/game/Entities/Creature/CreatureData.h index 325bc22ab..8d631ff9c 100644 --- a/src/server/game/Entities/Creature/CreatureData.h +++ b/src/server/game/Entities/Creature/CreatureData.h @@ -212,9 +212,12 @@ enum CreatureFlagsExtra : uint32 CREATURE_FLAG_EXTRA_UNUSED_31 = 0x80000000, // Masks - CREATURE_FLAG_EXTRA_UNUSED = (CREATURE_FLAG_EXTRA_UNUSED_22 | - CREATURE_FLAG_EXTRA_UNUSED_23 | CREATURE_FLAG_EXTRA_UNUSED_24 | CREATURE_FLAG_EXTRA_UNUSED_25 | - CREATURE_FLAG_EXTRA_UNUSED_26 | CREATURE_FLAG_EXTRA_UNUSED_27 | CREATURE_FLAG_EXTRA_UNUSED_31), // SKIP + //npcbot + CREATURE_FLAG_EXTRA_NPCBOT = (CREATURE_FLAG_EXTRA_UNUSED_31 | CREATURE_FLAG_EXTRA_UNUSED_25 | CREATURE_FLAG_EXTRA_UNUSED_26 | CREATURE_FLAG_EXTRA_UNUSED_27), + CREATURE_FLAG_EXTRA_NPCBOT_PET = (CREATURE_FLAG_EXTRA_UNUSED_31 | CREATURE_FLAG_EXTRA_UNUSED_25 | CREATURE_FLAG_EXTRA_UNUSED_27), + //end npcbot + + CREATURE_FLAG_EXTRA_UNUSED = (CREATURE_FLAG_EXTRA_UNUSED_22 | CREATURE_FLAG_EXTRA_UNUSED_23 | CREATURE_FLAG_EXTRA_UNUSED_24), // SKIP CREATURE_FLAG_EXTRA_DB_ALLOWED = (0xFFFFFFFF & ~(CREATURE_FLAG_EXTRA_UNUSED | CREATURE_FLAG_EXTRA_DUNGEON_BOSS)) // SKIP }; @@ -356,6 +359,21 @@ struct TC_GAME_API CreatureTemplate uint32 GetFirstVisibleModel() const; // helpers + //npcbot + inline bool IsNPCBot() const + { + return (flags_extra & CREATURE_FLAG_EXTRA_NPCBOT) == CREATURE_FLAG_EXTRA_NPCBOT; + } + inline bool IsNPCBotPet() const + { + return (flags_extra & CREATURE_FLAG_EXTRA_NPCBOT) == CREATURE_FLAG_EXTRA_NPCBOT_PET; + } + inline bool IsNPCBotOrPet() const + { + return IsNPCBot() || IsNPCBotPet(); + } + //end npcbot + SkillType GetRequiredLootSkill() const { if (type_flags & CREATURE_TYPE_FLAG_SKIN_WITH_HERBALISM) diff --git a/src/server/game/Entities/Creature/TemporarySummon.cpp b/src/server/game/Entities/Creature/TemporarySummon.cpp index ccd95f385..cdc016147 100644 --- a/src/server/game/Entities/Creature/TemporarySummon.cpp +++ b/src/server/game/Entities/Creature/TemporarySummon.cpp @@ -26,6 +26,11 @@ #include "Pet.h" #include "Player.h" +//npcbot +#include "botmgr.h" +#include "bpet_ai.h" +//end npcbot + TempSummon::TempSummon(SummonPropertiesEntry const* properties, WorldObject* owner, bool isWorldObject) : Creature(isWorldObject), m_Properties(properties), m_type(TEMPSUMMON_MANUAL_DESPAWN), m_timer(0), m_lifetime(0), m_canFollowOwner(true), m_visibleBySummonerOnly(false) @@ -196,6 +201,12 @@ void TempSummon::InitStats(uint32 duration) if (!m_Properties) return; + //npcbot: skip deleting/reassigning player totems + //normally no creatorGUID is assigned at this point, perform full check anyway for compatibilty reasons + if (!(m_Properties->Slot && m_Properties->Slot >= SUMMON_SLOT_TOTEM_FIRE && m_Properties->Slot < MAX_TOTEM_SLOT && + GetCreatorGUID() && GetCreatorGUID().IsCreature() && owner && owner->GetTypeId() == TYPEID_PLAYER && + owner->ToPlayer()->HaveBot() && owner->ToPlayer()->GetBotMgr()->GetBot(GetCreatorGUID()))) + //end npcbot if (owner) { if (uint32 slot = m_Properties->Slot) @@ -264,6 +275,14 @@ void TempSummon::UnSummon(uint32 msTime) return; } + //npcbot + if (IsNPCBotPet()) + { + if (Creature* petowner = GetBotPetAI()->GetPetsOwner()) + petowner->AI()->SummonedCreatureDespawn(this); + } + else + //end npcbot if (WorldObject * owner = GetSummoner()) { if (owner->GetTypeId() == TYPEID_UNIT && owner->ToCreature()->IsAIEnabled()) @@ -322,6 +341,15 @@ void Minion::InitStats(uint32 duration) SetReactState(REACT_PASSIVE); + //npcbot + //do not add bot totem to player's controlled list + //client indicator will be OwnerGUID + if (m_Properties && m_Properties->Slot && m_Properties->Slot >= SUMMON_SLOT_TOTEM_FIRE && m_Properties->Slot < MAX_TOTEM_SLOT && + GetCreatorGUID() && GetCreatorGUID().IsCreature() && GetOwner() && GetOwner()->GetTypeId() == TYPEID_PLAYER && + GetOwner()->ToPlayer()->HaveBot() && GetOwner()->ToPlayer()->GetBotMgr()->GetBot(GetCreatorGUID())) + return; + //end npcbot + SetCreatorGUID(GetOwner()->GetGUID()); SetFaction(GetOwner()->GetFaction()); diff --git a/src/server/game/Entities/GameObject/GameObject.cpp b/src/server/game/Entities/GameObject/GameObject.cpp index d893a357b..ec602c81b 100644 --- a/src/server/game/Entities/GameObject/GameObject.cpp +++ b/src/server/game/Entities/GameObject/GameObject.cpp @@ -1919,6 +1919,39 @@ void GameObject::Use(Unit* user) case GAMEOBJECT_TYPE_SUMMONING_RITUAL: //18 { + //npcbot + if (user->IsNPCBot()) + { + GameObjectTemplate const* info = GetGOInfo(); + Player* botOwner = user->ToCreature()->GetBotOwner(); + spellCaster = botOwner; + + if (info->summoningRitual.animSpell) + { + user->CastSpell(user, info->summoningRitual.animSpell, true); + triggered = true; + } + + spellId = info->summoningRitual.spellId; + if (spellId == 62330) + { + spellId = 61993; + triggered = true; + } + if (!info->summoningRitual.ritualPersistent) + SetLootState(GO_JUST_DEACTIVATED); + else + { + // reset ritual for this GO + m_ritualOwnerGUID.Clear(); + m_unique_users.clear(); + m_usetimes = 0; + } + + break; + } + //end npcbot + if (user->GetTypeId() != TYPEID_PLAYER) return; @@ -2074,6 +2107,20 @@ void GameObject::Use(Unit* user) case GAMEOBJECT_TYPE_FLAGSTAND: // 24 { + //npcbot + if (user->IsNPCBot()) + { + Creature* bot = user->ToCreature(); + if (Battleground* botbg = bot->GetBotBG()) + { + bot->RemoveAurasByType(SPELL_AURA_MOD_STEALTH); + bot->RemoveAurasByType(SPELL_AURA_MOD_INVISIBILITY); + botbg->EventBotClickedOnFlag(bot, this); + return; + } + } + //end npcbot + if (user->GetTypeId() != TYPEID_PLAYER) return; @@ -2118,6 +2165,38 @@ void GameObject::Use(Unit* user) case GAMEOBJECT_TYPE_FLAGDROP: // 26 { + //npcbot + if (user->IsNPCBot()) + { + Creature* bot = user->ToCreature(); + if (Battleground* botbg = bot->GetBotBG()) + { + bot->RemoveAurasByType(SPELL_AURA_MOD_STEALTH); + bot->RemoveAurasByType(SPELL_AURA_MOD_INVISIBILITY); + + if (GameObjectTemplate const* bgoinfo = GetGOInfo()) + { + switch (bgoinfo->entry) + { + case 179785: // Silverwing Flag + case 179786: // Warsong Flag + if (botbg->GetTypeID(true) == BATTLEGROUND_WS) + botbg->EventBotClickedOnFlag(bot, this); + break; + case 184142: // Netherstorm Flag + if (botbg->GetTypeID(true) == BATTLEGROUND_EY) + botbg->EventBotClickedOnFlag(bot, this); + break; + } + } + //this cause to call return, all flags must be deleted here!! + spellId = 0; + Delete(); + break; + } + } + //end npcbot + if (user->GetTypeId() != TYPEID_PLAYER) return; diff --git a/src/server/game/Entities/Object/Object.cpp b/src/server/game/Entities/Object/Object.cpp index 059fb6ebc..b42e5bbd9 100644 --- a/src/server/game/Entities/Object/Object.cpp +++ b/src/server/game/Entities/Object/Object.cpp @@ -51,6 +51,11 @@ #include "World.h" #include +//npcbot +#include "botdatamgr.h" +#include "botmgr.h" +//end npcbot + constexpr float VisibilityDistances[AsUnderlyingType(VisibilityDistanceType::Max)] = { DEFAULT_VISIBILITY_DISTANCE, @@ -1006,6 +1011,11 @@ void WorldObject::setActive(bool on) if (GetTypeId() == TYPEID_PLAYER) return; + //npcbot: bots should never be removed from active + if (on == false && IsNPCBotOrPet()) + return; + //end npcbot + m_isActive = on; if (on && !IsInWorld()) @@ -1644,7 +1654,26 @@ bool WorldObject::CanDetect(WorldObject const* obj, bool implicitDetect, bool ch { WorldObject const* seer = this; - // If a unit is possessing another one, it uses the detection of the latter + //npcbot: master's sight only partially affects bots + if (IsNPCBot()) + { + Unit const* owner = ToCreature()->GetBotOwner(); + if (!owner) + owner = ToUnit(); + + if (!obj->IsAlwaysDetectableFor(seer) && !obj->IsAlwaysDetectableFor(owner) && !implicitDetect) + { + if (!seer->CanDetectInvisibilityOf(obj) && !(owner->IsInWorld() && owner->GetMap()->IsDungeon() && owner->CanDetectInvisibilityOf(obj))) + return false; + + if (!seer->CanDetectStealthOf(obj, checkAlert)) + return false; + } + + return true; + } + //end npcbot + // Pets don't have detection, they use the detection of their masters if (Unit const* thisUnit = ToUnit()) { @@ -1927,6 +1956,11 @@ TempSummon* Map::SummonCreature(uint32 entry, Position const& pos, SummonPropert summon = new Puppet(properties, summonerUnit); break; case UNIT_MASK_TOTEM: + //npcbot: totem emul step 1 + if (summoner && summoner->IsNPCBot()) + summon = new Totem(properties, summoner->ToCreature()->GetBotOwner()); + else + //end npcbot summon = new Totem(properties, summonerUnit); break; case UNIT_MASK_MINION: @@ -1940,6 +1974,11 @@ TempSummon* Map::SummonCreature(uint32 entry, Position const& pos, SummonPropert return nullptr; } + //npcbot: totem emul step 2 + if (summoner && summoner->IsNPCBot()) + summon->SetCreatorGUID(summoner->GetGUID()); // see TempSummon::InitStats() + //end npcbot + summon->SetCreatedBySpell(spellId); summon->SetHomePosition(pos); @@ -1951,6 +1990,11 @@ TempSummon* Map::SummonCreature(uint32 entry, Position const& pos, SummonPropert AddToMap(summon->ToCreature()); summon->InitSummon(); + //npcbot: totem emul step 3 + if (summoner && summoner->IsNPCBot()) + summoner->ToCreature()->OnBotSummon(summon); + //end npcbot + // call MoveInLineOfSight for nearby creatures Trinity::AIRelocationNotifier notifier(*summon); Cell::VisitAllObjects(summon, notifier, GetVisibilityRange()); @@ -2188,11 +2232,23 @@ Player* WorldObject::GetCharmerOrOwnerPlayerOrPlayerItself() const if (guid.IsPlayer()) return ObjectAccessor::GetPlayer(*this, guid); + //npcbot + if (GetTypeId() == TYPEID_UNIT && ToCreature()->IsNPCBotOrPet()) + if (Unit* creator = ToUnit()->GetCreator()) + return creator->ToPlayer(); + //end npcbot + return const_cast(this)->ToPlayer(); } Player* WorldObject::GetAffectingPlayer() const { + //npcbot: affecting player is creator + if (GetTypeId() == TYPEID_UNIT && ToCreature()->IsNPCBotOrPet()) + if (Unit* creator = ToUnit()->GetCreator()) + return creator->ToPlayer(); + //end npcbot + if (!GetCharmerOrOwnerGUID()) return const_cast(this)->ToPlayer(); @@ -2278,12 +2334,36 @@ float WorldObject::ApplyEffectModifiers(SpellInfo const* spellInfo, uint8 effInd break; } } + + //npcbot: handle effect mods + if (IsNPCBot()) + ToCreature()->ApplyCreatureEffectMods(spellInfo, effIndex, value); + //end npcbot + return value; } int32 WorldObject::CalcSpellDuration(SpellInfo const* spellInfo) const { uint8 comboPoints = 0; + //npcbot + if (IsNPCBot()) + comboPoints = ToCreature()->GetCreatureComboPoints(); + else + //npcbot: combo points support for spell duration (vehicle) + if (ToCreature() && ToCreature()->IsVehicle() && ToCreature()->GetCharmerGUID().IsCreature() && + spellInfo->GetDuration() != spellInfo->GetMaxDuration()) + { + Unit const* bot = ToCreature()->GetCharmer(); + if (bot && bot->IsNPCBot()) + { + comboPoints = bot->ToCreature()->GetCreatureComboPoints(); + //TC_LOG_ERROR("scripts", "CalcSpellDuration bot {} veh spell {} cp {}", + // bot->GetName(), spellProto->Id, uint32(comboPoints)); + } + } + else + //end npcbot if (Unit const* unit = ToUnit()) comboPoints = unit->GetComboPoints(); @@ -2402,6 +2482,11 @@ void WorldObject::ModSpellCastTime(SpellInfo const* spellInfo, int32& castTime, if (Player* modOwner = GetSpellModOwner()) modOwner->ApplySpellMod(spellInfo->Id, SPELLMOD_CASTING_TIME, castTime, spell); + //npcbot - apply bot spell cast time mods + if (castTime > 0 && IsNPCBot()) + ToCreature()->ApplyCreatureSpellCastTimeMods(spellInfo, castTime); + //end npcbot + Unit const* unitCaster = ToUnit(); if (!unitCaster) return; @@ -2427,6 +2512,11 @@ void WorldObject::ModSpellDurationTime(SpellInfo const* spellInfo, int32& durati if (Player* modOwner = GetSpellModOwner()) modOwner->ApplySpellMod(spellInfo->Id, SPELLMOD_CASTING_TIME, duration, spell); + //npcbot - apply bot spell cast time mods + if (duration > 0 && IsNPCBot()) + ToCreature()->ApplyCreatureSpellCastTimeMods(spellInfo, duration); + //end npcbot + Unit const* unitCaster = ToUnit(); if (!unitCaster) return; @@ -2495,6 +2585,11 @@ SpellMissInfo WorldObject::MagicSpellHitResult(Unit* victim, SpellInfo const* sp if (Unit const* unit = ToUnit()) HitChance += int32(unit->m_modSpellHitChance * 100.0f); + //npcbot: spell hit chance bonus + if (IsNPCBot()) + HitChance -= int32(ToCreature()->GetCreatureMissChance() * 100.f); + //end npcbot + RoundToInterval(HitChance, 0, 10000); int32 tmp = 10000 - HitChance; @@ -2670,10 +2765,20 @@ ReputationRank WorldObject::GetReactionTo(WorldObject const* target) const Unit const* unit = Coalesce(ToUnit(), selfPlayerOwner); Unit const* targetUnit = Coalesce(target->ToUnit(), targetPlayerOwner); + //npcbot + /* if (unit && unit->HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED)) { if (targetUnit && targetUnit->HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED)) { + */ + if (unit && (unit->HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED) || unit->IsNPCBotOrPet())) + { + if (targetUnit && (targetUnit->HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED) || targetUnit->IsNPCBotOrPet())) + { + if (unit->IsInRaidWith(targetUnit)) + return REP_FRIENDLY; + //end npcbot if (selfPlayerOwner && targetPlayerOwner) { // always friendly to other unit controlled by player, or to the player himself @@ -2744,6 +2849,16 @@ ReputationRank WorldObject::GetReactionTo(WorldObject const* target) const if ((factionTemplateEntry->Flags & FACTION_TEMPLATE_FLAG_CONTESTED_GUARD) && targetPlayerOwner->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_CONTESTED_PVP)) return REP_HOSTILE; + + //npcbot + if (target->IsNPCBotOrPet() && (factionTemplateEntry->Flags & FACTION_TEMPLATE_FLAG_CONTESTED_GUARD)) + { + Unit const* bot = target->IsNPCBotPet() ? static_cast(targetPlayerOwner->GetBotMgr()->GetBot(target->GetOwnerGUID())) : target->ToUnit(); + if (bot && bot->IsNPCBot() && BotMgr::IsBotContestedPvP(bot->ToCreature())) + return REP_HOSTILE; + } + //end npcbot + if (ReputationRank const* repRank = targetPlayerOwner->GetReputationMgr().GetForcedRankIfAny(factionTemplateEntry)) return *repRank; if (target->IsUnit() && !target->ToUnit()->HasUnitFlag2(UNIT_FLAG2_IGNORE_REPUTATION)) @@ -2761,6 +2876,14 @@ ReputationRank WorldObject::GetReactionTo(WorldObject const* target) const } } } + //npcbot: contested guards reaction to bots in contested PvP mode + else if (target->IsNPCBotOrPet() && (factionTemplateEntry->Flags & FACTION_TEMPLATE_FLAG_CONTESTED_GUARD)) + { + Unit const* bot = target->IsNPCBotPet() ? target->ToUnit()->GetCreator() : target->ToUnit(); + if (bot && bot->IsNPCBot() && BotMgr::IsBotContestedPvP(bot->ToCreature())) + return REP_HOSTILE; + } + //end npcbot // common faction based check if (factionTemplateEntry->IsHostileTo(*targetFactionTemplateEntry)) @@ -2820,6 +2943,10 @@ SpellCastResult WorldObject::CastSpell(CastSpellTargetArg const& targets, uint32 return SPELL_FAILED_SPELL_UNAVAILABLE; } + //npcbot: try override + info = info->TryGetSpellInfoOverride(this); + //end npcbot + if (!targets.Targets) { TC_LOG_ERROR("entities.unit", "CastSpell: Invalid target passed to spell cast {} by {}", spellId, GetGUID().ToString()); @@ -2855,6 +2982,11 @@ bool WorldObject::IsValidAttackTarget(WorldObject const* target, SpellInfo const if (target->GetTypeId() == TYPEID_PLAYER && target->ToPlayer()->IsGameMaster()) return false; + //npcbot: can't attack unit if controlled by a GM (bots, pets, possible others) + if (unitTarget && unitTarget->IsControlledByPlayer() && unitTarget->GetFaction() == 35) + return false; + //end npcbot + Unit const* unit = ToUnit(); // visibility checks (only units) if (unit) @@ -2891,6 +3023,9 @@ bool WorldObject::IsValidAttackTarget(WorldObject const* target, SpellInfo const unitOrOwner = go->GetOwner(); // ignore immunity flags when assisting + //npcbot: rewrite all that + /* + //end npcbot if (unitOrOwner && unitTarget && !(isPositiveSpell && bySpell->HasAttribute(SPELL_ATTR6_ASSIST_IGNORE_IMMUNE_FLAG))) { if (!unitOrOwner->HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED) && unitTarget->IsImmuneToNPC()) @@ -2905,7 +3040,61 @@ bool WorldObject::IsValidAttackTarget(WorldObject const* target, SpellInfo const if (unitTarget->HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED) && unitOrOwner->IsImmuneToPC()) return false; } + //npcbot + */ + if (unitOrOwner && unitTarget && !(isPositiveSpell && bySpell->HasAttribute(SPELL_ATTR6_ASSIST_IGNORE_IMMUNE_FLAG))) + { + if (!unitOrOwner->HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED) && !unitOrOwner->IsNPCBotOrPet() && unitTarget->IsImmuneToNPC()) + return false; + + if (!unitTarget->HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED) && !unitTarget->IsNPCBotOrPet() && unitOrOwner->IsImmuneToNPC()) + return false; + + if ((unitOrOwner->HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED) || unitOrOwner->IsNPCBotOrPet()) && unitTarget->IsImmuneToPC()) + return false; + + if ((unitTarget->HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED) || unitTarget->IsNPCBotOrPet()) && unitOrOwner->IsImmuneToPC()) + return false; + } + //end npcbot + + //npcbot: CvB, BvC case + if (unit && unitTarget && + ((IsNPCBotOrPet() && ToCreature()->IsFreeBot()) || (target->IsNPCBotOrPet() && target->ToCreature()->IsFreeBot())) && + !IsFriendlyTo(unitTarget) && !unitTarget->IsFriendlyTo(this)) + { + if (unitTarget->IsNPCBotOrPet() && unit->IsContestedGuard()) + { + if (Unit const* bot = unitTarget->IsNPCBotPet() ? unitTarget->GetCreator() : unitTarget) + { + if (BotMgr::IsBotContestedPvP(bot->ToCreature())) + return true; + } + } + else if (unit->IsNPCBotOrPet() && unitTarget->IsContestedGuard()) + { + if (Unit const* bot = unit->IsNPCBotPet() ? unit->GetCreator() : unit) + { + if (BotMgr::IsBotContestedPvP(bot->ToCreature())) + return true; + } + } + + auto const* ft1 = sFactionTemplateStore.LookupEntry(unit->GetFaction()); + auto const* ft2 = sFactionTemplateStore.LookupEntry(unitTarget->GetFaction()); + auto const* fe1 = ft1 ? sFactionStore.LookupEntry(ft1->Faction) : nullptr; + auto const* fe2 = ft2 ? sFactionStore.LookupEntry(ft2->Faction) : nullptr; + if ((IsNPCBotOrPet() && fe2 && fe2->CanHaveReputation() && ReputationMgr::ReputationToRank(BotDataMgr::GetBotBaseReputation(unit->ToCreature(), fe2)) >= REP_NEUTRAL) || + (target->IsNPCBotOrPet() && fe1 && fe1->CanHaveReputation() && ReputationMgr::ReputationToRank(BotDataMgr::GetBotBaseReputation(unitTarget->ToCreature(), fe1)) >= REP_NEUTRAL)) + return false; + } + //end npcbot + //npcbot + if (unit && unitTarget && (unit->IsNPCBotOrPet() || unitTarget->IsNPCBotOrPet())) + {} + else + //end npcbot // CvC case - can attack each other only when one of them is hostile if (unit && !unit->HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED) && unitTarget && !unitTarget->HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED)) return IsHostileTo(unitTarget) || unitTarget->IsHostileTo(this); @@ -2927,6 +3116,13 @@ bool WorldObject::IsValidAttackTarget(WorldObject const* target, SpellInfo const Player const* playerAffectingAttacker = unit && unit->HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED) ? GetAffectingPlayer() : go ? GetAffectingPlayer() : nullptr; Player const* playerAffectingTarget = unitTarget && unitTarget->HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED) ? unitTarget->GetAffectingPlayer() : nullptr; + //npcbot: get affectingplayers for bots + if (!playerAffectingAttacker && unit && unit->IsNPCBotOrPet()) + playerAffectingAttacker = unit->GetAffectingPlayer(); + if (!playerAffectingTarget && unitTarget && unitTarget->IsNPCBotOrPet()) + playerAffectingTarget = unitTarget->GetAffectingPlayer(); + //end npcbot + // Not all neutral creatures can be attacked (even some unfriendly faction does not react aggresive to you, like Sporaggar) if ((playerAffectingAttacker && !playerAffectingTarget) || (!playerAffectingAttacker && playerAffectingTarget)) { @@ -2962,6 +3158,13 @@ bool WorldObject::IsValidAttackTarget(WorldObject const* target, SpellInfo const if (unitTarget && unitTarget->HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED) && unitOrOwner && unitOrOwner->HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED) && (unitTarget->IsInSanctuary() || unitOrOwner->IsInSanctuary())) return false; + //npcbot: BvP, PvB, BvB sanctuary case + if (unitTarget && (unitTarget->HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED) || unitTarget->IsNPCBotOrPet()) && + unitOrOwner && (unitOrOwner->HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED) || unitOrOwner->IsNPCBotOrPet()) && + (unitTarget->IsInSanctuary() || unitOrOwner->IsInSanctuary())) + return false; + //end npcbot + // additional checks - only PvP case if (playerAffectingAttacker && playerAffectingTarget) { @@ -2974,6 +3177,21 @@ bool WorldObject::IsValidAttackTarget(WorldObject const* target, SpellInfo const return playerAffectingAttacker->HasPvpFlag(UNIT_BYTE2_FLAG_UNK1) || playerAffectingTarget->HasPvpFlag(UNIT_BYTE2_FLAG_UNK1); } + //npcbot: BvP checks + else if (playerAffectingTarget && !playerAffectingAttacker && unit && unit->IsNPCBotOrPet()) + { + if (Unit const* bot = unit->IsNPCBotPet() ? unit->GetCreator() : unit) + { + if (playerAffectingTarget->IsPvP()) + return true; + + if (bot->IsFFAPvP() && playerAffectingTarget->IsFFAPvP()) + return true; + + return bot->HasPvpFlag(UNIT_BYTE2_FLAG_UNK1) || playerAffectingTarget->HasPvpFlag(UNIT_BYTE2_FLAG_UNK1); + } + } + //end npcbot return true; } @@ -3033,6 +3251,13 @@ bool WorldObject::IsValidAssistTarget(WorldObject const* target, SpellInfo const if (unitTarget && unitTarget->IsImmuneToPC()) return false; } + //npcbot + else if (unit && unit->IsNPCBotOrPet()) + { + if (unitTarget && unitTarget->IsImmuneToPC()) + return false; + } + //end npcbot else { if (unitTarget && unitTarget->IsImmuneToNPC()) @@ -3077,6 +3302,20 @@ bool WorldObject::IsValidAssistTarget(WorldObject const* target, SpellInfo const return ((creatureTarget->GetCreatureTemplate()->type_flags & CREATURE_TYPE_FLAG_TREAT_AS_RAID_UNIT) || (creatureTarget->GetCreatureTemplate()->type_flags & CREATURE_TYPE_FLAG_CAN_ASSIST)); } + //npcbot: PvP (BvB) case + if (unit && unit->IsNPCBotOrPet() && unitTarget && unitTarget->IsNPCBotOrPet()) + { + Player const* selfPlayerOwner = GetAffectingPlayer(); + Player const* targetPlayerOwner = unitTarget->GetAffectingPlayer(); + if (selfPlayerOwner && targetPlayerOwner && selfPlayerOwner != targetPlayerOwner && targetPlayerOwner->duel) + return false; + if (unitTarget->IsFFAPvP() && !unit->IsFFAPvP()) + return false; + if (unitTarget->IsPvP() && unit->IsInSanctuary() && !unitTarget->IsInSanctuary()) + return false; + } + //end npcbot + return true; } @@ -3212,7 +3451,7 @@ Position WorldObject::GetNearPosition(float dist, float angle) return pos; } -Position WorldObject::GetFirstCollisionPosition(float dist, float angle) +Position WorldObject::GetFirstCollisionPosition(float dist, float angle) const { Position pos = GetPosition(); MovePositionToFirstCollision(pos, dist, angle); @@ -3278,7 +3517,7 @@ void WorldObject::MovePosition(Position &pos, float dist, float angle) pos.SetOrientation(GetOrientation()); } -void WorldObject::MovePositionToFirstCollision(Position &pos, float dist, float angle) +void WorldObject::MovePositionToFirstCollision(Position &pos, float dist, float angle) const { angle += GetOrientation(); float destx, desty, destz; diff --git a/src/server/game/Entities/Object/Object.h b/src/server/game/Entities/Object/Object.h index 9dca2efa0..28e50b971 100644 --- a/src/server/game/Entities/Object/Object.h +++ b/src/server/game/Entities/Object/Object.h @@ -164,6 +164,12 @@ class TC_GAME_API Object // FG: some hacky helpers void ForceValuesUpdateAtIndex(uint32); + //npcbot + virtual bool IsNPCBot() const { return false; } + virtual bool IsNPCBotPet() const { return false; } + virtual bool IsNPCBotOrPet() const { return false; } + //end npcbot + inline bool IsWorldObject() const { return isType(TYPEMASK_WORLDOBJECT); } static WorldObject* ToWorldObject(Object* o) { return o ? o->ToWorldObject() : nullptr; } static WorldObject const* ToWorldObject(Object const* o) { return o ? o->ToWorldObject() : nullptr; } @@ -314,8 +320,8 @@ class TC_GAME_API WorldObject : public Object, public WorldLocation void GetClosePoint(float& x, float& y, float& z, float size, float distance2d = 0, float relAngle = 0) const; void MovePosition(Position &pos, float dist, float angle); Position GetNearPosition(float dist, float angle); - void MovePositionToFirstCollision(Position &pos, float dist, float angle); - Position GetFirstCollisionPosition(float dist, float angle); + void MovePositionToFirstCollision(Position &pos, float dist, float angle) const; + Position GetFirstCollisionPosition(float dist, float angle) const; Position GetRandomNearPosition(float radius); void GetContactPoint(WorldObject const* obj, float& x, float& y, float& z, float distance2d = CONTACT_DISTANCE) const; diff --git a/src/server/game/Entities/Player/KillRewarder.cpp b/src/server/game/Entities/Player/KillRewarder.cpp index 65b9fffc0..3450733a2 100644 --- a/src/server/game/Entities/Player/KillRewarder.cpp +++ b/src/server/game/Entities/Player/KillRewarder.cpp @@ -26,6 +26,10 @@ #include "Pet.h" #include "Player.h" +//npcbot +#include "botmgr.h" +//end npcbot + // == KillRewarder ==================================================== // KillRewarder encapsulates logic of rewarding player upon kill with: // * XP; @@ -76,6 +80,10 @@ KillRewarder::KillRewarder(Player* killer, Unit* victim, bool isBattleGround) : // mark the credit as pvp if victim is player if (victim->GetTypeId() == TYPEID_PLAYER) _isPvP = true; + //npcbot + else if (victim->IsNPCBotOrPet()) + _isPvP = true; + //end npcbot // or if its owned by player and its not a vehicle else if (victim->GetCharmerOrOwnerGUID().IsPlayer()) _isPvP = !victim->IsVehicle(); @@ -153,6 +161,27 @@ inline void KillRewarder::_RewardXP(Player* player, float rate) // 4.2.2. Apply auras modifying rewarded XP (SPELL_AURA_MOD_XP_PCT). xp *= player->GetTotalAuraMultiplier(SPELL_AURA_MOD_XP_PCT); + //npcbot 4.2.2.1. Apply NpcBot XP reduction + uint8 bots_count = 0; + if (_group) + { + for (GroupReference const* itr = _group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + if (Player const* gPlayer = itr->GetSource()) + bots_count = std::max(bots_count, gPlayer->GetNpcBotsCount()); + } + } + else + bots_count = player->GetNpcBotsCount(); + uint8 xp_reduction = BotMgr::GetNpcBotXpReduction(); + uint8 xp_reduction_start = BotMgr::GetNpcBotXpReductionStartingNumber(); + if (xp_reduction_start > 0 && xp_reduction > 0 && bots_count >= xp_reduction_start) + { + uint32 ratePct = std::max(100 - ((bots_count - (xp_reduction_start - 1)) * xp_reduction), 10); + xp = xp * ratePct / 100; + } + //end npcbot + // 4.2.3. Give XP to player. player->GiveXP(xp, _victim, _groupRate); if (Pet* pet = player->GetPet()) diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 17eb1bb9e..3ecd6d9a9 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -106,6 +106,11 @@ #include "WorldSession.h" #include "WorldStatePackets.h" +//npcbot +#include "botmgr.h" +#include "botdatamgr.h" +//end npcbot + #define ZONE_UPDATE_INTERVAL (1*IN_MILLISECONDS) #define PLAYER_SKILL_INDEX(x) (PLAYER_SKILL_INFO_1_1 + ((x)*3)) @@ -387,6 +392,10 @@ Player::Player(WorldSession* session): Unit(true) m_reputationMgr = new ReputationMgr(this); m_groupUpdateTimer.Reset(5000); + + /////////////// NPCBot System ////////////////// + _botMgr = new BotMgr(this); + ///////////// End NPCBot System //////////////// } Player::~Player() @@ -418,6 +427,10 @@ Player::~Player() delete m_reputationMgr; delete _cinematicMgr; + //npcbot + delete _botMgr; + //end npcbot + sWorld->DecreasePlayerCount(); } @@ -1327,6 +1340,10 @@ 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); + + //NpcBot mod: Update + _botMgr->Update(p_time); + //end Npcbot } void Player::setDeathState(DeathState s) @@ -1777,6 +1794,11 @@ bool Player::TeleportTo(uint32 mapid, float x, float y, float z, float orientati if (pet) UnsummonPetTemporaryIfAny(); + //bot: teleport npcbots + if (HaveBot()) + _botMgr->OnTeleportFar(mapid, x, y, z, orientation); + //end bot + // remove all dyn objects RemoveAllDynObjects(); @@ -1965,6 +1987,25 @@ bool Player::IsImmunedToSpellEffect(SpellInfo const* spellInfo, SpellEffectInfo return Unit::IsImmunedToSpellEffect(spellInfo, spellEffectInfo, caster, requireImmunityPurgesEffectAttribute); } +//NPCBOT +bool Player::HaveBot() const +{ + return _botMgr->HaveBot(); +} +uint8 Player::GetNpcBotsCount() const +{ + return _botMgr->GetNpcBotsCount(); +} +void Player::RemoveAllBots(uint8 removetype) +{ + _botMgr->RemoveAllBots(removetype); +} +void Player::UpdatePhaseForBots() +{ + _botMgr->UpdatePhaseForBots(); +} +//END NPCBOT + void Player::RegenerateAll() { m_regenTimerCount += m_regenTimer; @@ -2202,6 +2243,11 @@ Creature* Player::GetNPCIfCanInteractWith(ObjectGuid const& guid, NPCFlags npcFl if (creature->GetCharmerGUID()) return nullptr; + //npcbot + if (creature->IsNPCBot() && creature->IsWithinDistInMap(this, INTERACTION_DISTANCE)) + return creature; + //end npcbot + // not unfriendly/hostile if (creature->GetReactionTo(this) <= REP_UNFRIENDLY) return nullptr; @@ -2325,6 +2371,10 @@ void Player::SetGameMaster(bool on) m_serverSideVisibilityDetect.SetValue(SERVERSIDE_VISIBILITY_GM, SEC_PLAYER); } + //npcbot: pet is handled already, bots are not, so do it + _botMgr->OnOwnerSetGameMaster(on); + //end npcbot + UpdateObjectVisibility(); } @@ -2406,6 +2456,52 @@ void Player::RemoveFromGroup(Group* group, ObjectGuid guid, RemoveMethod method if (!group) return; + //npcbot - player is being removed from group - remove bots from that group + if (Player* player = ObjectAccessor::FindPlayer(guid)) + { + if (player->HaveBot()) + { + //remove npcbots and set up new group if needed + player->GetBotMgr()->RemoveAllBotsFromGroup(); + group = player->GetGroup(); + if (!group) + return; //group has been disbanded + } + } + //npcbot - deleting player from db: remove bots + else if (guid.IsPlayer()) + { + std::vector botguids; + botguids.reserve(BotMgr::GetMaxNpcBots(DEFAULT_MAX_LEVEL) / 2 + 1); + BotDataMgr::GetNPCBotGuidsByOwner(botguids, guid); + for (std::vector::const_iterator ci = botguids.begin(); ci != botguids.end(); ++ci) + { + if (group->IsMember(*ci)) + { + if (!group->RemoveMember(*ci, method, kicker, reason)) + return; + } + } + } + //npcbot - bot is being removed from group - find master and remove bot through botmap + else if (guid.IsCreature()) + { + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + if (Player* member = itr->GetSource()) + { + if (!member->HaveBot()) + continue; + + if (Creature* bot = member->GetBotMgr()->GetBot(guid)) + { + member->GetBotMgr()->RemoveBotFromGroup(bot); + return; + } + } + } + } + group->RemoveMember(guid, method, kicker, reason); } @@ -2440,6 +2536,9 @@ void Player::GiveXP(uint32 xp, Unit* victim, float group_rate) return; if (victim && victim->GetTypeId() == TYPEID_UNIT && !victim->ToCreature()->hasLootRecipient()) + //npcbot + if (!(victim->IsNPCBot() && victim->FindMap() && victim->GetMap()->IsBattleground())) + //end npcbot return; uint8 level = GetLevel(); @@ -2574,6 +2673,10 @@ void Player::GiveLevel(uint8 level) SendQuestGiverStatusMultiple(); sScriptMgr->OnPlayerLevelChanged(this, oldLevel); + + //npcbot: force bots to update stats + _botMgr->SetBotsShouldUpdateStats(); + //end npcbot } bool Player::IsMaxLevel() const @@ -4311,6 +4414,12 @@ void Player::DeleteFromDB(ObjectGuid playerguid, uint32 accountId, bool updateRe trans->Append(stmt); Corpse::DeleteFromDB(playerguid, trans); + + //npcbot - erase npcbots + uint32 newOwner = 0; + BotDataMgr::UpdateNpcBotDataAll(guid, NPCBOT_UPDATE_OWNER, &newOwner); + //end npcbot + break; } // The character gets unlinked from the account, the name gets freed up and appears as deleted ingame @@ -6700,6 +6809,49 @@ bool Player::RewardHonor(Unit* victim, uint32 groupsize, int32 honor, bool pvpto UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HONORABLE_KILL, 1, 0, victim); UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_SPECIAL_PVP_KILL, 1, 0, victim); } + //npcbot: honor for bots + else if (victim->IsNPCBot() && !victim->ToCreature()->IsTempBot()) + { + static const float WANDERING_BOT_HONOR_GAIN_MULT = 10.0f; + + if (!BotMgr::IsBotHKEnabled()) + return false; + + Creature const* bot = victim->ToCreature(); + + uint32 victimTeam = !bot->IsFreeBot() ? bot->GetBotOwner()->GetTeam() : BotDataMgr::GetTeamForFaction(bot->GetFaction()); + if (GetTeam() == victimTeam && !sWorld->IsFFAPvPRealm()) + return false; + + uint8 k_level = GetLevel(); + uint8 k_grey = Trinity::XP::GetGrayLevel(k_level); + uint8 v_level = victim->GetLevel(); + + if (v_level <= k_grey) + return false; + + if (!BotMgr::IsBotHKMessageEnabled()) + victim_guid.Clear(); // Don't show HK: message, only log. + + //TODO: honor gain rate + honor_f = ceil(Trinity::Honor::hk_honor_at_level_f(k_level) * (v_level - k_grey) / (k_level - k_grey)); + honor_f *= BotMgr::GetBotHKHonorRate(); + if (bot->IsWandererBot() && !bot->GetBotBG()) + honor_f *= WANDERING_BOT_HONOR_GAIN_MULT; + + if (BotMgr::IsBotHKAchievementsEnabled()) + { + ApplyModUInt32Value(PLAYER_FIELD_KILLS, 1, true); + ApplyModUInt32Value(PLAYER_FIELD_LIFETIME_HONORABLE_KILLS, 1, true); + UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_EARN_HONORABLE_KILL); + UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HK_CLASS, BotMgr::GetBotPlayerClass(victim->ToCreature())); + UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HK_RACE, BotMgr::GetBotPlayerRace(victim->ToCreature())); + UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HONORABLE_KILL_AT_AREA, GetAreaId()); + UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HONORABLE_KILL, 1, 0, victim); + UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_SPECIAL_PVP_KILL, 1, 0, victim); + } + } + //end npcbot else { if (!victim->ToCreature()->IsRacialLeader()) @@ -8329,6 +8481,14 @@ void Player::SendLoot(ObjectGuid guid, LootType loot_type) if (GameObjectTemplateAddon const* addon = go->GetTemplateAddon()) loot->generateMoneyLoot(addon->Mingold, addon->Maxgold); + //npcbot: fill wandering bot kill reward + if (lootid) + { + if (go->GetEntry() == GO_BOT_MONEY_BAG) + BotMgr::OnBotWandererKilled(go); + } + //end npcbot + if (loot_type == LOOT_FISHING) go->getFishLoot(loot, this); else if (loot_type == LOOT_FISHING_JUNK) @@ -19464,6 +19624,10 @@ void Player::SaveToDB(CharacterDatabaseTransaction trans, bool create /* = false // save pet (hunter pet level and experience and all type pets health/mana). if (Pet* pet = GetPet()) pet->SavePetToDB(PET_SAVE_AS_CURRENT); + + //npcbot: save stored items + BotDataMgr::SaveNpcBotStoredGear(GetGUID(), trans); + //end npcbot } // fast save function for item/money cheating preventing - save only inventory and money state @@ -21584,6 +21748,18 @@ bool Player::BuyItemFromVendorSlot(ObjectGuid vendorguid, uint32 vendorslot, uin return false; } + // npcbot + if (HaveBot()) + { + if (!(pProto->AllowableClass & (GetClassMask() | GetBotMgr()->GetAllNpcBotsClassMask())) && + pProto->Bonding == BIND_WHEN_PICKED_UP && !IsGameMaster()) + { + SendBuyError(BUY_ERR_CANT_FIND_ITEM, nullptr, item, 0); + return false; + } + } + else + // end npcbot if (!(pProto->AllowableClass & GetClassMask()) && pProto->Bonding == BIND_WHEN_PICKED_UP && !IsGameMaster()) { SendBuyError(BUY_ERR_CANT_FIND_ITEM, nullptr, item, 0); @@ -21857,6 +22033,10 @@ void Player::UpdatePvP(bool state, bool _override) pvpInfo.EndTimer = GameTime::GetGameTime(); SetPvP(state); } + + //npcbot: update pvp flags for bots + _botMgr->UpdatePvPForBots(); + //end npcbot } void Player::UpdatePotionCooldown(Spell* spell) @@ -23724,6 +23904,11 @@ bool Player::isHonorOrXPTarget(Unit* victim) const if (Creature const* creature = victim->ToCreature()) { + //npcbot: count npcbots at xp targets (DEPRECATED) + if (victim->ToCreature()->IsNPCBotOrPet()) + return true; + //end npcbots + if (creature->IsCritter() || creature->IsTotem()) return false; } diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 6a0e544d3..055ad5528 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -81,6 +81,10 @@ enum ItemClass : uint8; enum LootError : uint8; enum LootType : uint8; +// NpcBot mod +class BotMgr; +// end NpcBot mod + typedef std::deque PlayerMails; #define PLAYER_MAX_SKILLS 128 @@ -2238,6 +2242,18 @@ class TC_GAME_API Player : public Unit, public GridObject std::string GetDebugInfo() const override; + /*****************************************************************/ + /*** NPCBOT SYSTEM ***/ + /*****************************************************************/ + BotMgr* GetBotMgr() const { return _botMgr; } + bool HaveBot() const; + uint8 GetNpcBotsCount() const; + void RemoveAllBots(uint8 removetype = 0); + void UpdatePhaseForBots(); + /*****************************************************************/ + /*** END NPCBOT SYSTEM ***/ + /*****************************************************************/ + protected: // Gamemaster whisper whitelist GuidList WhisperList; @@ -2483,6 +2499,14 @@ class TC_GAME_API Player : public Unit, public GridObject TimeTracker m_groupUpdateTimer; private: + /*****************************************************************/ + /*** NPCBOT SYSTEM ***/ + /*****************************************************************/ + BotMgr* _botMgr; + /*****************************************************************/ + /*** END NPCBOT SYSTEM ***/ + /*****************************************************************/ + // internal common parts for CanStore/StoreItem functions InventoryResult CanStoreItem_InSpecificSlot(uint8 bag, uint8 slot, ItemPosCountVec& dest, ItemTemplate const* pProto, uint32& count, bool swap, Item* pSrcItem) const; InventoryResult CanStoreItem_InBag(uint8 bag, ItemPosCountVec& dest, ItemTemplate const* pProto, uint32& count, bool merge, bool non_specialized, Item* pSrcItem, uint8 skip_bag, uint8 skip_slot) const; diff --git a/src/server/game/Entities/Totem/Totem.cpp b/src/server/game/Entities/Totem/Totem.cpp index 0337e9031..0a95ef123 100644 --- a/src/server/game/Entities/Totem/Totem.cpp +++ b/src/server/game/Entities/Totem/Totem.cpp @@ -25,6 +25,11 @@ #include "SpellInfo.h" #include "TotemPackets.h" +//npcbot +#include "botmgr.h" +#include "ObjectAccessor.h" +//end npcbot + Totem::Totem(SummonPropertiesEntry const* properties, Unit* owner) : Minion(properties, owner, false) { m_unitTypeMask |= UNIT_MASK_TOTEM; @@ -34,6 +39,20 @@ Totem::Totem(SummonPropertiesEntry const* properties, Unit* owner) : Minion(prop void Totem::Update(uint32 time) { + //npcbot: do not despawn bot totem if master is dead + Creature const* botOwner = (GetOwner()->GetTypeId() == TYPEID_PLAYER && GetOwner()->ToPlayer()->HaveBot()) ? + GetOwner()->ToPlayer()->GetBotMgr()->GetBot(GetCreatorGUID()) : nullptr; + + if (botOwner) + { + if (!botOwner->IsAlive() || !IsAlive()) + { + UnSummon(); + return; + } + } + else + //end npcbot if (!GetOwner()->IsAlive() || !IsAlive()) { UnSummon(); // remove self @@ -152,6 +171,12 @@ void Totem::UnSummon(uint32 msTime) if (IsAlive()) setDeathState(DEAD); + //npcbot: send SummonedCreatureDespawn() + if (Unit* creator = GetCreator()) + if (creator->IsNPCBot()) + creator->ToCreature()->OnBotDespawn(this); + //end npcbot + AddObjectToRemoveList(); } diff --git a/src/server/game/Entities/Unit/StatSystem.cpp b/src/server/game/Entities/Unit/StatSystem.cpp index cb467b8de..cae18dd3a 100644 --- a/src/server/game/Entities/Unit/StatSystem.cpp +++ b/src/server/game/Entities/Unit/StatSystem.cpp @@ -1209,16 +1209,58 @@ void Creature::CalculateMinMaxDamage(WeaponAttackType attType, bool normalized, float weaponMinDamage = GetWeaponDamageRange(attType, MINDAMAGE); float weaponMaxDamage = GetWeaponDamageRange(attType, MAXDAMAGE); + //npcbot: support for feral form + if (IsNPCBot() && IsInFeralForm()) + { + float att_speed = GetAPMultiplier(attType, false); + uint8 lvl = GetLevel(); + if (lvl > 60) + lvl = 60; + + weaponMinDamage = lvl*0.85f*att_speed; + weaponMaxDamage = lvl*1.25f*att_speed; + } + else + //end npcbot if (!CanUseAttackType(attType)) // disarm case { + //npcbot: mimic player-like disarm (retain damage) + if (IsNPCBot()) + { + // Main hand melee is always usable, but disarm reduces damage drastically + if (attType == BASE_ATTACK) + { + weaponMinDamage *= 0.25f; + weaponMaxDamage *= 0.25f; + } + else + { + weaponMinDamage = 0.0f; + weaponMaxDamage = 0.0f; + } + } + else + { + //end npcbot weaponMinDamage = 0.0f; weaponMaxDamage = 0.0f; + //npcbot + } + } + //end npcbot + //npcbot: support for ammo + else if (attType == RANGED_ATTACK) + { + float att_speed = GetAPMultiplier(attType, false); + weaponMinDamage += GetCreatureAmmoDPS() * att_speed; + weaponMaxDamage += GetCreatureAmmoDPS() * att_speed; + //end npcbot } float attackPower = GetTotalAttackPowerValue(attType); float attackSpeedMulti = GetAPMultiplier(attType, normalized); - float baseValue = GetFlatModifierValue(unitMod, BASE_VALUE) + (attackPower / 14.0f) * variance; - float basePct = GetPctModifierValue(unitMod, BASE_PCT) * attackSpeedMulti; + float baseValue = GetFlatModifierValue(unitMod, BASE_VALUE) + (attackPower / 14.0f) * variance * (IsNPCBot() ? attackSpeedMulti : 1.0f); + float basePct = GetPctModifierValue(unitMod, BASE_PCT) * (!IsNPCBot() ? attackSpeedMulti : 1.0f); float totalValue = GetFlatModifierValue(unitMod, TOTAL_VALUE); float totalPct = addTotalPct ? GetPctModifierValue(unitMod, TOTAL_PCT) : 1.0f; float dmgMultiplier = GetCreatureTemplate()->ModDamage; // = ModDamage * _GetDamageMod(rank); diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 88df3a10c..663834854 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -81,6 +81,10 @@ #include "WorldSession.h" #include +//npcbot +#include "botmgr.h" +//end npcbot + float baseMoveSpeed[MAX_MOVE_TYPE] = { 2.5f, // MOVE_WALK @@ -185,6 +189,10 @@ DamageInfo::DamageInfo(SpellNonMeleeDamage const& spellNonMeleeDamage, DamageEff m_hitMask |= PROC_HIT_BLOCK; if (spellNonMeleeDamage.absorb) m_hitMask |= PROC_HIT_ABSORB; + + //npcbot: override spellInfo + const_cast(m_spellInfo) = m_spellInfo->TryGetSpellInfoOverride(m_attacker); + //end npcbot } void DamageInfo::ModifyDamage(int32 amount) @@ -717,6 +725,26 @@ bool Unit::HasBreakableByDamageCrowdControlAura(Unit* excludeCasterChannel) cons if (UnitAI* attackerAI = attacker ? attacker->GetAI() : nullptr) attackerAI->DamageDealt(victim, damage, damagetype); + //npcbot + if (victim->IsNPCBot()) + BotMgr::OnBotDamageTaken(attacker, victim, damage, cleanDamage, damagetype, spellProto); + //end npcbot + //npcbot: damage dealt hook for crits and spells + if (attacker && attacker->IsNPCBot()) + BotMgr::OnBotDamageDealt(attacker, victim, damage, cleanDamage, damagetype, spellProto); + //end npcbot + + //npcbot: damage tracker hook + if (damage > 0 && damage < victim->GetHealth()) + { + Player const* botowner = victim->GetTypeId() == TYPEID_PLAYER ? victim->ToPlayer() : + victim->IsNPCBot() && !victim->ToCreature()->IsFreeBot() ? victim->ToCreature()->GetBotOwner() : nullptr; + + if (botowner && botowner->GetBotMgr() && (botowner->HaveBot() || (botowner->GetGroup() && botowner->GetGroup()->IsMember(victim->GetGUID())))) + botowner->GetBotMgr()->TrackDamage(victim, damage); + } + //end npcbot + // Hook for OnDamage Event sScriptMgr->OnDamage(attacker, victim, damage); @@ -729,6 +757,11 @@ bool Unit::HasBreakableByDamageCrowdControlAura(Unit* excludeCasterChannel) cons controlledAI->OwnerAttackedBy(attacker); } + //npcbot + if (attacker && attacker != victim && victim->IsVehicle() && victim->IsAlive()) + BotMgr::OnVehicleAttackedBy(attacker, victim); + //end npcbot + if (Player* player = victim->ToPlayer()) if (player->GetCommandStatus(CHEAT_GOD)) return 0; @@ -861,9 +894,23 @@ bool Unit::HasBreakableByDamageCrowdControlAura(Unit* excludeCasterChannel) cons if (Battleground* bg = killer->GetBattleground()) bg->UpdatePlayerScore(killer, SCORE_DAMAGE_DONE, damage); + //npcbot + if (victim->IsNPCBot()) + if (Battleground* bg = killer->GetBattleground()) + bg->UpdatePlayerScore(killer, SCORE_DAMAGE_DONE, damage); + //end npcbot + killer->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_DAMAGE_DONE, health > damage ? damage : health, 0, victim); killer->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HIGHEST_HIT_DEALT, damage); } + //npcbot + if (attacker->IsNPCBot() && (victim->IsPlayer() || victim->IsNPCBot())) + { + Creature const* bot = attacker->ToCreature(); + if (Battleground* bg = bot->GetBotBG()) + bg->UpdateBotScore(bot, SCORE_DAMAGE_DONE, damage); + } + //end npcbot } if (victim->GetTypeId() == TYPEID_PLAYER) @@ -1011,6 +1058,18 @@ void Unit::CalculateSpellDamageTaken(SpellNonMeleeDamage* damageInfo, int32 dama case SPELL_DAMAGE_CLASS_RANGED: case SPELL_DAMAGE_CLASS_MELEE: { + //NpcBot mod: apply bot damage mods + if (GetTypeId() == TYPEID_UNIT && ToCreature()->IsNPCBotOrPet()) + { + //TODO: rename to ApplyBotDamageMultiplierPhysical + ToCreature()->ApplyBotDamageMultiplierMelee(damage, *damageInfo, spellInfo, attackType, crit); + if (damageSchoolMask & SPELL_SCHOOL_MASK_NORMAL) + damage *= (BotMgr::IsWanderingWorldBot(ToCreature()) ? BotMgr::GetBotWandererDamageMod() : BotMgr::GetBotDamageModPhysical()); + else if (damageSchoolMask & SPELL_SCHOOL_MASK_MAGIC) + damage *= (BotMgr::IsWanderingWorldBot(ToCreature()) ? BotMgr::GetBotWandererDamageMod() : BotMgr::GetBotDamageModSpell()); + } + //End NpcBot + if (crit) { damageInfo->HitInfo |= SPELL_HIT_TYPE_CRIT; @@ -1062,6 +1121,17 @@ void Unit::CalculateSpellDamageTaken(SpellNonMeleeDamage* damageInfo, int32 dama case SPELL_DAMAGE_CLASS_NONE: case SPELL_DAMAGE_CLASS_MAGIC: { + //NpcBot mod: apply bot damage mods + if (GetTypeId() == TYPEID_UNIT && ToCreature()->IsNPCBotOrPet()) + { + ToCreature()->ApplyBotDamageMultiplierSpell(damage, *damageInfo, spellInfo, attackType, crit); + if (damageSchoolMask & SPELL_SCHOOL_MASK_NORMAL) + damage *= (BotMgr::IsWanderingWorldBot(ToCreature()) ? BotMgr::GetBotWandererDamageMod() : BotMgr::GetBotDamageModPhysical()); + else if (damageSchoolMask & SPELL_SCHOOL_MASK_MAGIC) + damage *= (BotMgr::IsWanderingWorldBot(ToCreature()) ? BotMgr::GetBotWandererDamageMod() : BotMgr::GetBotDamageModSpell()); + } + //End NpcBot + // If crit add critical bonus if (crit) { @@ -1119,6 +1189,10 @@ void Unit::DealSpellDamage(SpellNonMeleeDamage const* damageInfo, bool durabilit return; } + //npcbot: override spellInfo + spellProto = spellProto->TryGetSpellInfoOverride(damageInfo->attacker); + //end npcbot + // Call default DealDamage CleanDamage cleanDamage(damageInfo->cleanDamage, damageInfo->absorb, BASE_ATTACK, MELEE_HIT_NORMAL); Unit::DealDamage(this, victim, damageInfo->damage, &cleanDamage, SPELL_DIRECT_DAMAGE, SpellSchoolMask(damageInfo->schoolMask), spellProto, durabilityLoss); @@ -1206,6 +1280,17 @@ void Unit::CalculateMeleeDamage(Unit* victim, CalcDamageInfo* damageInfo, Weapon // Script Hook For CalculateMeleeDamage -- Allow scripts to change the Damage pre class mitigation calculations sScriptMgr->ModifyMeleeDamage(damageInfo->Target, damageInfo->Attacker, damage); + //NpcBot mod: apply bot damage mods + if (GetTypeId() == TYPEID_UNIT && ToCreature()->IsNPCBotOrPet()) + { + damageInfo->Damages[i].Damage = damage; + //damage is unused. TODO: remove this redundant argument + ToCreature()->ApplyBotDamageMultiplierMelee(damageInfo->Damages[i].Damage, *damageInfo); + damage = damageInfo->Damages[i].Damage; + damage *= (BotMgr::IsWanderingWorldBot(ToCreature()) ? BotMgr::GetBotWandererDamageMod() : BotMgr::GetBotDamageModPhysical()); + } + //End NpcBot + // Calculate armor reduction if (Unit::IsDamageReducedByArmor(SpellSchoolMask(damageInfo->Damages[i].DamageSchoolMask))) { @@ -1216,6 +1301,11 @@ void Unit::CalculateMeleeDamage(Unit* victim, CalcDamageInfo* damageInfo, Weapon damageInfo->Damages[i].Damage = damage; } + //NpcBot mod: check custom melee outcome + if (IsNPCBot()) + damageInfo->HitOutCome = ToCreature()->BotRollMeleeOutcomeAgainst(damageInfo->Target, damageInfo->AttackType); + else + //End NpcBot damageInfo->HitOutCome = RollMeleeOutcomeAgainst(damageInfo->Target, damageInfo->AttackType); switch (damageInfo->HitOutCome) @@ -1483,6 +1573,9 @@ void Unit::DealMeleeDamage(CalcDamageInfo* damageInfo, bool durabilityLoss) if ((damageInfo->HitOutCome == MELEE_HIT_CRIT || damageInfo->HitOutCome == MELEE_HIT_CRUSHING || damageInfo->HitOutCome == MELEE_HIT_NORMAL || damageInfo->HitOutCome == MELEE_HIT_GLANCING) && GetTypeId() != TYPEID_PLAYER && !ToCreature()->IsControlledByPlayer() && !victim->HasInArc(float(M_PI), this) && (victim->GetTypeId() == TYPEID_PLAYER || !victim->ToCreature()->isWorldBoss())&& !victim->IsVehicle()) + //npcbot: prevent daze caused by bots + if (!IsNPCBotOrPet()) + //end npcbot { // 20% base chance float chance = 20.0f; @@ -1507,6 +1600,13 @@ void Unit::DealMeleeDamage(CalcDamageInfo* damageInfo, bool durabilityLoss) DamageInfo dmgInfo(*damageInfo); ToPlayer()->CastItemCombatSpell(dmgInfo); } + //npcbot - CastItemCombatSpell for bots + else if (IsNPCBot()) + { + DamageInfo dmgInfo(*damageInfo); + ToCreature()->CastCreatureItemCombatSpell(dmgInfo); + } + //end npcbot // Do effect if any damage done to target if (damageInfo->Damages[0].Damage + damageInfo->Damages[1].Damage) @@ -1630,6 +1730,15 @@ void Unit::HandleEmoteCommand(Emote emoteId) } } + //npcbot: armor penetration modifier + if (attacker && attacker->IsNPCBot()) + { + // SPELL_AURA_MOD_ARMOR_PENETRATION_PCT is handled in class mods + // No cap + armor -= CalculatePct(armor, attacker->ToCreature()->GetCreatureArmorPenetrationCoef()); + } + //end npcbot + if (armor < 0.0f) armor = 0.0f; @@ -1732,6 +1841,14 @@ void Unit::HandleEmoteCommand(Emote emoteId) victimResistance += float(unitCaster->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_TARGET_RESISTANCE, schoolMask)); } + //npcbot - spell resist and spell penetration for bots + if (caster && caster->IsNPCBot()) + victimResistance -= caster->ToCreature()->GetCreatureSpellPenetration(); + + if (victim->IsNPCBot()) + victimResistance += victim->ToCreature()->GetCreatureResistanceBonus(schoolMask); + //end npcbot + // holy resistance exists in pve and comes from level difference, ignore template values if (schoolMask & SPELL_SCHOOL_MASK_HOLY) victimResistance = 0.0f; @@ -1883,6 +2000,10 @@ void Unit::HandleEmoteCommand(Emote emoteId) if (float manaMultiplier = absorbAurEff->GetSpellEffectInfo().CalcValueMultiplier(absorbAurEff->GetCaster())) manaReduction = int32(float(manaReduction) * manaMultiplier); + //npcbot: fix absorption with 'manaMultiplier' < 1.0 (Mana Shield 35064) + manaReduction = std::max(manaReduction, 1); + //end npcbot + int32 manaTaken = -damageInfo.GetVictim()->ModifyPower(POWER_MANA, -manaReduction); // take case when mana has ended up into account @@ -2161,6 +2282,15 @@ MeleeHitOutcome Unit::RollMeleeOutcomeAgainst(Unit const* victim, WeaponAttackTy int32 block_chance = int32(GetUnitBlockChance(attType, victim) * 100.0f); int32 parry_chance = int32(GetUnitParryChance(attType, victim) * 100.0f); + //npcbot - expertise + if (IsNPCBot()) + { + int32 reductionFromExpertise = ToCreature()->GetCreatureExpertise() * 100 / 4; + dodge_chance -= reductionFromExpertise; + parry_chance -= reductionFromExpertise; + } + //end npcbot + // melee attack table implementation // outcome priority: // 1. > 2. > 3. > 4. > 5. > 6. > 7. > 8. @@ -2175,7 +2305,28 @@ MeleeHitOutcome Unit::RollMeleeOutcomeAgainst(Unit const* victim, WeaponAttackTy // only creatures can dodge if attacker is behind bool canDodge = victim->GetTypeId() != TYPEID_PLAYER || canParryOrBlock; + //npcbot: player rules for dodge + if (victim->IsNPCBot() && !canParryOrBlock) + canDodge = false; + //end npcbot + // if victim is casting or cc'd it can't avoid attacks + //npcbot: allow some bot classes to parry while casting + if (victim->IsNPCBot()) + { + if (victim->HasUnitState(UNIT_STATE_CONTROLLED)) + { + canDodge = false; + canParryOrBlock = false; + } + else if (victim->IsNonMeleeSpellCast(false, false, true)) + { + canDodge = false; + canParryOrBlock = BotMgr::CanBotParryWhileCasting(victim->ToCreature()); + } + } + else + //end npcbot if (victim->IsNonMeleeSpellCast(false, false, true) || victim->HasUnitState(UNIT_STATE_CONTROLLED)) { canDodge = false; @@ -2211,6 +2362,9 @@ MeleeHitOutcome Unit::RollMeleeOutcomeAgainst(Unit const* victim, WeaponAttackTy // 4. GLANCING // Max 40% chance to score a glancing blow against mobs of the same or higher level (only players and pets, not for ranged weapons). + //npcbot: no glances on npcbots and their pets + if (!(victim->GetTypeId() == TYPEID_UNIT && victim->ToCreature()->IsNPCBotOrPet())) + //end npcbot if ((GetTypeId() == TYPEID_PLAYER || IsPet()) && victim->GetTypeId() != TYPEID_PLAYER && !victim->IsPet() && GetLevel() <= victim->GetLevelForTarget(this)) @@ -2562,6 +2716,10 @@ uint32 Unit::GetDefenseSkillValue(Unit const* target) const value += uint32(ToPlayer()->GetRatingBonusValue(CR_DEFENSE_SKILL)); return value; } + //npcbot - defense + else if (IsNPCBot()) + return ToCreature()->GetCreatureDefense(); + //end npcbot else return GetMaxSkillValueForLevel(target); } @@ -2584,6 +2742,14 @@ float Unit::GetUnitDodgeChance(WeaponAttackType attType, Unit const* victim) con if (!victim->IsTotem()) { chance = 5.0f; + //npcbot - custom dodge chance instead of bunch of auras and remove base chance + if (victim->IsNPCBot()) + { + if (!victim->ToCreature()->CanDodge()) + return 0.f; + chance = victim->ToCreature()->GetCreatureDodgeChance(); + } + //end npcbot chance += victim->GetTotalAuraModifier(SPELL_AURA_MOD_DODGE_PERCENT); if (skillDiff <= 10) @@ -2636,6 +2802,14 @@ float Unit::GetUnitParryChance(WeaponAttackType attType, Unit const* victim) con if (!victim->IsTotem() && !(victim->ToCreature()->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_NO_PARRY)) { chance = 5.0f; + //npcbot - custom parry chance instead of bunch of auras + if (victim->IsNPCBot()) + { + if (!victim->ToCreature()->CanParry()) + return 0.f; + chance = victim->ToCreature()->GetCreatureParryChance(); + } + //end npcbot chance += victim->GetTotalAuraModifier(SPELL_AURA_MOD_PARRY_PERCENT); if (skillDiff <= 10) @@ -2662,6 +2836,11 @@ float Unit::GetUnitMissChance() const if (Player const* player = ToPlayer()) miss_chance += player->GetMissPercentageFromDefense(); + //npcbot: defense skill bonus + if (Creature const* creature = ToCreature()) + miss_chance += (creature->GetCreatureDefense() - GetLevel() * 5) * 0.04f; + //end npcbot + return miss_chance; } @@ -2690,6 +2869,10 @@ float Unit::GetUnitBlockChance(WeaponAttackType attType, Unit const* victim) con if (!victim->IsTotem() && !(victim->ToCreature()->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_NO_BLOCK)) { chance = 5.0f; + //npcbot - custom block chance instead of bunch of auras and remove base chance + if (victim->IsNPCBot()) + chance = victim->ToCreature()->GetCreatureBlockChance(); + //end npcbot chance += victim->GetTotalAuraModifier(SPELL_AURA_MOD_BLOCK_PERCENT); if (skillDiff <= 10) @@ -2730,6 +2913,10 @@ float Unit::GetUnitCriticalChanceDone(WeaponAttackType attackType) const if (!(ToCreature()->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_NO_CRIT)) { chance = 5.0f; + //npcbot - custom crit chance instead of bunch of auras and remove base chance + if (IsNPCBot()) + chance = ToCreature()->GetCreatureCritChance(); + //end npcbot chance += GetTotalAuraModifier(SPELL_AURA_MOD_WEAPON_CRIT_PERCENT); chance += GetTotalAuraModifier(SPELL_AURA_MOD_CRIT_PCT); } @@ -3032,6 +3219,11 @@ void Unit::InterruptSpell(CurrentSpellTypes spellType, bool withDelayed, bool wi if (GetTypeId() == TYPEID_PLAYER) ToPlayer()->SendAutoRepeatCancel(this); + //npcbot + if (IsNPCBot()) + BotMgr::OnBotSpellInterrupt(this, spellType); + //end npcbot + if (spell->getState() != SPELL_STATE_FINISHED) spell->cancel(result, resultOther); else @@ -3828,6 +4020,11 @@ void Unit::RemoveAurasDueToSpellByDispel(uint32 spellId, uint32 dispellerSpellId // Call AfterDispel hook on AuraScript aura->CallScriptAfterDispel(&dispelInfo); + //npcbot: hook dispels + if (dispeller->IsNPCBot()) + BotMgr::OnBotDispelDealt(dispeller->ToUnit(), this, dispelInfo.GetRemovedCharges()); + //end npcbot + return; } else @@ -5083,6 +5280,23 @@ std::vector Unit::GetGameObjects(uint32 spellId) const return gameobjects; } +//npcbot +GameObject* Unit::GetFirstGameObjectById(uint32 id) const +{ + for (GameObjectList::const_iterator i = m_gameObj.begin(); i != m_gameObj.end(); ++i) + if ((*i)->GetEntry() == id) + return *i; + + return nullptr; +} + +void Unit::SetCreator(Unit* creator) +{ + //creator is unrelated to creator guid + m_creator = creator; +} +//end npcbot + void Unit::AddGameObject(GameObject* gameObj) { if (!gameObj || gameObj->GetOwnerGUID()) @@ -5422,6 +5636,22 @@ void Unit::SetPowerType(Powers new_powertype, bool sendUpdate/* = true*/) owner->ToPlayer()->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PET_POWER_TYPE); } } + //npcbot + else if (IsNPCBotOrPet()) + { + if (IsNPCBot()) + { + if (ToCreature()->GetBotGroup()) + BotMgr::SetBotGroupUpdateFlag(ToCreature(), GROUP_UPDATE_FLAG_POWER_TYPE); + } + else + { + Unit const* owner = GetOwner(); + if (owner && owner->IsNPCBot() && owner->ToCreature()->GetBotsPet() == this && owner->ToCreature()->GetBotGroup()) + BotMgr::SetBotGroupUpdateFlag(owner->ToCreature(), GROUP_UPDATE_FLAG_PET_POWER_TYPE); + } + } + //end npcbot // Update max power UpdateMaxPower(new_powertype); @@ -5605,6 +5835,9 @@ bool Unit::Attack(Unit* victim, bool meleeAttack) // ToCreature()->SetCombatStartPosition(GetPositionX(), GetPositionY(), GetPositionZ()); if (creature && !IsControlledByPlayer()) + //npcbot - not for npcbots either + if (!creature->IsNPCBotOrPet()) + //end npcbot { EngageWithTarget(victim); // ensure that anything we're attacking has threat @@ -5706,6 +5939,19 @@ void Unit::CombatStopWithPets(bool includingCast) for (Unit* minion : m_Controlled) minion->CombatStop(includingCast); + + //npcbot: combatstop for bots + if (GetTypeId() == TYPEID_PLAYER && ToPlayer()->HaveBot()) + { + BotMap const* map = ToPlayer()->GetBotMgr()->GetBotMap(); + for (BotMap::const_iterator itr = map->begin(); itr != map->end(); ++itr) + { + itr->second->CombatStop(includingCast); + if (Unit* botPet = itr->second->GetBotsPet()) + botPet->CombatStop(includingCast); + } + } + //end npcbot } bool Unit::isAttackingPlayer() const @@ -5863,6 +6109,10 @@ Player* Unit::GetControllingPlayer() const return master->GetControllingPlayer(); return nullptr; } + //npcbot + else if (IsNPCBotOrPet() && ToUnit()->GetCreator()) + return ToUnit()->GetCreator()->ToPlayer(); + //end npcbot else return const_cast(ToPlayer()); } @@ -6193,6 +6443,14 @@ void Unit::SetCharm(Unit* charm, bool apply) player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HIGHEST_HEAL_CAST, addhealth); } + //npcbot + if (unit->IsNPCBot()) + { + Creature const* bot = unit->ToCreature(); + if (Battleground* bg = bot->GetBotBG()) + bg->UpdateBotScore(bot, SCORE_HEALING_DONE, gain); + } + //end npcbot } if (Player* player = victim->ToPlayer()) @@ -6305,13 +6563,23 @@ Unit* Unit::GetNextRandomRaidMemberOrPet(float radius) else if (GetTypeId() == TYPEID_UNIT && IsPet()) player = GetOwner()->ToPlayer(); + //npcbot + /* + //end npcbot if (!player) return nullptr; Group* group = player->GetGroup(); + //npcbot + */ + Group* group = player ? player->GetGroup() : IsNPCBot() ? ToCreature()->GetBotGroup() : nullptr; + //end npcbot // When there is no group check pet presence if (!group) { // We are pet now, return owner + //npcbot + if (player) + //end npcbot if (player != this) return IsWithinDistInMap(player, radius) ? player : nullptr; Unit* pet = GetGuardianPet(); @@ -6339,6 +6607,17 @@ Unit* Unit::GetNextRandomRaidMemberOrPet(float radius) nearMembers.push_back(pet); } + //npcbot + for (GroupBotReference* itr = group->GetFirstBotMember(); itr != nullptr; itr = itr->next()) + { + if (Creature* bot = itr->GetSource()) + { + if (bot != this && bot->IsAlive() && IsWithinDistInMap(bot, radius) && !IsHostileTo(bot)) + nearMembers.push_back(bot); + } + } + //end npcbot + if (nearMembers.empty()) return nullptr; @@ -6433,7 +6712,13 @@ void Unit::SendEnergizeSpellLog(Unit* victim, uint32 spellId, int32 damage, Powe void Unit::EnergizeBySpell(Unit* victim, uint32 spellId, int32 damage, Powers powerType) { if (SpellInfo const* info = sSpellMgr->GetSpellInfo(spellId)) + { + //npcbot: override spellInfo + info = info->TryGetSpellInfoOverride(this); + //end npcbot + EnergizeBySpell(victim, info, damage, powerType); + } } void Unit::EnergizeBySpell(Unit* victim, SpellInfo const* spellInfo, int32 damage, Powers powerType) @@ -6604,6 +6889,11 @@ float Unit::SpellDamagePctDone(Unit* victim, SpellInfo const* spellProto, Damage // Done total percent damage auras float DoneTotalMod = 1.0f; + //npcbot: do not affect bots + if (GetTypeId() == TYPEID_UNIT && ToCreature()->IsNPCBotOrPet()) + { /*do nothing*/ } + else + //end npcbot // Pet damage? if (GetTypeId() == TYPEID_UNIT && !IsPet()) DoneTotalMod *= ToCreature()->GetSpellDamageMod(ToCreature()->GetCreatureTemplate()->rank); @@ -6941,6 +7231,11 @@ uint32 Unit::SpellDamageBonusTaken(Unit* caster, SpellInfo const* spellProto, ui // multiplicative bonus, for example Dispersion + Shadowform (0.10*0.85=0.085) TakenTotalMod *= GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN, spellProto->GetSchoolMask()); + //npcbot - damage taken modifier + if (IsNPCBot()) + TakenTotalMod *= BotMgr::GetBotDamageTakenMod(ToCreature(), true); + //end npcbot + // From caster spells if (caster) { @@ -6977,6 +7272,11 @@ int32 Unit::SpellBaseDamageBonusDone(SpellSchoolMask schoolMask) const { int32 DoneAdvertisedBenefit = GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_DAMAGE_DONE, schoolMask); + //npcbot: apply bot spellpower + if ((schoolMask & SPELL_SCHOOL_MASK_MAGIC) && IsNPCBot()) + DoneAdvertisedBenefit += ToCreature()->GetCreatureSpellPower(); + //end npcbot + if (GetTypeId() == TYPEID_PLAYER) { // Base value @@ -7005,6 +7305,9 @@ float Unit::SpellCritChanceDone(SpellInfo const* spellInfo, SpellSchoolMask scho { //! Mobs can't crit with spells. (Except player controlled) if (GetTypeId() == TYPEID_UNIT && !GetSpellModOwner()) + //npcbot - allow bots to crit + if (!ToCreature()->IsNPCBotOrPet()) + //end npcbot return 0.0f; // not critting spell @@ -7240,6 +7543,11 @@ float Unit::SpellCritChanceTaken(Unit const* caster, SpellInfo const* spellInfo, }); } + //npcbot - apply bot spell crit mods + if (caster && caster->IsNPCBot()) + caster->ToCreature()->ApplyBotCritMultiplierAll(this, crit_chance, spellInfo, schoolMask, attackType); + //end npcbot + return std::max(crit_chance, 0.0f); } @@ -7461,6 +7769,11 @@ uint32 Unit::SpellHealingBonusDone(Unit* victim, SpellInfo const* spellProto, ui if (Player* modOwner = GetSpellModOwner()) modOwner->ApplySpellMod(spellProto->Id, damagetype == DOT ? SPELLMOD_DOT : SPELLMOD_DAMAGE, heal); + //npcbot - healing bonus done for bots + if (IsNPCBot()) + ToCreature()->ApplyBotDamageMultiplierHeal(victim, heal, spellProto, damagetype, stack); + //end npcbot + return uint32(std::max(heal, 0.0f)); } @@ -7601,6 +7914,11 @@ int32 Unit::SpellBaseHealingBonusDone(SpellSchoolMask schoolMask) const return false; }); + //npcbot: apply bot spellpower to healing + if (IsNPCBot()) + advertisedBenefit += ToCreature()->GetCreatureSpellPower(); + //end npcbot + // Healing bonus of spirit, intellect and strength if (GetTypeId() == TYPEID_PLAYER) { @@ -8039,6 +8357,11 @@ uint32 Unit::MeleeDamageBonusTaken(Unit* attacker, uint32 pdamage, WeaponAttackT // ..taken TakenTotalMod *= GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN, attacker->GetMeleeDamageSchoolMask()); + //npcbot - damage taken modifier + if (IsNPCBot()) + TakenTotalMod *= BotMgr::GetBotDamageTakenMod(ToCreature(), false); + //end npcbot + // .. taken pct (special attacks) if (spellProto) { @@ -8173,6 +8496,25 @@ void Unit::Mount(uint32 mount, uint32 VehicleId, uint32 creatureEntry) SetUnitFlag(UNIT_FLAG_MOUNT); + //npcbot + if (IsNPCBot()) + { + if (VehicleId) + { + TC_LOG_ERROR("scripts", "NPCBot::Mount mounting {}, vehicle {} ({})", mount, VehicleId, creatureEntry); + if (CreateVehicleKit(VehicleId, creatureEntry)) + { + // Send others that we now have a vehicle + WorldPacket data(SMSG_PLAYER_VEHICLE_DATA, GetPackGUID().size()+4); + data << GetPackGUID(); + data << uint32(VehicleId); + SendMessageToSet(&data, true); + GetVehicleKit()->InstallAllAccessories(false); + } + } + } + else + //end npcbot if (Player* player = ToPlayer()) { // mount as a vehicle @@ -8233,6 +8575,19 @@ void Unit::Dismount() SendMessageToSet(&data, true); // dismount as a vehicle + //npcbot + if (IsNPCBot() && GetVehicleKit()) + { + //TC_LOG_ERROR("scripts", "NPCBot::Dismount dismounting vehicle {} (base {}, cre {})", + // GetVehicleKit()->GetVehicleInfo()->m_ID, GetVehicleKit()->GetBase()->GetEntry(), GetVehicleKit()->GetCreatureEntry()); + data.Initialize(SMSG_PLAYER_VEHICLE_DATA, 8 + 4); + data << GetPackGUID(); + data << uint32(0); + SendMessageToSetInRange(&data, GetVisibilityRange(), /*not used*/true); + RemoveVehicleKit(); + } + else + //end npcbot if (GetTypeId() == TYPEID_PLAYER && GetVehicleKit()) { // Send other players that we are no longer a vehicle @@ -8314,6 +8669,14 @@ void Unit::SetImmuneToPC(bool apply, bool keepCombat) for (auto const& pair : m_combatManager.GetPvPCombatRefs()) if (pair.second->GetOther(this)->HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED)) toEnd.push_back(pair.second); + //npcbot + for (auto const& pair : m_combatManager.GetPvECombatRefs()) + if (pair.second->GetOther(this)->IsNPCBotOrPet()) + toEnd.push_back(pair.second); + for (auto const& pair : m_combatManager.GetPvPCombatRefs()) + if (pair.second->GetOther(this)->IsNPCBotOrPet()) + toEnd.push_back(pair.second); + //end npcbot for (CombatReference* ref : toEnd) ref->EndCombat(); } @@ -8337,6 +8700,9 @@ void Unit::SetImmuneToNPC(bool apply, bool keepCombat) for (auto const& pair : m_combatManager.GetPvPCombatRefs()) if (!pair.second->GetOther(this)->HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED)) toEnd.push_back(pair.second); + //npcbot + toEnd.remove_if([this](CombatReference const* cref) { return cref->GetOther(this)->IsNPCBotOrPet(); }); + //end npcbot for (CombatReference* ref : toEnd) ref->EndCombat(); } @@ -8476,6 +8842,11 @@ bool Unit::IsAlwaysVisibleFor(WorldObject const* seer) const if (ownerPlayer->IsGroupVisibleFor(seerPlayer)) return true; + //npcbot - bots are always visible for owner + if (GetCreator() && seer->GetGUID() == GetCreator()->GetGUID()) + return true; + //end npcbot + return false; } @@ -8629,6 +9000,20 @@ void Unit::UpdateSpeed(UnitMoveType mtype) } } } + + //npcbot + if (creature->IsNPCBotPet() && !creature->IsInCombat() && GetMotionMaster()->GetCurrentMovementGeneratorType() == FOLLOW_MOTION_TYPE) + { + Unit* followed = ASSERT_NOTNULL(dynamic_cast(GetMotionMaster()->GetCurrentMovementGenerator()))->GetTarget(); + if (followed && (followed->GetGUID() == GetOwnerGUID() || followed->GetGUID() == GetCreatorGUID()) && !followed->IsInCombat()) + { + float ownerSpeed = followed->GetSpeedRate(mtype); + if (speed < ownerSpeed) + speed = ownerSpeed; + speed *= std::min(std::max(1.0f, 0.75f + (GetDistance(followed) - PET_FOLLOW_DIST) * 0.05f), 1.5f); + } + } + //end npcbot } // Apply strongest slow aura mod to speed @@ -8787,7 +9172,45 @@ void Unit::AtTargetAttacked(Unit* target, bool canInitialAggro) myPlayerOwner->UpdatePvP(true); myPlayerOwner->SetContestedPvP(targetPlayerOwner); myPlayerOwner->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_ENTER_PVP_COMBAT); + + //npcbot: init contested PvP for owned bots + if (IsNPCBotOrPet() && target->IsNPCBotOrPet()) + { + if (Unit* bot = IsNPCBotPet() ? static_cast(myPlayerOwner->GetBotMgr()->GetBot(GetOwnerGUID())) : this) + { + BotMgr::SetBotContestedPvP(bot->ToCreature()); + bot->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_ENTER_PVP_COMBAT); + } + } + //end npcbot + } + //npcbot: init contested PvP for free bots + else if (!myPlayerOwner && IsNPCBotOrPet() && (target->IsNPCBotOrPet() || targetPlayerOwner)) + { + if (Unit* bot = IsNPCBotPet() ? GetCreator() : this) + { + BotMgr::SetBotContestedPvP(bot->ToCreature()); + bot->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_ENTER_PVP_COMBAT); + } } + else if (myPlayerOwner && !targetPlayerOwner && target->IsNPCBotOrPet() && (IsPlayer() || IsNPCBotOrPet())) + { + if (IsPlayer()) + { + myPlayerOwner->UpdatePvP(true); + myPlayerOwner->SetContestedPvP(); + myPlayerOwner->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_ENTER_PVP_COMBAT); + } + else // if (IsNPCBotOrPet()) + { + if (Unit* bot = IsNPCBotPet() ? static_cast(myPlayerOwner->GetBotMgr()->GetBot(GetOwnerGUID())) : this) + { + BotMgr::SetBotContestedPvP(bot->ToCreature()); + bot->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_ENTER_PVP_COMBAT); + } + } + } + //end npcbot } void Unit::UpdatePetCombatState() @@ -8854,6 +9277,12 @@ bool Unit::ApplyDiminishingToDuration(SpellInfo const* auraSpellInfo, bool trigg if (target->IsAffectedByDiminishingReturns() && source->GetTypeId() == TYPEID_PLAYER) duration = limitDuration; + + //npcbot: limit duration if casted by npcbots + if (target->GetTypeId() == TYPEID_PLAYER && source->GetTypeId() == TYPEID_UNIT && + source->ToCreature()->IsNPCBotOrPet()) + duration = limitDuration; + //end npcbots } float mod = 1.0f; @@ -8930,6 +9359,17 @@ uint32 Unit::GetCreatureType() const return raceEntry->CreatureType; } } + //npcbot: support for druid's shapeshifting + else if (IsNPCBot()) + { + ShapeshiftForm form = GetShapeshiftForm(); + SpellShapeshiftFormEntry const* ssEntry = sSpellShapeshiftFormStore.LookupEntry(form); + if (ssEntry && ssEntry->CreatureType > 0) + return ssEntry->CreatureType; + else + return CREATURE_TYPE_HUMANOID; + } + //end npcbot else return ToCreature()->GetCreatureTemplate()->type; } @@ -9316,6 +9756,11 @@ float Unit::GetWeaponDamageRange(WeaponAttackType attType, WeaponDamageRange typ bool Unit::CanFreeMove() const { + //npcbot: skip owner guid condition for bots + if (IsNPCBotOrPet()) + return !HasUnitState(UNIT_STATE_CONFUSED | UNIT_STATE_FLEEING | UNIT_STATE_IN_FLIGHT | + UNIT_STATE_ROOT | UNIT_STATE_STUNNED | UNIT_STATE_DISTRACTED); + //end npcbot return !HasUnitState(UNIT_STATE_CONFUSED | UNIT_STATE_FLEEING | UNIT_STATE_IN_FLIGHT | UNIT_STATE_ROOT | UNIT_STATE_STUNNED | UNIT_STATE_DISTRACTED) && GetOwnerGUID().IsEmpty(); } @@ -9335,6 +9780,13 @@ void Unit::SetLevel(uint8 lvl, bool sendUpdate/* = true*/) sCharacterCache->UpdateCharacterLevel(GetGUID(), lvl); } + //npcbot + else if (IsNPCBot()) + { + if (ToCreature()->GetBotGroup()) + BotMgr::SetBotGroupUpdateFlag(ToCreature(), GROUP_UPDATE_FLAG_LEVEL); + } + //end npcbot } void Unit::SetHealth(uint32 val) @@ -9367,6 +9819,22 @@ void Unit::SetHealth(uint32 val) owner->ToPlayer()->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PET_CUR_HP); } } + //npcbot + else if (IsNPCBotOrPet()) + { + if (IsNPCBot()) + { + if (ToCreature()->GetBotGroup()) + BotMgr::SetBotGroupUpdateFlag(ToCreature(), GROUP_UPDATE_FLAG_CUR_HP); + } + else + { + Unit const* owner = GetOwner(); + if (owner && owner->IsNPCBot() && owner->ToCreature()->GetBotsPet() == this && owner->ToCreature()->GetBotGroup()) + BotMgr::SetBotGroupUpdateFlag(owner->ToCreature(), GROUP_UPDATE_FLAG_PET_CUR_HP); + } + } + //end npcbot } void Unit::SetMaxHealth(uint32 val) @@ -9392,6 +9860,22 @@ void Unit::SetMaxHealth(uint32 val) owner->ToPlayer()->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PET_MAX_HP); } } + //npcbot + else if (IsNPCBotOrPet()) + { + if (IsNPCBot()) + { + if (ToCreature()->GetBotGroup()) + BotMgr::SetBotGroupUpdateFlag(ToCreature(), GROUP_UPDATE_FLAG_MAX_HP); + } + else + { + Unit const* owner = GetOwner(); + if (owner && owner->IsNPCBot() && owner->ToCreature()->GetBotsPet() == this && owner->ToCreature()->GetBotGroup()) + BotMgr::SetBotGroupUpdateFlag(owner->ToCreature(), GROUP_UPDATE_FLAG_PET_MAX_HP); + } + } + //end npcbot if (val < health) SetHealth(val); @@ -9436,6 +9920,22 @@ void Unit::SetPower(Powers power, uint32 val, bool withPowerUpdate /*= true*/, b if (pet->getPetType() == HUNTER_PET && power == POWER_HAPPINESS) pet->UpdateDamagePhysical(BASE_ATTACK); } + //npcbot + else if (IsNPCBotOrPet()) + { + if (IsNPCBot()) + { + if (ToCreature()->GetBotGroup()) + BotMgr::SetBotGroupUpdateFlag(ToCreature(), GROUP_UPDATE_FLAG_CUR_POWER); + } + else + { + Unit const* owner = GetOwner(); + if (owner && owner->IsNPCBot() && owner->ToCreature()->GetBotsPet() == this && owner->ToCreature()->GetBotGroup()) + BotMgr::SetBotGroupUpdateFlag(owner->ToCreature(), GROUP_UPDATE_FLAG_PET_CUR_POWER); + } + } + //end npcbot } void Unit::SetMaxPower(Powers power, uint32 val) @@ -9458,6 +9958,22 @@ void Unit::SetMaxPower(Powers power, uint32 val) owner->ToPlayer()->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PET_MAX_POWER); } } + //npcbot + else if (IsNPCBotOrPet()) + { + if (IsNPCBot()) + { + if (ToCreature()->GetBotGroup()) + BotMgr::SetBotGroupUpdateFlag(ToCreature(), GROUP_UPDATE_FLAG_MAX_POWER); + } + else + { + Unit const* owner = GetOwner(); + if (owner && owner->IsNPCBot() && owner->ToCreature()->GetBotsPet() == this && owner->ToCreature()->GetBotGroup()) + BotMgr::SetBotGroupUpdateFlag(owner->ToCreature(), GROUP_UPDATE_FLAG_PET_MAX_POWER); + } + } + //end npcbot if (val < cur_power) SetPower(power, val); @@ -10116,6 +10632,13 @@ void Unit::ProcSkillsAndReactives(bool isVictim, Unit* procTarget, uint32 typeMa ModifyAuraState(AURA_STATE_DEFENSE, true); StartReactiveTimer(REACTIVE_DEFENSE); } + //npcbot - update reactives for bots (victim) + if ((hitMask & PROC_HIT_PARRY) && IsNPCBot() && ToCreature()->GetBotClass() == CLASS_HUNTER) + { + ModifyAuraState(AURA_STATE_HUNTER_PARRY, true); + StartReactiveTimer(REACTIVE_HUNTER_PARRY); + } + //end npcbot } else // For attacker { @@ -10130,6 +10653,15 @@ void Unit::ProcSkillsAndReactives(bool isVictim, Unit* procTarget, uint32 typeMa AddComboPoints(procTarget, 1); StartReactiveTimer(REACTIVE_WOLVERINE_BITE); } + + //npcbot - update reactives for bots (attacker) + if ((hitMask & (PROC_HIT_DODGE | PROC_HIT_PARRY)) && IsNPCBot() && ToCreature()->GetBotClass() == CLASS_WARRIOR) + { + AddComboPoints(procTarget, 1); + StartReactiveTimer(REACTIVE_OVERPOWER); + } + //TODO REACTIVE_WOLVERINE_BITE for bot hunter pets + //end npcbot } } } @@ -10489,6 +11021,13 @@ void Unit::SendComboPoints() Player* owner = nullptr; if (ownerGuid.IsPlayer()) owner = ObjectAccessor::GetPlayer(*this, ownerGuid); + //npcbot + else if (IsNPCBotOrPet()) + { + if (Unit* creator = ToUnit()->GetCreator()) + owner = creator->ToPlayer(); + } + //end npcbot if (movingMe || owner) { WorldPacket data; @@ -10509,6 +11048,32 @@ void Unit::ClearComboPointHolders() (*m_ComboPointHolders.begin())->ClearComboPoints(); // this also removes it from m_comboPointHolders } +//npcbot +void Unit::ClearReactive(ReactiveType reactive) +{ + m_reactiveTimer[reactive] = 0; + + switch (reactive) + { + case REACTIVE_DEFENSE: + if (HasAuraState(AURA_STATE_DEFENSE)) + ModifyAuraState(AURA_STATE_DEFENSE, false); + break; + case REACTIVE_HUNTER_PARRY: + if (GetClass() == CLASS_HUNTER && HasAuraState(AURA_STATE_HUNTER_PARRY)) + ModifyAuraState(AURA_STATE_HUNTER_PARRY, false); + break; + case REACTIVE_OVERPOWER: + if (GetClass() == CLASS_WARRIOR) + ClearComboPoints(); + break; + default: + break; + //TODO WOLVERINE_BITE clear + } +} +//end npcbot + void Unit::ClearAllReactives() { for (uint8 i = 0; i < MAX_REACTIVE; ++i) @@ -10638,6 +11203,9 @@ uint32 Unit::GetCastingTimeForBonus(SpellInfo const* spellProto, DamageEffectTyp { // Not apply this to creature cast spells with casttime == 0 if (CastingTime == 0 && GetTypeId() == TYPEID_UNIT && !IsPet()) + //npcbot - skip bots + if (!ToCreature()->IsNPCBotOrPet()) + //end npcbot return 3500; if (CastingTime > 7000) CastingTime = 7000; @@ -10751,6 +11319,28 @@ void Unit::UpdateAuraForGroup(uint8 slot) } } } + //npcbot + else if (IsNPCBotOrPet()) + { + if (IsNPCBot()) + { + if (ToCreature()->GetBotGroup()) + { + BotMgr::SetBotGroupUpdateFlag(ToCreature(), GROUP_UPDATE_FLAG_AURAS); + BotMgr::SetBotAuraUpdateMaskForRaid(ToCreature(), slot); + } + } + else + { + Unit const* owner = GetOwner(); + if (owner && owner->IsNPCBot() && owner->ToCreature()->GetBotsPet() == this && owner->ToCreature()->GetBotGroup()) + { + BotMgr::SetBotGroupUpdateFlag(owner->ToCreature(), GROUP_UPDATE_FLAG_PET_AURAS); + BotMgr::SetBotPetAuraUpdateMaskForRaid(ToCreature(), slot); + } + } + } + //end npcbot } void Unit::SetCantProc(bool apply) @@ -10916,6 +11506,11 @@ bool Unit::InitTamedPet(Pet* pet, uint8 level, uint32 spell_id) // find player: owner of controlled `this` or `this` itself maybe Player* player = nullptr; + //npcbot - loot recipient of bot's vehicle is owner + if (attacker && attacker->IsVehicle() && attacker->GetCharmerGUID().IsCreature() && attacker->GetCreator() && attacker->GetCreator()->IsPlayer()) + player = attacker->GetCreator()->ToPlayer(); + else + //end npcbot if (attacker) player = attacker->GetCharmerOrOwnerPlayerOrPlayerItself(); @@ -11014,6 +11609,11 @@ bool Unit::InitTamedPet(Pet* pet, uint8 level, uint32 spell_id) if (creature->GetLootMode() > 0) loot->generateMoneyLoot(creature->GetCreatureTemplate()->mingold, creature->GetCreatureTemplate()->maxgold); + //npcbot: spawn wandering bot kill reward + if (creature->IsNPCBot() && creature->IsWandererBot()) + BotMgr::OnBotWandererKilled(creature, looter); + //end npcbot + if (group) { if (hasLooterGuid) @@ -11030,6 +11630,11 @@ bool Unit::InitTamedPet(Pet* pet, uint8 level, uint32 spell_id) player->RewardPlayerAndGroupAtKill(victim, false); } + //npcbot: spawn wandering bot kill reward + if (creature && creature->IsNPCBot()) + BotMgr::OnBotKilled(creature, attacker); + //end npcbot + // Do KILL and KILLED procs. KILL proc is called only for the unit who landed the killing blow (and its owner - for pets and totems) regardless of who tapped the victim if (attacker && (attacker->IsPet() || attacker->IsTotem())) { @@ -11097,6 +11702,12 @@ bool Unit::InitTamedPet(Pet* pet, uint8 level, uint32 spell_id) // at original death (not at SpiritOfRedemtionTalent timeout) plrVictim->SetPvPDeath(player != nullptr); + //npcbot - bots should not cause durability loss + if (durabilityLoss && attacker && attacker->GetTypeId() == TYPEID_UNIT && attacker->ToCreature()->GetBotAI() && + !sWorld->getBoolConfig(CONFIG_DURABILITY_LOSS_IN_PVP)) + durabilityLoss = false; + //end npcbot + // only if not player and not controlled by player pet. And not at BG if ((durabilityLoss && !player && !victim->ToPlayer()->InBattleground()) || (player && sWorld->getBoolConfig(CONFIG_DURABILITY_LOSS_IN_PVP))) { @@ -11198,6 +11809,10 @@ bool Unit::InitTamedPet(Pet* pet, uint8 level, uint32 spell_id) { if (Player* playerVictim = victim->ToPlayer()) bg->HandleKillPlayer(playerVictim, player); + //npcbot: handler PvB bg kill + else if (victim->IsNPCBot() && victim->ToCreature()->GetBotBG() == bg) + bg->HandlePlayerKillBot(victim->ToCreature(), player); + //end npcbot else bg->HandleKillUnit(victim->ToCreature(), player); } @@ -11226,6 +11841,14 @@ bool Unit::InitTamedPet(Pet* pet, uint8 level, uint32 spell_id) { if (Player* killed = victim->ToPlayer()) sScriptMgr->OnPlayerKilledByCreature(killerCre, killed); + //npcbot: Creature Kill hook for owner + else if (Creature* killedCre = victim->ToCreature()) + { + Unit* killerCreOwner = killerCre->GetCreator(); + if (killerCre->IsNPCBotOrPet() && killerCreOwner && killerCreOwner->GetTypeId() == TYPEID_PLAYER) + sScriptMgr->OnCreatureKill(killerCreOwner->ToPlayer(), killedCre); + } + //end npcbot } } } @@ -11898,6 +12521,15 @@ bool Unit::IsInPartyWith(Unit const* unit) const (u1->GetTypeId() == TYPEID_PLAYER && u2->GetTypeId() == TYPEID_UNIT && u2->ToCreature()->GetCreatureTemplate()->type_flags & CREATURE_TYPE_FLAG_TREAT_AS_RAID_UNIT)) return true; + //npcbot + Player const* pla = u1->IsPlayer() ? u1->ToPlayer() : u2->IsPlayer() ? u2->ToPlayer() : nullptr; + Creature const* bot = u1->IsNPCBot() ? u1->ToCreature() : u2->IsNPCBot() ? u2->ToCreature() : nullptr; + if (pla && bot) + return (pla->GetGroup() && pla->GetGroup() == bot->GetBotGroup()) ? pla->GetSubGroup() == bot->GetSubGroup() : !!pla->GetBotMgr()->GetBot(bot->GetGUID()); + if (u1->IsNPCBot() && u2->IsNPCBot() && u1->ToCreature()->GetBotGroup() && u1->ToCreature()->GetBotGroup() == u2->ToCreature()->GetBotGroup()) + return u1->ToCreature()->GetSubGroup() == u2->ToCreature()->GetSubGroup(); + //end npcbot + return u1->GetTypeId() == TYPEID_UNIT && u2->GetTypeId() == TYPEID_UNIT && u1->GetFaction() == u2->GetFaction(); } @@ -11916,6 +12548,14 @@ bool Unit::IsInRaidWith(Unit const* unit) const else if ((u2->GetTypeId() == TYPEID_PLAYER && u1->GetTypeId() == TYPEID_UNIT && u1->ToCreature()->GetCreatureTemplate()->type_flags & CREATURE_TYPE_FLAG_TREAT_AS_RAID_UNIT) || (u1->GetTypeId() == TYPEID_PLAYER && u2->GetTypeId() == TYPEID_UNIT && u2->ToCreature()->GetCreatureTemplate()->type_flags & CREATURE_TYPE_FLAG_TREAT_AS_RAID_UNIT)) return true; + //npcbot + Player const* pla = u1->IsPlayer() ? u1->ToPlayer() : u2->IsPlayer() ? u2->ToPlayer() : nullptr; + Creature const* bot = u1->IsNPCBot() ? u1->ToCreature() : u2->IsNPCBot() ? u2->ToCreature() : nullptr; + if (pla && bot) + return (pla->GetGroup() && pla->GetGroup() == bot->GetBotGroup()) ? true : !!pla->GetBotMgr()->GetBot(bot->GetGUID()); + if (u1->IsNPCBot() && u2->IsNPCBot() && u1->ToCreature()->GetBotGroup()) + return u1->ToCreature()->GetBotGroup() == u2->ToCreature()->GetBotGroup(); + //end npcbot return u1->GetTypeId() == TYPEID_UNIT && u2->GetTypeId() == TYPEID_UNIT && u1->GetFaction() == u2->GetFaction(); } @@ -11927,9 +12567,21 @@ void Unit::GetPartyMembers(std::list &TagUnitMap) if (owner->GetTypeId() == TYPEID_PLAYER) group = owner->ToPlayer()->GetGroup(); + //npcbot: get bot group + if (!group && IsNPCBot()) + group = ToCreature()->GetBotGroup(); + //end npcbot + if (group) { + //npcbot: get bot group + /* + //end npcbot uint8 subgroup = owner->ToPlayer()->GetSubGroup(); + //npcbot: get bot group + */ + uint8 subgroup = owner->IsPlayer() ? owner->ToPlayer()->GetSubGroup() : group->GetMemberGroup(owner->GetGUID()); + //end npcbot for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) { @@ -11946,6 +12598,14 @@ void Unit::GetPartyMembers(std::list &TagUnitMap) TagUnitMap.push_back(pet); } } + //npcbot: count bots + for (GroupBotReference* itr = group->GetFirstBotMember(); itr != nullptr; itr = itr->next()) + { + Creature* bot = itr->GetSource(); + if (bot && group->GetMemberGroup(bot->GetGUID()) == subgroup && bot->IsAlive() && IsInMap(bot) && !IsHostileTo(bot)) + TagUnitMap.push_back(bot); + } + //end npcbot } else { @@ -11954,6 +12614,18 @@ void Unit::GetPartyMembers(std::list &TagUnitMap) if (Guardian* pet = owner->GetGuardianPet()) if ((pet == this || IsInMap(pet)) && pet->IsAlive()) TagUnitMap.push_back(pet); + + //npcbot: count bots + if (owner->GetTypeId() == TYPEID_PLAYER && owner->ToPlayer()->HaveBot()) + { + BotMap const* map = owner->ToPlayer()->GetBotMgr()->GetBotMap(); + for (BotMap::const_iterator it = map->begin(); it != map->end(); ++it) + { + if (it->second->IsAlive() && IsInMap(it->second) && !IsHostileTo(it->second)) + TagUnitMap.push_back(it->second); + } + } + //end npcbot } } @@ -11982,6 +12654,10 @@ Aura* Unit::AddAura(uint32 spellId, Unit* target) if (!spellInfo) return nullptr; + //npcbot: override spellInfo + spellInfo = spellInfo->TryGetSpellInfoOverride(this); + //end npcbot + return AddAura(spellInfo, MAX_EFFECT_MASK, target); } @@ -12046,6 +12722,11 @@ void Unit::SendPlaySpellImpact(ObjectGuid guid, uint32 id) const bool Unit::CanApplyResilience() const { + //npcbot: allow bots' damage to be mitigated by target's resilience + if (GetTypeId() == TYPEID_UNIT && ToCreature()->IsNPCBotOrPet()) + return true; + //end npcbot + return !IsVehicle() && GetOwnerGUID().IsPlayer(); } @@ -12122,12 +12803,27 @@ int32 Unit::CalculateAOEAvoidance(int32 damage, uint32 schoolMask, ObjectGuid co float Unit::MeleeSpellMissChance(Unit const* victim, WeaponAttackType attType, int32 skillDiff, uint32 spellId) const { SpellInfo const* spellInfo = spellId ? sSpellMgr->GetSpellInfo(spellId) : nullptr; + + //npcbot: override spellInfo + spellInfo = spellInfo ? spellInfo->TryGetSpellInfoOverride(this) : spellInfo; + //end npcbot + if (spellInfo && spellInfo->HasAttribute(SPELL_ATTR7_CANT_MISS)) return 0.f; //calculate miss chance float missChance = victim->GetUnitMissChance(); + //npcbot - custom miss chance instead of bunch of auras + if (IsNPCBot()) + { + if (!ToCreature()->CanMiss()) + return 0.f; + + missChance += ToCreature()->GetCreatureMissChance(); + } + //end npcbot + // melee attacks while dual wielding have +19% chance to miss if (!spellId && haveOffhandWeapon()) missChance += 19.0f; @@ -12184,6 +12880,11 @@ void Unit::SetPhaseMask(uint32 newPhaseMask, bool update) if ((*itr)->GetTypeId() == TYPEID_UNIT) (*itr)->SetPhaseMask(newPhaseMask, true); + //npcbot: update for temporarily uncontrolled bots (teleport, taxi) + if (GetTypeId() == TYPEID_PLAYER) + ToPlayer()->UpdatePhaseForBots(); + //end npcbot + for (uint8 i = 0; i < MAX_SUMMON_SLOT; ++i) if (m_SummonSlot[i]) if (Creature* summon = GetMap()->GetCreature(m_SummonSlot[i])) @@ -12239,6 +12940,10 @@ float Unit::GetCombatRatingReduction(CombatRating cr) const { if (Player const* player = ToPlayer()) return player->GetRatingBonusValue(cr); + //npcbot: get bot resilience + else if (GetTypeId() == TYPEID_UNIT && ToCreature()->IsNPCBotOrPet()) + return BotMgr::GetBotResilience(ToCreature()); + //end npcbot // Player's pet get resilience from owner else if (IsPet() && GetOwner()) if (Player* owner = GetOwner()->ToPlayer()) @@ -12438,6 +13143,183 @@ uint32 Unit::GetModelForForm(ShapeshiftForm form, uint32 spellId) const break; } } + else if (ToCreature() && ToCreature()->GetBotOwner() && ToCreature()->GetBotOwner()->ToPlayer()) + { + //this has to be modified after implementation of bots' appearances which will include player bytes emulation + Player const* player = ToCreature()->GetBotOwner(); + //let's make druids look according to player but base model must be selected based on our race + switch (form) + { + case FORM_CAT: + // Based on master's Hair color + if (GetRace() == RACE_NIGHTELF) + { + uint8 hairColor = player->GetByteValue(PLAYER_BYTES, 3); + switch (hairColor) + { + case 7: // Violet + case 8: + return 29405; + case 3: // Light Blue + return 29406; + case 0: // Green + case 1: // Light Green + case 2: // Dark Green + return 29407; + case 4: // White + return 29408; + default: // original - Dark Blue + return 892; + } + } + // Based on master's Skin color + else if (GetRace() == RACE_TAUREN) + { + uint8 skinColor = player->GetByteValue(PLAYER_BYTES, 0); + // Male master + if (GetGender() == GENDER_MALE) + { + switch (skinColor) + { + case 12: // White + case 13: + case 14: + case 18: // Completly White + return 29409; + case 9: // Light Brown + case 10: + case 11: + return 29410; + case 6: // Brown + case 7: + case 8: + return 29411; + case 0: // Dark + case 1: + case 2: + case 3: // Dark Grey + case 4: + case 5: + return 29412; + default: // original - Grey + return 8571; + } + } + // Female master + else switch (skinColor) + { + case 10: // White + return 29409; + case 6: // Light Brown + case 7: + return 29410; + case 4: // Brown + case 5: + return 29411; + case 0: // Dark + case 1: + case 2: + case 3: + return 29412; + default: // original - Grey + return 8571; + } + } + else if (Player::TeamForRace(GetRace()) == ALLIANCE) + return 892; + else + return 8571; + case FORM_DIREBEAR: + case FORM_BEAR: + // Based on Hair color + if (GetRace() == RACE_NIGHTELF) + { + uint8 hairColor = player->GetByteValue(PLAYER_BYTES, 3); + switch (hairColor) + { + case 0: // Green + case 1: // Light Green + case 2: // Dark Green + return 29413; // 29415? + case 6: // Dark Blue + return 29414; + case 4: // White + return 29416; + case 3: // Light Blue + return 29417; + default: // original - Violet + return 2281; + } + } + // Based on Skin color + else if (GetRace() == RACE_TAUREN) + { + uint8 skinColor = player->GetByteValue(PLAYER_BYTES, 0); + // Male + if (GetGender() == GENDER_MALE) + { + switch (skinColor) + { + case 0: // Dark (Black) + case 1: + case 2: + return 29418; + case 3: // White + case 4: + case 5: + case 12: + case 13: + case 14: + return 29419; + case 9: // Light Brown/Grey + case 10: + case 11: + case 15: + case 16: + case 17: + return 29420; + case 18: // Completly White + return 29421; + default: // original - Brown + return 2289; + } + } + // Female + else switch (skinColor) + { + case 0: // Dark (Black) + case 1: + return 29418; + case 2: // White + case 3: + return 29419; + case 6: // Light Brown/Grey + case 7: + case 8: + case 9: + return 29420; + case 10: // Completly White + return 29421; + default: // original - Brown + return 2289; + } + } + else if (Player::TeamForRace(GetRace()) == ALLIANCE) + return 2281; + else + return 2289; + case FORM_FLIGHT: + if (Player::TeamForRace(GetRace()) == ALLIANCE) + return 20857; + return 20872; + case FORM_FLIGHT_EPIC: + if (Player::TeamForRace(GetRace()) == ALLIANCE) + return 21243; + return 21244; + default: + break; + } + } uint32 modelid = 0; SpellShapeshiftFormEntry const* formEntry = sSpellShapeshiftFormStore.LookupEntry(form); @@ -12845,6 +13727,10 @@ bool Unit::CanSwim() const return false; if (HasUnitFlag(UNIT_FLAG_PET_IN_COMBAT)) return true; + //npcbot + if (IsNPCBotOrPet()) + return true; + //end npcbot return HasUnitFlag(UNIT_FLAG_RENAME | UNIT_FLAG_CAN_SWIM); } @@ -12938,6 +13824,11 @@ bool Unit::UpdatePosition(float x, float y, float z, float orientation, bool tel UpdatePositionData(); + //npcbot: send bot group update + if ((relocated || turn) && IsNPCBot()) + BotMgr::SetBotGroupUpdateFlag(ToCreature(), GROUP_UPDATE_FLAG_POSITION); + //end npcbot + _positionUpdateInfo.Relocated = relocated; _positionUpdateInfo.Turned = turn; @@ -13128,6 +14019,19 @@ void Unit::StopAttackFaction(uint32 faction_id) for (Unit* minion : m_Controlled) minion->StopAttackFaction(faction_id); + + //npcbot: stopattackfaction for bots + if (GetTypeId() == TYPEID_PLAYER && ToPlayer()->HaveBot()) + { + BotMap const* map = ToPlayer()->GetBotMgr()->GetBotMap(); + for (BotMap::const_iterator itr = map->begin(); itr != map->end(); ++itr) + { + itr->second->StopAttackFaction(faction_id); + if (Unit* botPet = itr->second->GetBotsPet()) + botPet->StopAttackFaction(faction_id); + } + } + //end npcbot } void Unit::OutDebugInfo() const @@ -13435,6 +14339,10 @@ void Unit::BuildValuesUpdate(uint8 updateType, ByteBuffer* data, Player const* t if (plr && plr->IsInSameRaidWith(target)) visibleFlag |= UF_FLAG_PARTY_MEMBER; + //npcbot + else if (IsNPCBotOrPet() && IsInRaidWith(target)) + visibleFlag |= UF_FLAG_PARTY_MEMBER; + //end npcbot Creature const* creature = ToCreature(); for (uint16 index = 0; index < m_valuesCount; ++index) @@ -13559,6 +14467,24 @@ void Unit::BuildValuesUpdate(uint8 updateType, ByteBuffer* data, Player const* t else fieldBuffer << m_uint32Values[index]; } + //npcbot + else if (IsNPCBotOrPet() && IsInRaidWith(target)) + { + FactionTemplateEntry const* ft1 = GetFactionTemplateEntry(); + FactionTemplateEntry const* ft2 = target->GetFactionTemplateEntry(); + if (!ft1->IsFriendlyTo(*ft2)) + { + if (index == UNIT_FIELD_BYTES_2) + // Allow targetting opposite faction in party when enabled in config + fieldBuffer << (m_uint32Values[UNIT_FIELD_BYTES_2] & ((UNIT_BYTE2_FLAG_SANCTUARY /*| UNIT_BYTE2_FLAG_AURAS | UNIT_BYTE2_FLAG_UNK5*/) << 8)); // this flag is at uint8 offset 1 !! + else + // pretend that all other HOSTILE players have own faction, to allow follow, heal, rezz (trade wont work) + fieldBuffer << uint32(target->GetFaction()); + } + else + fieldBuffer << m_uint32Values[index]; + } + //end npcbot else fieldBuffer << m_uint32Values[index]; } diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h index 42e94a53e..12c102209 100644 --- a/src/server/game/Entities/Unit/Unit.h +++ b/src/server/game/Entities/Unit/Unit.h @@ -316,7 +316,7 @@ struct PlayerMovementPendingChange } knockbackInfo; // used if knockback }; -enum CombatRating +enum CombatRating : uint8 { CR_WEAPON_SKILL = 0, CR_DEFENSE_SKILL = 1, @@ -1254,6 +1254,14 @@ class TC_GAME_API Unit : public WorldObject ObjectGuid GetCharmedGUID() const { return GetGuidValue(UNIT_FIELD_CHARM); } Unit* GetCharmed() const { return m_charmed; } + //npcbot + void SetControlledByPlayer(bool set) { m_ControlledByPlayer = set; } + GameObject* GetFirstGameObjectById(uint32 id) const; + void SetCreator(Unit* creator); + Unit* GetCreator() const { return m_creator; } + Unit* m_creator = nullptr; + //end npcbot + bool IsControlledByPlayer() const { return m_ControlledByPlayer; } Player* GetControllingPlayer() const; ObjectGuid GetCharmerOrOwnerGUID() const override { return IsCharmed() ? GetCharmerGUID() : GetOwnerGUID(); } @@ -1827,6 +1835,11 @@ class TC_GAME_API Unit : public WorldObject std::string GetDebugInfo() const override; + //npcbot + bool HasReactive(ReactiveType reactive) const { return m_reactiveTimer[reactive] > 0; } + void ClearReactive(ReactiveType reactive); + //end npcbot + protected: explicit Unit (bool isWorldObject); diff --git a/src/server/game/Entities/Vehicle/Vehicle.cpp b/src/server/game/Entities/Vehicle/Vehicle.cpp index 4011703c7..2146163b3 100755 --- a/src/server/game/Entities/Vehicle/Vehicle.cpp +++ b/src/server/game/Entities/Vehicle/Vehicle.cpp @@ -33,6 +33,10 @@ #include "Unit.h" #include "Util.h" +//npcbot +#include "botmgr.h" +//end npcbot + Vehicle::Vehicle(Unit* unit, VehicleEntry const* vehInfo, uint32 creatureEntry) : UsableSeatNum(0), _me(unit), _vehicleInfo(vehInfo), _creatureEntry(creatureEntry), _status(STATUS_NONE) { @@ -512,10 +516,21 @@ Vehicle* Vehicle::RemovePassenger(Unit* unit) if (seat->second.SeatInfo->Flags & VEHICLE_SEAT_FLAG_PASSENGER_NOT_SELECTABLE && !seat->second.Passenger.IsUninteractible) unit->RemoveUnitFlag(UNIT_FLAG_UNINTERACTIBLE); + //npcbot + if (unit->GetTypeId() == TYPEID_UNIT && unit->ToCreature()->GetBotAI()) + BotMgr::OnBotExitVehicle(unit->ToCreature(), this); + //end npcbot + seat->second.Passenger.Reset(); if (_me->GetTypeId() == TYPEID_UNIT && unit->GetTypeId() == TYPEID_PLAYER && seat->second.SeatInfo->Flags & VEHICLE_SEAT_FLAG_CAN_CONTROL) + { + //npcbot + if (unit->ToPlayer()->HaveBot()) + BotMgr::OnBotOwnerExitVehicle(unit->ToPlayer(), this); + //end npcbot _me->RemoveCharmedBy(unit); + } if (_me->IsInWorld()) { @@ -831,6 +846,16 @@ bool VehicleJoinEvent::Execute(uint64, uint32) else Target->GetBase()->RemoveNpcFlag(UNIT_NPC_FLAG_SPELLCLICK); } + //npcbot: do not allow other passengers on bot vehicles + if (Passenger->IsNPCBot()/* && + (Seat->second.SeatInfo->m_flags & VEHICLE_SEAT_FLAG_CAN_CONTROL)*/) + { + if (Target->GetBase()->GetTypeId() == TYPEID_PLAYER) + Target->GetBase()->RemoveFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_PLAYER_VEHICLE); + else + Target->GetBase()->RemoveFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_SPELLCLICK); + } + //end npcbot } Passenger->InterruptNonMeleeSpells(false); @@ -853,6 +878,14 @@ bool VehicleJoinEvent::Execute(uint64, uint32) player->UnsummonPetTemporaryIfAny(); } + //npcbot + if (Creature* bot = Passenger->ToCreature()) + { + if (Battleground* bg = bot->GetBotBG()) + bg->EventBotDroppedFlag(bot); + } + //end npcbot + if (veSeat->HasFlag(VEHICLE_SEAT_FLAG_PASSENGER_NOT_SELECTABLE)) Passenger->SetUnitFlag(UNIT_FLAG_UNINTERACTIBLE); @@ -867,6 +900,11 @@ bool VehicleJoinEvent::Execute(uint64, uint32) Passenger->m_movementInfo.transport.seat = Seat->first; Passenger->m_movementInfo.transport.guid = Target->GetBase()->GetGUID(); + //npcbot + if (Passenger->GetTypeId() == TYPEID_UNIT && Passenger->ToCreature()->GetBotAI()) + BotMgr::OnBotEnterVehicle(Passenger->ToCreature(), Target); + //end npcbot + if (Target->GetBase()->GetTypeId() == TYPEID_UNIT && Passenger->GetTypeId() == TYPEID_PLAYER && veSeat->HasFlag(VEHICLE_SEAT_FLAG_CAN_CONTROL)) { @@ -877,6 +915,10 @@ bool VehicleJoinEvent::Execute(uint64, uint32) Abort(0); return true; } + //npcbot + if (Passenger->ToPlayer()->HaveBot()) + BotMgr::OnBotOwnerEnterVehicle(Passenger->ToPlayer(), Target); + //end npcbot } Passenger->SendClearTarget(); // SMSG_BREAK_TARGET diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp index 31a1fa1d2..2a695efaa 100644 --- a/src/server/game/Globals/ObjectMgr.cpp +++ b/src/server/game/Globals/ObjectMgr.cpp @@ -57,6 +57,10 @@ #include "Vehicle.h" #include "World.h" +//npcbot +#include "botdatamgr.h" +//end npcbot + ScriptMapMap sSpellScripts; ScriptMapMap sEventScripts; ScriptMapMap sWaypointScripts; @@ -1215,6 +1219,11 @@ void ObjectMgr::CheckCreatureTemplate(CreatureTemplate const* cInfo) const_cast(cInfo)->expansion = 0; } + //npcbot: skip flags check and damage multiplier + if (cInfo->IsNPCBotOrPet()) + return; + //end npcbot + if (uint32 badFlags = (cInfo->flags_extra & ~CREATURE_FLAG_EXTRA_DB_ALLOWED)) { TC_LOG_ERROR("sql.sql", "Table `creature_template` lists creature (Entry: {}) with disallowed `flags_extra` {}, removing incorrect flag.", cInfo->Entry, badFlags); @@ -9047,6 +9056,82 @@ SkillRangeType GetSkillRangeType(SkillRaceClassInfoEntry const* rcEntry) return SKILL_RANGE_LEVEL; } +void ObjectMgr::LoadCreatureOutfits() +{ + uint32 oldMSTime = getMSTime(); + + _creatureOutfitStore.clear(); // for reload case (test only) + + // 0 1 2 3 4 5 6 7 + QueryResult result = WorldDatabase.Query("SELECT entry, race, gender, skin, face, hair, haircolor, facialhair, " + //8 9 10 11 12 13 14 15 16 17 18 + "head, shoulders, body, chest, waist, legs, feet, wrists, hands, back, tabard FROM creature_template_outfits"); + + if (!result) + { + TC_LOG_ERROR("server.loading", ">> Loaded 0 creature outfits. DB table `creature_template_outfits` is empty!"); + return; + } + + uint32 count = 0; + + do + { + Field* fields = result->Fetch(); + + uint32 i = 0; + uint32 entry = fields[i++].GetUInt32(); + + if (!GetCreatureTemplate(entry)) + { + TC_LOG_ERROR("server.loading", ">> Creature entry {} in `creature_template_outfits`, but not in `creature_template`!", entry); + continue; + } + + CreatureOutfit co; // const, shouldnt be changed after saving + co.race = fields[i++].GetUInt8(); + ChrRacesEntry const* rEntry = sChrRacesStore.LookupEntry(co.race); + if (!rEntry) + { + TC_LOG_ERROR("server.loading", ">> Creature entry {} in `creature_template_outfits` has incorrect race ({}).", entry, uint32(co.race)); + continue; + } + co.gender = fields[i++].GetUInt8(); + // Set correct displayId + switch (co.gender) + { + case GENDER_FEMALE: + _creatureTemplateStore[entry].Modelid1 = rEntry->FemaleDisplayID; + break; + case GENDER_MALE: + _creatureTemplateStore[entry].Modelid1 = rEntry->MaleDisplayID; + break; + default: + TC_LOG_ERROR("server.loading", ">> Creature entry {} in `creature_template_outfits` has invalid gender {}", entry, uint32(co.gender)); + continue; + } + _creatureTemplateStore[entry].Modelid2 = 0; + _creatureTemplateStore[entry].Modelid3 = 0; + _creatureTemplateStore[entry].Modelid4 = 0; + _creatureTemplateStore[entry].unit_flags2 |= UNIT_FLAG2_MIRROR_IMAGE; // Needed so client requests mirror packet + + co.skin = fields[i++].GetUInt8(); + co.face = fields[i++].GetUInt8(); + co.hair = fields[i++].GetUInt8(); + co.haircolor = fields[i++].GetUInt8(); + co.facialhair = fields[i++].GetUInt8(); + for (uint32 j = 0; j != MAX_CREATURE_OUTFIT_DISPLAYS; ++j) + co.outfit[j] = fields[i+j].GetUInt32(); + + _creatureOutfitStore[entry] = co; + + ++count; + } + while (result->NextRow()); + + TC_LOG_INFO("server.loading", ">> Loaded {} creature outfits in {} ms", count, GetMSTimeDiffToNow(oldMSTime)); +} + void ObjectMgr::LoadGameTele() { uint32 oldMSTime = getMSTime(); @@ -10306,6 +10391,18 @@ GameObjectOverride const* ObjectMgr::GetGameObjectOverride(ObjectGuid::LowType s CreatureTemplate const* ObjectMgr::GetCreatureTemplate(uint32 entry) const { + //npcbot: try fetch custom creature template + if (entry >= BOT_ENTRY_CREATE_BEGIN) + { + if (CreatureTemplate const* extra_template = BotDataMgr::GetBotExtraCreatureTemplate(entry)) + { + //custom creature template should only exist in custom container + ASSERT_NODEBUGINFO(_creatureTemplateStore.find(entry) == _creatureTemplateStore.end()); + return extra_template; + } + } + //end npcbot + return Trinity::Containers::MapGetValuePtr(_creatureTemplateStore, entry); } diff --git a/src/server/game/Globals/ObjectMgr.h b/src/server/game/Globals/ObjectMgr.h index cdd1981ad..638269ee2 100644 --- a/src/server/game/Globals/ObjectMgr.h +++ b/src/server/game/Globals/ObjectMgr.h @@ -169,6 +169,21 @@ struct GameTele typedef std::unordered_map GameTeleContainer; +#define MAX_CREATURE_OUTFIT_DISPLAYS 11 +struct CreatureOutfit +{ + uint8 race; + uint8 gender; + uint8 face; + uint8 skin; + uint8 hair; + uint8 facialhair; + uint8 haircolor; + uint32 outfit[MAX_CREATURE_OUTFIT_DISPLAYS]; +}; + +typedef std::unordered_map CreatureOutfitContainer; + enum ScriptsType { SCRIPTS_FIRST = 1, @@ -1221,6 +1236,8 @@ class TC_GAME_API ObjectMgr void LoadNPCSpellClickSpells(); + void LoadCreatureOutfits(); + void LoadGameTele(); void LoadGossipMenu(); @@ -1476,6 +1493,8 @@ class TC_GAME_API ObjectMgr bool AddGameTele(GameTele& data); bool DeleteGameTele(std::string_view name); + CreatureOutfitContainer const& GetCreatureOutfitMap() const { return _creatureOutfitStore; } + Trainer::Trainer const* GetTrainer(uint32 creatureId) const; std::vector const& GetClassTrainers(uint8 classId) const { return _classTrainers.at(classId); } @@ -1631,6 +1650,8 @@ class TC_GAME_API ObjectMgr PageTextContainer _pageTextStore; InstanceTemplateContainer _instanceTemplateStore; + CreatureOutfitContainer _creatureOutfitStore; + private: void LoadScripts(ScriptsType type); void LoadQuestRelationsHelper(QuestRelations& map, std::string const& table); diff --git a/src/server/game/Grids/Notifiers/GridNotifiers.h b/src/server/game/Grids/Notifiers/GridNotifiers.h index 5448b8e93..2e63d6e9a 100644 --- a/src/server/game/Grids/Notifiers/GridNotifiers.h +++ b/src/server/game/Grids/Notifiers/GridNotifiers.h @@ -892,6 +892,10 @@ namespace Trinity player = u->ToPlayer(); else if (u->IsPet() && u->GetOwner()) player = u->GetOwner()->ToPlayer(); + //npcbot: find bot owner + else if (u->GetTypeId() == TYPEID_UNIT && u->ToCreature()->IsNPCBotOrPet() && !u->ToCreature()->IsFreeBot()) + player = u->ToCreature()->GetBotOwner(); + //end npcbot if (!player) return false; diff --git a/src/server/game/Groups/Group.cpp b/src/server/game/Groups/Group.cpp index e3b870cba..a456afa7e 100644 --- a/src/server/game/Groups/Group.cpp +++ b/src/server/game/Groups/Group.cpp @@ -42,6 +42,11 @@ #include "WorldPacket.h" #include "WorldSession.h" +//npcbot +#include "botdatamgr.h" +#include "botmgr.h" +//end npcbot + Roll::Roll(ObjectGuid _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), @@ -148,6 +153,38 @@ void Group::SelectNewPartyOrRaidLeader() } } +//npcbot +bool Group::Create(Creature* leader) +{ + ASSERT(isBGGroup()); + + ObjectGuid leaderGuid = leader->GetGUID(); + ObjectGuid::LowType lowguid = sGroupMgr->GenerateGroupId(); + + m_guid = ObjectGuid(HighGuid::Group, lowguid); + m_leaderGuid = leaderGuid; + m_leaderName = leader->GetName(); + + m_groupType = GROUPTYPE_BGRAID; + + _initRaidSubGroupsCounter(); + + m_lootMethod = FREE_FOR_ALL; + + m_lootThreshold = ITEM_QUALITY_UNCOMMON; + m_looterGuid = leaderGuid; + m_masterLooterGuid.Clear(); + + m_dungeonDifficulty = DUNGEON_DIFFICULTY_NORMAL; + m_raidDifficulty = RAID_DIFFICULTY_10MAN_NORMAL; + + if (!AddMember(leader)) + return false; + + return true; +} +//end npcbot + bool Group::Create(Player* leader) { ObjectGuid leaderGuid = leader->GetGUID(); @@ -164,6 +201,15 @@ bool Group::Create(Player* leader) if (m_groupType & GROUPTYPE_RAID) _initRaidSubGroupsCounter(); + //npcbot - set loot mode on create + //TC_LOG_ERROR("entities.player", "Group::Create(): new group with leader {}", leader->GetName()); + if (leader->HaveBot()) //player + npcbot so set to free-for-all on create + { + if (!isLFGGroup()) + m_lootMethod = FREE_FOR_ALL; + } + else + //end npcbot if (!isLFGGroup()) m_lootMethod = GROUP_LOOT; @@ -283,6 +329,37 @@ void Group::LoadMemberFromDB(ObjectGuid::LowType guidLow, uint8 memberFlags, uin sLFGMgr->SetupGroupMember(member.guid, GetGUID()); } +//npcbot +void Group::LoadCreatureMemberFromDB(uint32 entry, uint8 memberFlags, uint8 subgroup, uint8 roles) +{ + MemberSlot member; + member.guid = BotDataMgr::GetNPCBotGuid(entry); + + // skip non-existed bot + if (member.guid == ObjectGuid::Empty) + { + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_NPCBOT_GROUP_MEMBER); + stmt->setUInt32(0, entry); + CharacterDatabase.Execute(stmt); + return; + } + + CreatureTemplate const* ct = sObjectMgr->GetCreatureTemplate(entry); + ASSERT(ct); + + member.name = ct->Name; + member.group = subgroup; + member.flags = memberFlags; + member.roles = roles; + + m_memberSlots.push_back(member); + + SubGroupCounterIncrease(subgroup); + + //sLFGMgr->SetupGroupMember(member.guid, GetGUID()); +} +//end npcbot + void Group::ConvertToLFG() { m_groupType = GroupType(m_groupType | GROUPTYPE_LFG | GROUPTYPE_LFG_RESTRICTED); @@ -393,6 +470,76 @@ Player* Group::GetInvited(const std::string& name) const return nullptr; } +//npcbot +bool Group::AddMember(Creature* creature) +{ + // Get first not-full group + uint8 subGroup = 0; + if (m_subGroupsCounts) + { + bool groupFound = false; + for (; subGroup < MAX_RAID_SUBGROUPS; ++subGroup) + { + if (m_subGroupsCounts[subGroup] < MAX_GROUP_SIZE) + { + groupFound = true; + break; + } + } + // We are raid group and no one slot is free + if (!groupFound) + return false; + } + + MemberSlot member; + member.guid = creature->GetGUID(); + member.name = creature->GetName(); + member.group = subGroup; + member.flags = 0; + member.roles = 0; + m_memberSlots.push_back(member); + + SubGroupCounterIncrease(subGroup); + + if (creature->GetBotGroup()) + { + if (isBGGroup() || isBFGroup()) // if player is in group and he is being added to BG raid group, then call SetBattlegroundRaid() + creature->SetBattlegroundOrBattlefieldRaid(this, subGroup); + else //if player is in bg raid and we are adding him to normal group, then call SetOriginalGroup() + creature->SetOriginalGroup(this, subGroup); + } + else //if player is not in group, then call set group + creature->SetBotGroup(this, subGroup); + + if (!isRaidGroup()) + { + for (uint8 i = 0; i < TARGET_ICONS_COUNT; ++i) + m_targetIcons[i].Clear(); + } + + // insert into the table if we're not a battleground group + if (!isBGGroup() && !isBFGroup()) + { + //INSERT INTO characters_npcbot_group_member (guid, entry, memberFlags, subgroup, roles) VALUES(?, ?, ?, ?, ?), CONNECTION_ASYNC + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_NPCBOT_GROUP_MEMBER); + stmt->setUInt32(0, m_dbStoreId); + stmt->setUInt32(1, member.guid.GetEntry()); + stmt->setUInt8(2, member.flags); + stmt->setUInt8(3, member.group); + stmt->setUInt8(4, member.roles); + CharacterDatabase.Execute(stmt); + } + + SendUpdate(); + sScriptMgr->OnGroupAddMember(this, creature->GetGUID()); + + BotMgr::SetBotGroupUpdateFlag(creature, GROUP_UPDATE_FULL); + UpdateBotOutOfRange(creature); + + return true; +} +//end npcbot + bool Group::AddMember(Player* player) { // Get first not-full group @@ -536,6 +683,11 @@ bool Group::AddMember(Player* player) if (m_maxEnchantingLevel < player->GetSkillValue(SKILL_ENCHANTING)) m_maxEnchantingLevel = player->GetSkillValue(SKILL_ENCHANTING); + //npcbot: if player has been added to bot BG raid switch leader to it + if (!m_leaderGuid.IsPlayer()) + ChangeLeader(player->GetGUID()); + //end npcbot + return true; } @@ -545,6 +697,74 @@ bool Group::RemoveMember(ObjectGuid guid, RemoveMethod const& method /*= GROUP_R sScriptMgr->OnGroupRemoveMember(this, guid, method, kicker, reason); + //npcbot: skip group size check before removing a bot + if (guid.IsCreature()) + { + // LFG group vote kick handled in scripts + if (isLFGGroup() && method == GROUP_REMOVEMETHOD_KICK) + return !m_memberSlots.empty(); + + if (GetMembersCount() > ((isBGGroup() || isLFGGroup() || isBFGroup()) ? 1u : 2u)) + { + if (Creature const* cbot = BotDataMgr::FindBot(guid.GetEntry())) + { + Creature* bot = const_cast(cbot); + if (isBGGroup() || isBFGroup()) + bot->RemoveFromBattlegroundOrBattlefieldRaid(); + else + { + if (bot->GetOriginalGroup() == this) + bot->SetOriginalGroup(nullptr); + else + bot->SetBotGroup(nullptr); + } + } + + // Remove bot from group in DB + if (!isBGGroup() && !isBFGroup()) + { + //DELETE FROM characters_npcbot_group_member WHERE entry = ?, CONNECTION_ASYNC + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_NPCBOT_GROUP_MEMBER); + stmt->setUInt32(0, guid.GetEntry()); + CharacterDatabase.Execute(stmt); + DelinkBotMember(guid); + } + + // Update subgroup + member_witerator slot = _getMemberWSlot(guid); + if (slot != m_memberSlots.end()) + { + SubGroupCounterDecrease(slot->group); + m_memberSlots.erase(slot); + } + + SendUpdate(); + + // do not disband raid group if bot owner logging out within dungeon + // 1-player raid groups will not happen unless player is gm - bots will rejoin at login + if (GetMembersCount() < 2 && isRaidGroup() && !(isBGGroup() || isBFGroup()) && GetLeaderGUID()) + { + Player const* player = ObjectAccessor::FindPlayer(GetLeaderGUID()); + Map const* map = player ? player->FindMap() : nullptr; + if (!(map && map->IsDungeon() && player && player->GetSession()->PlayerLogout())) + Disband(); + } + else if (GetMembersCount() < ((isBGGroup() || isLFGGroup() || isBFGroup()) ? 1u : 2u)) + { + Disband(); + return false; + } + + return true; + } + else + { + Disband(); + return false; + } + } + //end npcbot + Player* player = ObjectAccessor::FindConnectedPlayer(guid); if (player) { @@ -693,6 +913,9 @@ bool Group::RemoveMember(ObjectGuid guid, RemoveMethod const& method /*= GROUP_R } if (m_memberMgr.getSize() < ((isLFGGroup() || isBGGroup()) ? 1u : 2u)) + //npcbot: prevent group from being disbanded due to checking only players count + if (GetMembersCount() < ((isLFGGroup() || isBGGroup()) ? 1u : 2u)) + //end npcbot Disband(); return true; @@ -817,6 +1040,26 @@ void Group::Disband(bool hideDestroy /* = false */) Player* player; for (member_citerator citr = m_memberSlots.begin(); citr != m_memberSlots.end(); ++citr) { + //npcbot: set bot's group + if (citr->guid.IsCreature()) + { + if (Creature const* cbot = BotDataMgr::FindBot(citr->guid.GetEntry())) + { + Creature* bot = const_cast(cbot); + if (isBGGroup() || isBFGroup()) + bot->RemoveFromBattlegroundOrBattlefieldRaid(); + else + { + if (bot->GetOriginalGroup() == this) + bot->SetOriginalGroup(nullptr); + else + bot->SetBotGroup(nullptr); + } + } + continue; + } + //end npcbot + player = ObjectAccessor::FindConnectedPlayer(citr->guid); if (!player) continue; @@ -895,6 +1138,12 @@ void Group::Disband(bool hideDestroy /* = false */) stmt->setUInt32(0, m_dbStoreId); trans->Append(stmt); + //npcbot: bot members deletion + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_NPCBOT_GROUP_MEMBER_ALL); + stmt->setUInt32(0, m_dbStoreId); + trans->Append(stmt); + //end npcbot + CharacterDatabase.CommitTransaction(trans); ResetInstances(INSTANCE_RESET_GROUP_DISBAND, false, nullptr); @@ -1674,6 +1923,33 @@ void Group::SetTargetIcon(uint8 id, ObjectGuid whoGuid, ObjectGuid targetGuid) m_targetIcons[id] = targetGuid; + //npcbot: name cache + bool need_cache_name = false; + Player const* setter = nullptr; + for (GroupReference const* itr = GetFirstMember(); itr != nullptr; itr = itr->next()) + { + if (itr->GetSource()) + { + if (!need_cache_name && itr->GetSource()->GetBotMgr()) + need_cache_name = true; + if (!setter && itr->GetSource()->GetGUID() == whoGuid) + setter = itr->GetSource(); + } + } + + if (need_cache_name && setter) + { + Unit const* newtarget = targetGuid ? ObjectAccessor::GetUnit(*setter, targetGuid) : nullptr; + std::string const& newname = newtarget ? newtarget->GetName() : ""; + for (GroupReference const* itr = GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player const* member = itr->GetSource(); + if (member && member->GetBotMgr()) + member->GetBotMgr()->UpdateTargetIconName(id, newname); + } + } + //end npcbot + WorldPacket data(MSG_RAID_TARGET_UPDATE, (1+8+1+8)); data << uint8(0); // set targets data << uint64(whoGuid); @@ -1716,6 +1992,11 @@ void Group::SendUpdate() void Group::SendUpdateToPlayer(Player const* player, MemberSlot const* slot /*= nullptr*/) { + //npcbot + if (!player || !player->GetGUID().IsPlayer()) + return; + //end npcbot + if (player->GetGroup() != this) { if (player->GetOriginalGroup() == this) @@ -1759,6 +2040,10 @@ void Group::SendUpdateToPlayer(Player const* player, MemberSlot const* slot /*= uint8 onlineState = (member && !member->GetSession()->PlayerLogout()) ? MEMBER_STATUS_ONLINE : MEMBER_STATUS_OFFLINE; onlineState = onlineState | ((isBGGroup() || isBFGroup()) ? MEMBER_STATUS_PVP : 0); + //npcbot: bots are always online + onlineState |= citr->guid.IsCreature() ? 1 : 0; + //end npcbot + data << citr->name; data << uint64(citr->guid); // guid data << uint8(onlineState); // online-state @@ -1796,6 +2081,25 @@ void Group::SendOriginalGroupUpdateToPlayer(Player const* player) const player->SendDirectMessage(&data); } +//npcbot +void Group::UpdateBotOutOfRange(Creature* creature) +{ + if (!creature || !creature->IsInWorld() || m_memberMgr.isEmpty()) + return; + + WorldPacket data; + BotMgr::BuildBotPartyMemberStatsChangedPacket(creature, &data); + + Player* member; + for (GroupReference* itr = GetFirstMember(); itr != nullptr; itr = itr->next()) + { + member = itr->GetSource(); + if (member/* && (!member->IsInMap(creature) || !member->IsWithinDist(creature, member->GetSightRange(), false))*/) + member->SendDirectMessage(&data); + } +} +//end npcbot + void Group::UpdatePlayerOutOfRange(Player* player) { if (!player || !player->IsInWorld()) @@ -1923,6 +2227,24 @@ void Group::ChangeMembersGroup(ObjectGuid guid, uint8 group) CharacterDatabase.Execute(stmt); } + //npcbot + if (guid.IsCreature()) + { + Creature const* cbot = BotDataMgr::FindBot(guid.GetEntry()); + if (Creature* bot = cbot ? const_cast(cbot) : nullptr) + { + if (bot->GetBotGroup() == this) + bot->SetOriginalSubGroup(group); + else + { + // If player is in BG raid, it is possible that he is also in normal raid - and that normal raid is stored in m_originalGroup reference + prevSubGroup = bot->GetOriginalSubGroup(); + bot->SetOriginalSubGroup(group); + } + } + } + else + //end npcbot // In case the moved player is online, update the player object with the new sub group references if (Player* player = ObjectAccessor::FindConnectedPlayer(guid)) { @@ -2606,12 +2928,26 @@ void Group::SetGroupMemberFlag(ObjectGuid guid, bool apply, GroupMemberFlags fla ToggleGroupMemberFlag(slot, flag, apply); // Preserve the new setting in the db + //npcbot + if (!guid.IsPlayer()) + { + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_NPCBOT_GROUP_MEMBER_FLAG); + stmt->setUInt8(0, slot->flags); + stmt->setUInt32(1, guid.GetEntry()); + CharacterDatabase.Execute(stmt); + } + else + { + //end npcbot CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_GROUP_MEMBER_FLAG); stmt->setUInt8(0, slot->flags); stmt->setUInt32(1, guid.GetCounter()); CharacterDatabase.Execute(stmt); + //npcbot + } + //end npcbot // Broadcast the changes to the group SendUpdate(); @@ -2666,6 +3002,28 @@ void Group::DelinkMember(ObjectGuid guid) } } +//npcbot +void Group::LinkBotMember(GroupBotReference* bRef) +{ + m_botMemberMgr.insertFirst(bRef); +} + +void Group::DelinkBotMember(ObjectGuid guid) +{ + GroupBotReference* ref = m_botMemberMgr.getFirst(); + while (ref) + { + GroupBotReference* nextRef = ref->next(); + if (ref->GetSource()->GetGUID() == guid) + { + ref->unlink(); + break; + } + ref = nextRef; + } +} +//end npcbot + Group::BoundInstancesMap& Group::GetBoundInstances(Difficulty difficulty) { return m_boundInstances[difficulty]; diff --git a/src/server/game/Groups/Group.h b/src/server/game/Groups/Group.h index f03a2fc44..a78e5655c 100644 --- a/src/server/game/Groups/Group.h +++ b/src/server/game/Groups/Group.h @@ -192,6 +192,16 @@ class TC_GAME_API Group bool Create(Player* leader); void LoadGroupFromDB(Field* field); void LoadMemberFromDB(ObjectGuid::LowType guidLow, uint8 memberFlags, uint8 subgroup, uint8 roles); + //npcbot + bool Create(Creature* leader); + bool AddMember(Creature* creature); + void LoadCreatureMemberFromDB(uint32 entry, uint8 memberFlags, uint8 subgroup, uint8 roles); + void UpdateBotOutOfRange(Creature* creature); + void LinkBotMember(GroupBotReference* bRef); + void DelinkBotMember(ObjectGuid guid); + GroupBotReference* GetFirstBotMember() { return m_botMemberMgr.getFirst(); } + GroupBotReference const* GetFirstBotMember() const { return m_botMemberMgr.getFirst(); } + //end npcbot bool AddInvite(Player* player); void RemoveInvite(Player* player); void RemoveAllInvites(); @@ -339,6 +349,10 @@ class TC_GAME_API Group // FG: evil hacks void BroadcastGroupUpdate(void); + //npcbots + ObjectGuid const* GetTargetIcons() const { return m_targetIcons; } + //end npcbots + Trinity::unique_weak_ptr GetWeakPtr() const { return m_scriptRef; } protected: @@ -354,6 +368,9 @@ class TC_GAME_API Group MemberSlotList m_memberSlots; GroupRefManager m_memberMgr; + //npcbot + GroupBotRefManager m_botMemberMgr; + //end npcbot InvitesList m_invitees; ObjectGuid m_leaderGuid; std::string m_leaderName; diff --git a/src/server/game/Groups/GroupMgr.cpp b/src/server/game/Groups/GroupMgr.cpp index 4a64c5a42..ab2e02ba5 100644 --- a/src/server/game/Groups/GroupMgr.cpp +++ b/src/server/game/Groups/GroupMgr.cpp @@ -133,7 +133,14 @@ void GroupMgr::LoadGroups() // Delete all groups whose leader does not exist CharacterDatabase.DirectExecute("DELETE FROM `groups` WHERE leaderGuid NOT IN (SELECT guid FROM characters)"); // Delete all groups with less than 2 members + //npcbot: adjust this + /* + //end npcbot CharacterDatabase.DirectExecute("DELETE FROM `groups` WHERE guid NOT IN (SELECT guid FROM group_member GROUP BY guid HAVING COUNT(guid) > 1)"); + //npcbot + */ + CharacterDatabase.DirectExecute("DELETE FROM `groups` WHERE guid NOT IN (SELECT guid from group_member GROUP BY guid HAVING (SELECT (SELECT COUNT(guid) FROM group_member) + (SELECT COUNT(guid) FROM characters_npcbot_group_member)) > 1)"); + //end npcbot // 0 1 2 3 4 5 6 7 8 9 QueryResult result = CharacterDatabase.Query("SELECT g.leaderGuid, g.lootMethod, g.looterGuid, g.lootThreshold, g.icon1, g.icon2, g.icon3, g.icon4, g.icon5, g.icon6" diff --git a/src/server/game/Groups/GroupRefManager.h b/src/server/game/Groups/GroupRefManager.h index acf4b7bb9..c80f51b34 100644 --- a/src/server/game/Groups/GroupRefManager.h +++ b/src/server/game/Groups/GroupRefManager.h @@ -30,4 +30,15 @@ class GroupRefManager : public RefManager GroupReference* getFirst() { return ((GroupReference*)RefManager::getFirst()); } GroupReference const* getFirst() const { return ((GroupReference const*)RefManager::getFirst()); } }; + +//npcbot +class Creature; + +class GroupBotRefManager : public RefManager +{ + public: + GroupBotReference* getFirst() { return ((GroupBotReference*)RefManager::getFirst()); } + GroupBotReference const* getFirst() const { return ((GroupBotReference const*)RefManager::getFirst()); } +}; +//end npcbot #endif diff --git a/src/server/game/Groups/GroupReference.cpp b/src/server/game/Groups/GroupReference.cpp index 5eaa242e2..058d392fe 100644 --- a/src/server/game/Groups/GroupReference.cpp +++ b/src/server/game/Groups/GroupReference.cpp @@ -35,3 +35,23 @@ void GroupReference::sourceObjectDestroyLink() // called from invalidate() //getTarget()->DelinkMember(this); } + +//npcbot +void GroupBotReference::targetObjectBuildLink() +{ + // called from link() + getTarget()->LinkBotMember(this); +} + +void GroupBotReference::targetObjectDestroyLink() +{ + // called from unlink() + //getTarget()->DelinkMember(this); +} + +void GroupBotReference::sourceObjectDestroyLink() +{ + // called from invalidate() + //getTarget()->DelinkMember(this); +} +//end npcbot diff --git a/src/server/game/Groups/GroupReference.h b/src/server/game/Groups/GroupReference.h index f0075e7cd..955a245ac 100644 --- a/src/server/game/Groups/GroupReference.h +++ b/src/server/game/Groups/GroupReference.h @@ -38,4 +38,24 @@ class TC_GAME_API GroupReference : public Reference uint8 getSubGroup() const { return iSubGroup; } void setSubGroup(uint8 pSubGroup) { iSubGroup = pSubGroup; } }; + +//npcbot +class Creature; + +class TC_GAME_API GroupBotReference : public Reference +{ + protected: + uint8 iSubGroup; + void targetObjectBuildLink() override; + void targetObjectDestroyLink() override; + void sourceObjectDestroyLink() override; + public: + GroupBotReference() : Reference(), iSubGroup(0) { } + ~GroupBotReference() { unlink(); } + GroupBotReference* next() { return (GroupBotReference*)Reference::next(); } + GroupBotReference const* next() const { return (GroupBotReference const*)Reference::next(); } + uint8 getSubGroup() const { return iSubGroup; } + void setSubGroup(uint8 pSubGroup) { iSubGroup = pSubGroup; } +}; +//end npcbot #endif diff --git a/src/server/game/Handlers/BattleGroundHandler.cpp b/src/server/game/Handlers/BattleGroundHandler.cpp index 3facbdaa5..8b02b4746 100644 --- a/src/server/game/Handlers/BattleGroundHandler.cpp +++ b/src/server/game/Handlers/BattleGroundHandler.cpp @@ -38,6 +38,11 @@ #include "World.h" #include "WorldPacket.h" +//npcbot +#include "botdatamgr.h" +#include "botmgr.h" +//end npcbot + void WorldSession::HandleBattlemasterHelloOpcode(WorldPacket& recvData) { ObjectGuid guid; @@ -193,6 +198,22 @@ void WorldSession::HandleBattlemasterJoinOpcode(WorldPacket& recvData) if (_player->HasAura(9454)) return; + //npcbot: do not allow entering as group if there are bots in group + if (_player->GetGroup() && _player->HaveBot()) + { + for (auto const& mslot : _player->GetGroup()->GetMemberSlots()) + { + if (mslot.guid.IsCreature() && _player->GetBotMgr()->GetBot(mslot.guid)) + { + WorldPacket data; + sBattlegroundMgr->BuildGroupJoinedBattlegroundPacket(&data, ERR_BATTLEGROUND_JOIN_FAILED); + _player->SendDirectMessage(&data); + return; + } + } + } + //end npcbot + BattlegroundQueue& bgQueue = sBattlegroundMgr->GetBattlegroundQueue(bgQueueTypeId); GroupQueueInfo* ginfo = bgQueue.AddGroup(_player, nullptr, bgTypeId, bracketEntry, 0, false, isPremade, 0, 0); @@ -254,6 +275,22 @@ void WorldSession::HandleBattlemasterJoinOpcode(WorldPacket& recvData) member->SendDirectMessage(&data); TC_LOG_DEBUG("bg.battleground", "Battleground: player joined queue for bg queue type {} bg type {}: GUID {}, NAME {}", bgQueueTypeId, bgTypeId, member->GetGUID().ToString(), member->GetName()); + + //npcbot: list bots + if (!member->HaveBot()) + continue; + + BotMap const* map = member->GetBotMgr()->GetBotMap(); + for (BotMap::const_iterator itr = map->begin(); itr != map->end(); ++itr) + { + Creature const* bot = itr->second; + if (!bot || !grp->IsMember(bot->GetGUID())) + continue; + + TC_LOG_DEBUG("bg.battleground", "Battleground: NPCBot joined queue for bg queue type {} bg type {}: GUID {}, NAME {} (owner: {})", + bgQueueTypeId, bgTypeId, bot->GetGUID().ToString(), bot->GetName(), member->GetName()); + } + //end npcbot } TC_LOG_DEBUG("bg.battleground", "Battleground: group end"); } @@ -272,11 +309,24 @@ void WorldSession::HandleBattlegroundPlayerPositionsOpcode(WorldPacket& /*recvDa Player* allianceFlagCarrier = nullptr; Player* hordeFlagCarrier = nullptr; + //npcbot + Creature const* afcbot = nullptr; + Creature const* hfcbot = nullptr; + //end npcbot + if (ObjectGuid guid = bg->GetFlagPickerGUID(TEAM_ALLIANCE)) { allianceFlagCarrier = ObjectAccessor::FindPlayer(guid); if (allianceFlagCarrier) ++flagCarrierCount; + //npcbot + else if (guid.IsCreature()) + { + afcbot = BotDataMgr::FindBot(guid.GetEntry()); + if (afcbot) + ++flagCarrierCount; + } + //end npcbot } if (ObjectGuid guid = bg->GetFlagPickerGUID(TEAM_HORDE)) @@ -284,6 +334,14 @@ void WorldSession::HandleBattlegroundPlayerPositionsOpcode(WorldPacket& /*recvDa hordeFlagCarrier = ObjectAccessor::FindPlayer(guid); if (hordeFlagCarrier) ++flagCarrierCount; + //npcbot + else if (guid.IsCreature()) + { + hfcbot = BotDataMgr::FindBot(guid.GetEntry()); + if (hfcbot) + ++flagCarrierCount; + } + //end npcbot } WorldPacket data(MSG_BATTLEGROUND_PLAYER_POSITIONS, 4 + 4 + 16 * flagCarrierCount); @@ -300,6 +358,14 @@ void WorldSession::HandleBattlegroundPlayerPositionsOpcode(WorldPacket& /*recvDa data << float(allianceFlagCarrier->GetPositionX()); data << float(allianceFlagCarrier->GetPositionY()); } + //npcbot + else if (afcbot) + { + data << uint64(afcbot->GetGUID()); + data << float(afcbot->GetPositionX()); + data << float(afcbot->GetPositionY()); + } + //end npcbot if (hordeFlagCarrier) { @@ -307,6 +373,14 @@ void WorldSession::HandleBattlegroundPlayerPositionsOpcode(WorldPacket& /*recvDa data << float(hordeFlagCarrier->GetPositionX()); data << float(hordeFlagCarrier->GetPositionY()); } + //npcbot + else if (hfcbot) + { + data << uint64(hfcbot->GetGUID()); + data << float(hfcbot->GetPositionX()); + data << float(hfcbot->GetPositionY()); + } + //end npcbot SendPacket(&data); } diff --git a/src/server/game/Handlers/GroupHandler.cpp b/src/server/game/Handlers/GroupHandler.cpp index 1cff1a545..6fe78fc79 100644 --- a/src/server/game/Handlers/GroupHandler.cpp +++ b/src/server/game/Handlers/GroupHandler.cpp @@ -35,6 +35,12 @@ #include "World.h" #include "WorldPacket.h" +//npcbot: try query bot name +#include "CreatureData.h" +#include "botdatamgr.h" +#include "botmgr.h" +//end npcbot + class Aura; /* differeces from off: @@ -609,6 +615,14 @@ void WorldSession::HandleGroupChangeSubGroupOpcode(WorldPacket& recvData) else guid = sCharacterCache->GetCharacterGuidByName(name); + //npcbot + if (guid.IsEmpty()) + { + if (Creature const* bot = BotDataMgr::FindBot(name, GetSessionDbcLocale())) + guid = bot->GetGUID(); + } + //end npcbot + if (guid.IsEmpty()) return; @@ -909,6 +923,21 @@ void WorldSession::HandleRequestPartyMemberStatsOpcode(WorldPacket &recvData) ObjectGuid Guid; recvData >> Guid; + //npcbot: try send bot group member info + if (Guid.IsCreature()) + { + uint32 creatureId = Guid.GetEntry(); + CreatureTemplate const* creatureTemplate = sObjectMgr->GetCreatureTemplate(creatureId); + if (creatureTemplate && creatureTemplate->IsNPCBot()) + { + WorldPacket bpdata(SMSG_PARTY_MEMBER_STATS_FULL, 4+2+2+2+1+2*6+8+1+8); + BotMgr::BuildBotPartyMemberStatsPacket(Guid, &bpdata); + SendPacket(&bpdata); + return; + } + } + //end npcbot + Player* player = ObjectAccessor::FindConnectedPlayer(Guid); if (!player || !GetPlayer()->IsInSameRaidWith(player)) { diff --git a/src/server/game/Handlers/ItemHandler.cpp b/src/server/game/Handlers/ItemHandler.cpp index ec546fcbf..ba824a3c4 100644 --- a/src/server/game/Handlers/ItemHandler.cpp +++ b/src/server/game/Handlers/ItemHandler.cpp @@ -30,6 +30,10 @@ #include "World.h" #include "WorldPacket.h" +// npcbot +#include "botmgr.h" +//end npcbot + void WorldSession::HandleSplitItemOpcode(WorldPacket& recvData) { //TC_LOG_DEBUG("network", "WORLD: CMSG_SPLIT_ITEM"); @@ -655,6 +659,15 @@ void WorldSession::SendListInventory(ObjectGuid vendorGuid) { if (ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(item->item)) { + // npcbot + if (_player->HaveBot()) + { + if (!(itemTemplate->AllowableClass & (_player->GetClassMask() | _player->GetBotMgr()->GetAllNpcBotsClassMask())) && + itemTemplate->Bonding == BIND_WHEN_PICKED_UP && !_player->IsGameMaster()) + continue; + } + else + // end npcbot if (!(itemTemplate->AllowableClass & _player->GetClassMask()) && itemTemplate->Bonding == BIND_WHEN_PICKED_UP && !_player->IsGameMaster()) continue; // Only display items in vendor lists for the team the diff --git a/src/server/game/Handlers/QueryHandler.cpp b/src/server/game/Handlers/QueryHandler.cpp index d1febe6ba..b800e451f 100644 --- a/src/server/game/Handlers/QueryHandler.cpp +++ b/src/server/game/Handlers/QueryHandler.cpp @@ -31,8 +31,47 @@ #include "UpdateMask.h" #include "World.h" +//npcbot +#include "CreatureData.h" +#include "botdatamgr.h" +#include "botmgr.h" +//end npcbot + void WorldSession::SendNameQueryOpcode(ObjectGuid guid) { + //npcbot: try query bot info + if (guid.IsCreature()) + { + uint32 creatureId = guid.GetEntry(); + CreatureTemplate const* creatureTemplate = sObjectMgr->GetCreatureTemplate(creatureId); + if (creatureTemplate && creatureTemplate->IsNPCBot()) + { + std::string creatureName = creatureTemplate->Name; + if (CreatureLocale const* creatureInfo = sObjectMgr->GetCreatureLocale(creatureId)) + { + uint32 loc = GetSessionDbLocaleIndex(); + if (creatureInfo->Name.size() > loc && !creatureInfo->Name[loc].empty() && Utf8FitTo(creatureInfo->Name[loc], {})) + creatureName = creatureInfo->Name[loc]; + } + + NpcBotExtras const* extData = ASSERT_NOTNULL(BotDataMgr::SelectNpcBotExtras(creatureId)); + NpcBotAppearanceData const* appData = BotDataMgr::SelectNpcBotAppearance(creatureId); + + WorldPacket bpdata(SMSG_NAME_QUERY_RESPONSE, (8+1+1+1+1+1+10)); + bpdata << guid.WriteAsPacked(); + bpdata << uint8(0); + bpdata << creatureName; + bpdata << uint8(0); + bpdata << uint8(BotMgr::GetBotPlayerRace(extData->bclass, extData->race)); + bpdata << uint8(appData ? appData->gender : uint8(GENDER_MALE)); + bpdata << uint8(BotMgr::GetBotPlayerClass(extData->bclass)); + bpdata << uint8(0); + SendPacket(&bpdata); + return; + } + } + //end npcbot + Player* player = ObjectAccessor::FindConnectedPlayer(guid); CharacterCacheEntry const* nameData = sCharacterCache->GetCharacterCacheByGuid(guid); diff --git a/src/server/game/Handlers/SpellHandler.cpp b/src/server/game/Handlers/SpellHandler.cpp index 06aab3789..6df78a997 100644 --- a/src/server/game/Handlers/SpellHandler.cpp +++ b/src/server/game/Handlers/SpellHandler.cpp @@ -39,6 +39,12 @@ #include "World.h" #include "WorldPacket.h" +//npcbot +#include "bot_ai.h" +#include "botdatamgr.h" +#include "botmgr.h" +//end npcbot + void WorldSession::HandleClientCastFlags(WorldPacket& recvPacket, uint8 castFlags, SpellCastTargets& targets) { // some spell cast packet including more data (for projectiles?) @@ -605,6 +611,109 @@ void WorldSession::HandleMirrorImageDataRequest(WorldPacket& recvData) if (!unit) return; + //npcbot + if (unit->GetTypeId() == TYPEID_UNIT) + { + CreatureOutfitContainer const& outfits = sObjectMgr->GetCreatureOutfitMap(); + CreatureOutfitContainer::const_iterator it = outfits.find(unit->GetEntry()); + if (it != outfits.end()) + { + WorldPacket data(SMSG_MIRRORIMAGE_DATA, 68); + data << uint64(guid); + data << uint32(unit->GetNativeDisplayId()); // displayId + data << uint8(it->second.race); // race + data << uint8(it->second.gender); // gender + data << uint8(unit->GetClass()); // class + data << uint8(it->second.skin); // skin + data << uint8(it->second.face); // face + data << uint8(it->second.hair); // hair + data << uint8(it->second.haircolor); // haircolor + data << uint8(it->second.facialhair); // facialhair + data << uint32(0); // guildId + + // item displays + for (uint8 i = 0; i != MAX_CREATURE_OUTFIT_DISPLAYS; ++i) + data << uint32(it->second.outfit[i]); + + SendPacket(&data); + return; + } + + //npcbot minion without a record in outfits table + //OR + //npcbot's mirror image + Creature const* bot = unit->ToCreature(); + if (!bot->IsNPCBot() && unit->HasAuraType(SPELL_AURA_CLONE_CASTER)) + if (Unit const* creator = unit->GetAuraEffectsByType(SPELL_AURA_CLONE_CASTER).front()->GetCaster()) + if (creator->IsNPCBot()) + bot = creator->ToCreature(); + + if (bot->IsNPCBot()) + { + NpcBotAppearanceData const* appearData = BotDataMgr::SelectNpcBotAppearance(bot->GetEntry()); + + WorldPacket data(SMSG_MIRRORIMAGE_DATA, 68); + data << uint64(guid); + data << uint32(bot->GetDisplayId()); // displayId + data << uint8(bot->GetRace()); // race + data << uint8(appearData ? appearData->gender : (uint8)bot->GetGender()); // gender + data << uint8(bot->GetBotAI()->GetPlayerClass()); // class + data << uint8(appearData ? appearData->skin : 0); // skin + data << uint8(appearData ? appearData->face : 0); // face + data << uint8(appearData ? appearData->hair : 0); // hair + data << uint8(appearData ? appearData->haircolor : 0); // haircolor + data << uint8(appearData ? appearData->features : 0); // facialhair + data << uint32(0); // guildId + + static uint8 const botItemSlots[MAX_CREATURE_OUTFIT_DISPLAYS] = + { + BOT_SLOT_HEAD, + BOT_SLOT_SHOULDERS, + BOT_SLOT_BODY, + BOT_SLOT_CHEST, + BOT_SLOT_WAIST, + BOT_SLOT_LEGS, + BOT_SLOT_FEET, + BOT_SLOT_WRIST, + BOT_SLOT_HANDS, + BOT_SLOT_BACK, + 0//tabard + }; + + // Display items in visible slots + for (uint8 i = 0; i != MAX_CREATURE_OUTFIT_DISPLAYS; ++i) + { + uint8 slot = botItemSlots[i]; + //Items not displayed on bot: tabard, head, back + if (slot == 0 || + (slot == BOT_SLOT_HEAD && BotMgr::ShowEquippedHelm() == false) || + (slot == BOT_SLOT_BACK && BotMgr::ShowEquippedCloak() == false)) + { + data << uint32(0); + continue; + } + + uint32 display_id = bot->GetBotAI()->GetEquipDisplayId(slot); + if (display_id) + data << uint32(display_id); + else + { + //don't allow to go naked + if (slot == BOT_SLOT_CHEST) + data << uint32(CHEST_HALISCAN); + else if (slot == BOT_SLOT_LEGS) + data << uint32(LEGS_HALISCAN); + else + data << uint32(0); + } + } + + SendPacket(&data); + return; + } + } + //end npcbot + if (!unit->HasAuraType(SPELL_AURA_CLONE_CASTER)) return; diff --git a/src/server/game/Instances/InstanceScript.cpp b/src/server/game/Instances/InstanceScript.cpp index 1435960d6..7503910f3 100644 --- a/src/server/game/Instances/InstanceScript.cpp +++ b/src/server/game/Instances/InstanceScript.cpp @@ -39,6 +39,10 @@ #include #include +//npcbot +#include "botmgr.h" +//end npcbot + BossBoundaryData::~BossBoundaryData() { for (const_iterator it = begin(); it != end(); ++it) @@ -633,6 +637,15 @@ void InstanceScript::DoRemoveAurasDueToSpellOnPlayer(Player* player, uint32 spel player->RemoveAurasDueToSpell(spell); + //npcbot: include bots + if (player->HaveBot()) + { + for (auto const& bitr : *player->GetBotMgr()->GetBotMap()) + if (bitr.second && bitr.second->IsInWorld()) + DoRemoveAurasDueToSpellOnNPCBot(bitr.second, spell); + } + //end npcbot + if (!includePets) return; @@ -668,6 +681,15 @@ void InstanceScript::DoCastSpellOnPlayer(Player* player, uint32 spell, bool incl player->CastSpell(player, spell, true); + //npcbot: include bots + if (player->HaveBot()) + { + for (auto const& bitr : *player->GetBotMgr()->GetBotMap()) + if (bitr.second && bitr.second->IsInWorld()) + DoCastSpellOnNPCBot(bitr.second, spell); + } + //end npcbot + if (!includePets) return; @@ -689,6 +711,24 @@ void InstanceScript::DoCastSpellOnPlayer(Player* player, uint32 spell, bool incl } } +//npcbot: hooks +void InstanceScript::DoRemoveAurasDueToSpellOnNPCBot(Creature* bot, uint32 spell) +{ + ASSERT(bot && bot->IsNPCBot() && bot->IsInWorld() && !bot->IsFreeBot()); + bot->RemoveAurasDueToSpell(spell); + if (Unit* botpet = bot->GetBotsPet()) + botpet->RemoveAurasDueToSpell(spell); +} + +void InstanceScript::DoCastSpellOnNPCBot(Creature* bot, uint32 spell) +{ + ASSERT(bot && bot->IsNPCBot() && bot->IsInWorld() && !bot->IsFreeBot()); + bot->CastSpell(bot, spell, true); + if (Unit* botpet = bot->GetBotsPet()) + botpet->CastSpell(botpet, spell, true); +} +//end npcbot + bool InstanceScript::ServerAllowsTwoSideGroups() { return sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_GROUP); diff --git a/src/server/game/Instances/InstanceScript.h b/src/server/game/Instances/InstanceScript.h index 0423dc2ec..99a9aecdd 100644 --- a/src/server/game/Instances/InstanceScript.h +++ b/src/server/game/Instances/InstanceScript.h @@ -200,6 +200,13 @@ class TC_GAME_API InstanceScript : public ZoneScript // Called when a player successfully leaves the instance. virtual void OnPlayerLeave(Player* /*player*/) { } + //npcbot: map hooks + virtual void OnNPCBotEnter(Creature* /*bot*/) { } + virtual void OnNPCBotLeave(Creature* /*bot*/) { } + void DoRemoveAurasDueToSpellOnNPCBot(Creature* bot, uint32 spell); + void DoCastSpellOnNPCBot(Creature* bot, uint32 spell); + //end npcbot + // Handle open / close objects // * use HandleGameObject(0, boolen, GO); in OnObjectCreate in instance scripts // * use HandleGameObject(GUID, boolen, nullptr); in any other script diff --git a/src/server/game/Maps/Map.cpp b/src/server/game/Maps/Map.cpp index 765852581..faec5b914 100644 --- a/src/server/game/Maps/Map.cpp +++ b/src/server/game/Maps/Map.cpp @@ -53,6 +53,11 @@ #include #include +//npcbot +#include "botdatamgr.h" +#include "botmgr.h" +//end npcbot + u_map_magic MapMagic = { {'M','A','P','S'} }; uint32 MapVersionMagic = 10; u_map_magic MapAreaMagic = { {'A','R','E','A'} }; @@ -3665,7 +3670,26 @@ uint32 Map::GetPlayersCountExceptGMs() const uint32 count = 0; for (MapRefManager::const_iterator itr = m_mapRefManager.begin(); itr != m_mapRefManager.end(); ++itr) if (!itr->GetSource()->IsGameMaster()) + //npcbot - count npcbots as group members (event if not in group) + { + if (itr->GetSource()->HaveBot() && BotMgr::LimitBots(this)) + { + ++count; + BotMap const* botmap = itr->GetSource()->GetBotMgr()->GetBotMap(); + for (BotMap::const_iterator itr = botmap->begin(); itr != botmap->end(); ++itr) + { + Creature* cre = itr->second; + if (!cre || !cre->IsInWorld() || cre->FindMap() != this || cre->IsTempBot()) + continue; + ++count; + } + continue; + } + //end npcbot ++count; + //npcbot + } + //end npcbot return count; } @@ -3763,6 +3787,10 @@ void Map::AddToActive(WorldObject* obj) GridCoord p = Trinity::ComputeGridCoord(respawnLocation->GetPositionX(), respawnLocation->GetPositionY()); if (getNGrid(p.x_coord, p.y_coord)) getNGrid(p.x_coord, p.y_coord)->incUnloadActiveLock(); + //npcbot + else if (obj->IsNPCBot()) + EnsureGridLoadedForActiveObject(Cell(Trinity::ComputeCellCoord(obj->GetPositionX(), obj->GetPositionY())), obj); + //end npcbot else { GridCoord p2 = Trinity::ComputeGridCoord(obj->GetPositionX(), obj->GetPositionY()); @@ -3783,6 +3811,11 @@ void Map::RemoveFromActive(WorldObject* obj) if (Creature* creature = obj->ToCreature(); !creature->IsPet() && creature->GetSpawnId()) { respawnLocation.emplace(); + //npcbot: prevent crash from accessing deleted creatureData + if (creature->IsNPCBot()) + creature->GetHomePosition().GetPosition(respawnLocation->m_positionX, respawnLocation->m_positionY, respawnLocation->m_positionZ); + else + //end npcbot creature->GetRespawnPosition(respawnLocation->m_positionX, respawnLocation->m_positionY, respawnLocation->m_positionZ); } break; @@ -3802,6 +3835,10 @@ void Map::RemoveFromActive(WorldObject* obj) GridCoord p = Trinity::ComputeGridCoord(respawnLocation->GetPositionX(), respawnLocation->GetPositionY()); if (getNGrid(p.x_coord, p.y_coord)) getNGrid(p.x_coord, p.y_coord)->decUnloadActiveLock(); + //npcbot + else if (obj->IsNPCBot()) + EnsureGridLoaded(Cell(Trinity::ComputeCellCoord(obj->GetPositionX(), obj->GetPositionY()))); + //end npcbot else { GridCoord p2 = Trinity::ComputeGridCoord(obj->GetPositionX(), obj->GetPositionY()); @@ -4491,6 +4528,11 @@ void Map::SaveRespawnTime(SpawnObjectType type, ObjectGuid::LowType spawnId, uin void Map::SaveRespawnInfoDB(RespawnInfo const& info, CharacterDatabaseTransaction dbTrans) { + //npcbot: DO NOT save npcbots respawn time + if (info.type == SPAWN_TYPE_CREATURE && BotDataMgr::SelectNpcBotData(info.entry)) + return; + //end npcbot + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_RESPAWN); stmt->setUInt16(0, info.type); stmt->setUInt32(1, info.spawnId); diff --git a/src/server/game/Maps/MapManager.cpp b/src/server/game/Maps/MapManager.cpp index b2e42a91b..5159cfeed 100644 --- a/src/server/game/Maps/MapManager.cpp +++ b/src/server/game/Maps/MapManager.cpp @@ -36,6 +36,11 @@ #include "ScriptMgr.h" #include +//npcbot +#include "botdatamgr.h" +#include "botmgr.h" +//end npcbot + MapManager::MapManager() : _nextInstanceId(0), _scheduledScripts(0) { @@ -53,6 +58,10 @@ void MapManager::Initialize() // Start mtmaps if needed. if (num_threads > 0) m_updater.activate(num_threads); + + //npcbot: load bots + BotMgr::Initialize(); + //end npcbot } void MapManager::InitializeVisibilityDistanceInfo() @@ -214,6 +223,10 @@ Map::EnterState MapManager::PlayerCannotEnter(uint32 mapid, Player* player, bool void MapManager::Update(uint32 diff) { + //npcbot + BotDataMgr::Update(diff); + //end npcbot + i_timer.Update(diff); if (!i_timer.Passed()) return; @@ -229,6 +242,10 @@ void MapManager::Update(uint32 diff) if (m_updater.activated()) m_updater.wait(); + //npcbot + BotMgr::HandleDelayedTeleports(); + //end npcbot + for (iter = i_maps.begin(); iter != i_maps.end(); ++iter) iter->second->DelayedUpdate(uint32(i_timer.GetCurrent())); diff --git a/src/server/game/Movement/MotionMaster.cpp b/src/server/game/Movement/MotionMaster.cpp index 96f810f37..365cc0818 100644 --- a/src/server/game/Movement/MotionMaster.cpp +++ b/src/server/game/Movement/MotionMaster.cpp @@ -819,6 +819,26 @@ void MotionMaster::MoveJump(float x, float y, float z, float o, float speedXY, f if (speedXY < 0.01f) return; + //npcbot: blademaser only (disabled) + /* + if (_owner->GetTypeId() == TYPEID_UNIT && _owner->ToCreature()->IsNPCBot()) + { + Movement::MoveSplineInit init(_owner); + init.MoveTo(x, y, z, false); + init.SetParabolic(speedZ, 0); + init.SetFacing(o); + init.SetOrientationFixed(true); + init.SetVelocity(speedXY); + + GenericMovementGenerator* movement = new GenericMovementGenerator(std::move(init), EFFECT_MOTION_TYPE, EVENT_JUMP); + movement->Priority = MOTION_PRIORITY_HIGHEST; + movement->BaseUnitState = UNIT_STATE_JUMPING; + Add(movement); + return; + } + */ + //end npcbot + float moveTimeHalf = speedZ / Movement::gravity; float max_height = -Movement::computeFallElevation(moveTimeHalf, false, -speedZ); diff --git a/src/server/game/Movement/MovementGenerators/FollowMovementGenerator.cpp b/src/server/game/Movement/MovementGenerators/FollowMovementGenerator.cpp index ee789dc97..46ec45950 100644 --- a/src/server/game/Movement/MovementGenerators/FollowMovementGenerator.cpp +++ b/src/server/game/Movement/MovementGenerators/FollowMovementGenerator.cpp @@ -198,4 +198,16 @@ void FollowMovementGenerator::UpdatePetSpeed(Unit* owner) oPet->UpdateSpeed(MOVE_SWIM); } } + + //npcbot + if (owner->IsNPCBotPet()) + { + if (GetTarget() && (GetTarget()->GetGUID() == owner->GetOwnerGUID() || GetTarget()->GetGUID() == owner->GetCreatorGUID())) + { + owner->UpdateSpeed(MOVE_RUN); + owner->UpdateSpeed(MOVE_WALK); + owner->UpdateSpeed(MOVE_SWIM); + } + } + //end npcbot } diff --git a/src/server/game/Movement/Spline/MoveSplineInit.cpp b/src/server/game/Movement/Spline/MoveSplineInit.cpp index 931b6ebd1..adf8b519a 100644 --- a/src/server/game/Movement/Spline/MoveSplineInit.cpp +++ b/src/server/game/Movement/Spline/MoveSplineInit.cpp @@ -117,6 +117,11 @@ namespace Movement if (Creature* creature = unit->ToCreature()) if (creature->HasSearchedAssistance()) args.velocity *= 0.66f; + + //npcbot: do not emit an error if unit cannot move at all + if (!unit->CanFreeMove() && !(args.velocity > 0.01f)) + return 0; + //end npcbot } // limit the speed in the same way the client does diff --git a/src/server/game/OutdoorPvP/OutdoorPvP.cpp b/src/server/game/OutdoorPvP/OutdoorPvP.cpp index 204b98869..e23f5c981 100644 --- a/src/server/game/OutdoorPvP/OutdoorPvP.cpp +++ b/src/server/game/OutdoorPvP/OutdoorPvP.cpp @@ -315,6 +315,23 @@ bool OPvPCapturePoint::Update(uint32 diff) if (!fact_diff) return false; + //npcbots - count bots as players but 2 times less affect and only if there is a players difference + uint32 botsCount[2]; + + for (uint8 team = 0; team != 2; ++team) + { + botsCount[team] = 0; + + for (GuidSet::iterator itr = m_activePlayers[team].begin(); itr != m_activePlayers[team].end(); ++itr) + { + if (Player* player = ObjectAccessor::FindPlayer(*itr)) + botsCount[team] += player->GetNpcBotsCount(); + } + } + + fact_diff += 0.5f * ((float)botsCount[0] - (float)botsCount[1]) * diff / OUTDOORPVP_OBJECTIVE_UPDATE_INTERVAL; + //end npcbot + uint32 Challenger = 0; float maxDiff = m_maxSpeed * diff; diff --git a/src/server/game/Scripting/ScriptMgr.cpp b/src/server/game/Scripting/ScriptMgr.cpp index d51b12bee..0d802cb0a 100644 --- a/src/server/game/Scripting/ScriptMgr.cpp +++ b/src/server/game/Scripting/ScriptMgr.cpp @@ -43,6 +43,10 @@ #include "WorldPacket.h" #include "WorldSession.h" +//npcbot +#include "botmgr.h" +//end npcbot + // Trait which indicates whether this script type // must be assigned in the database. template @@ -1048,6 +1052,10 @@ void ScriptMgr::Initialize() // LFGScripts lfg::AddSC_LFGScripts(); + //npcbot: load bot scripts here + AddNpcBotScripts(); + //end npcbot + // Load all static linked scripts through the script loader function. ASSERT(_script_loader_callback, "Script loader callback wasn't registered!"); diff --git a/src/server/game/Scripting/ScriptMgr.h b/src/server/game/Scripting/ScriptMgr.h index e329fbaee..b9414630b 100644 --- a/src/server/game/Scripting/ScriptMgr.h +++ b/src/server/game/Scripting/ScriptMgr.h @@ -91,6 +91,8 @@ enum XPColorChar : uint8; #define VISIBLE_RANGE 166.0f //MAX visible range (size of grid) +#define MOD_PRESENT_NPCBOTS 1 + /* @todo Add more script type classes. diff --git a/src/server/game/Server/WorldSession.cpp b/src/server/game/Server/WorldSession.cpp index 15cb8e383..b9f0da6c4 100644 --- a/src/server/game/Server/WorldSession.cpp +++ b/src/server/game/Server/WorldSession.cpp @@ -58,6 +58,10 @@ #include #include +//npcbot +#include "botmgr.h" +//end npcbot + namespace { std::string const DefaultPlayerName = ""; @@ -493,6 +497,10 @@ void WorldSession::LogoutPlayer(bool save) m_playerLogout = true; m_playerSave = save; + //npcbot - free all bots and remove from botmap + _player->RemoveAllBots(); + //end npcbots + if (_player) { if (ObjectGuid lguid = _player->GetLootGUID()) @@ -1724,6 +1732,16 @@ uint32 WorldSession::DosProtection::GetMaxPacketCounterAllowed(uint16 opcode) co maxPacketCounterAllowed = PLAYER_SLOTS_COUNT; break; } + //npcbot: prevent kicks when too many bots spawned in one spot + case CMSG_GET_MIRRORIMAGE_DATA: + { + if (BotMgr::GetBotInfoPacketsLimit() > -1) + maxPacketCounterAllowed = BotMgr::GetBotInfoPacketsLimit(); + else + maxPacketCounterAllowed = 100; + break; + } + //end npcbot default: { maxPacketCounterAllowed = 100; diff --git a/src/server/game/Spells/Auras/SpellAuraEffects.cpp b/src/server/game/Spells/Auras/SpellAuraEffects.cpp index 1346f8682..4689ef3c5 100644 --- a/src/server/game/Spells/Auras/SpellAuraEffects.cpp +++ b/src/server/game/Spells/Auras/SpellAuraEffects.cpp @@ -1847,6 +1847,10 @@ void AuraEffect::HandleAuraModShapeshift(AuraApplication const* aurApp, uint8 mo case FORM_DEFENSIVESTANCE: case FORM_BERSERKERSTANCE: { + //npcbot: skip this, handled inside class ai + if (target->IsNPCBot()) + break; + //end npcbot uint32 Rage_val = 0; // Defensive Tactics if (form == FORM_DEFENSIVESTANCE) @@ -1882,6 +1886,10 @@ void AuraEffect::HandleAuraModShapeshift(AuraApplication const* aurApp, uint8 mo if (target->GetTypeId() == TYPEID_PLAYER) target->ToPlayer()->InitDataForForm(); + //npcbot: skip bots (handled inside AI) + else if (target->GetTypeId() == TYPEID_UNIT && target->ToCreature()->IsNPCBotOrPet()) + {} + //end npcbot else target->UpdateDisplayPower(); @@ -1896,6 +1904,11 @@ void AuraEffect::HandleAuraModShapeshift(AuraApplication const* aurApp, uint8 mo // and also HandleAuraModDisarm is not triggered if (!target->CanUseAttackType(BASE_ATTACK)) { + //npcbot: skip bots (handled inside AI) + if (target->GetTypeId() == TYPEID_UNIT && target->ToCreature()->IsNPCBotOrPet()) + {} + else + //end npcbot if (Item* pItem = target->ToPlayer()->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND)) target->ToPlayer()->_ApplyWeaponDamage(EQUIPMENT_SLOT_MAINHAND, pItem->GetTemplate(), apply); } @@ -2775,6 +2788,17 @@ void AuraEffect::HandleAuraModTotalThreat(AuraApplication const* aurApp, uint8 m Unit* target = aurApp->GetTarget(); + //npcbot: handle for bots + if (target->IsAlive() && target->GetTypeId() == TYPEID_UNIT && + target->ToCreature()->IsNPCBotOrPet()) + { + Unit* caster = GetCaster(); + if (caster && caster->IsAlive()) + caster->GetThreatManager().UpdateMyTempModifiers(); + return; + } + //end npcbot + if (!target->IsAlive() || target->GetTypeId() != TYPEID_PLAYER) return; @@ -3154,6 +3178,17 @@ void AuraEffect::HandleAuraModEffectImmunity(AuraApplication const* aurApp, uint else sOutdoorPvPMgr->HandleDropFlag(player, GetSpellInfo()->Id); } + + //npcbot + if (Creature* bot = target->ToCreature()) + { + if (!apply && bot->IsNPCBot() && (GetSpellInfo()->AuraInterruptFlags & AURA_INTERRUPT_FLAG_IMMUNE_OR_LOST_SELECTION)) + { + if (Battleground* botbg = bot->GetBotBG()) + botbg->EventBotDroppedFlag(bot); + } + } + //end npcbot } void AuraEffect::HandleAuraModStateImmunity(AuraApplication const* aurApp, uint8 mode, bool apply) const @@ -5039,6 +5074,10 @@ void AuraEffect::HandlePeriodicTriggerSpellAuraTick(Unit* target, Unit* caster) if (SpellInfo const* triggeredSpellInfo = sSpellMgr->GetSpellInfo(triggerSpellId)) { + //npcbot: override spellInfo + triggeredSpellInfo = triggeredSpellInfo->TryGetSpellInfoOverride(caster); + //end npcbot + if (Unit* triggerCaster = triggeredSpellInfo->NeedsToBeTriggeredByCaster(m_spellInfo) ? caster : target) { triggerCaster->CastSpell(target, triggerSpellId, this); @@ -5060,6 +5099,10 @@ void AuraEffect::HandlePeriodicTriggerSpellWithValueAuraTick(Unit* target, Unit* if (SpellInfo const* triggeredSpellInfo = sSpellMgr->GetSpellInfo(triggerSpellId)) { + //npcbot: override spellInfo + triggeredSpellInfo = triggeredSpellInfo->TryGetSpellInfoOverride(caster); + //end npcbot + if (Unit* triggerCaster = triggeredSpellInfo->NeedsToBeTriggeredByCaster(m_spellInfo) ? caster : target) { CastSpellExtraArgs args(this); @@ -5101,6 +5144,14 @@ void AuraEffect::HandlePeriodicDamageAurasTick(Unit* target, Unit* caster) const if (GetAuraType() == SPELL_AURA_PERIODIC_DAMAGE) { + //npcbot: Black Arrow damage on targets below 20% + if (GetSpellInfo()->SpellFamilyName == SPELLFAMILY_WARLOCK && (GetSpellInfo()->SpellFamilyFlags[1] & 0x4) && + target->HasAuraState(AURA_STATE_HEALTHLESS_20_PERCENT)) + { + damage *= 5; + } + //end npcbot + // leave only target depending bonuses, rest is handled in calculate amount if (GetBase()->GetType() == DYNOBJ_AURA_TYPE) damage = caster->SpellDamageBonusDone(target, GetSpellInfo(), damage, DOT, GetSpellEffectInfo(), { }, GetBase()->GetStackAmount()); @@ -5627,6 +5678,50 @@ void AuraEffect::HandleProcTriggerSpellAuraProc(AuraApplication* aurApp, ProcEve if (SpellInfo const* triggeredSpellInfo = sSpellMgr->GetSpellInfo(triggerSpellId)) { TC_LOG_DEBUG("spells.aura.effect", "AuraEffect::HandleProcTriggerSpellAuraProc: Triggering spell {} from aura {} proc", triggeredSpellInfo->Id, GetId()); + + //npcbot: override spellInfo + triggeredSpellInfo = triggeredSpellInfo->TryGetSpellInfoOverride(aurApp->GetBase()->GetCaster()); + //end npcbot + + //npcbot + Aura const* triggeredByAura = aurApp->GetBase(); + int32 basepoints0 = 0; + switch (triggerSpellId) + { + // Quest - Self Healing from resurrect (invisible in log) + case 25155: + { + switch (GetId()) + { + //Vampiric Aura + case 20810: + { + DamageInfo const* dinfo = eventInfo.GetDamageInfo(); + uint32 damage = dinfo->GetDamage(); + if (!damage) + return; + + // 100% / 25% + if (triggerTarget->GetGUID() == triggeredByAura->GetCasterGUID()) + basepoints0 = int32(damage); + else + basepoints0 = int32(damage / 4); + + CastSpellExtraArgs args(true); + args.AddSpellBP0(basepoints0); + triggerCaster->CastSpell(triggerTarget, triggerSpellId, args); + return; + } + default: + break; + } + break; + } + default: + break; + } + //end npcbot + triggerCaster->CastSpell(triggerTarget, triggeredSpellInfo->Id, this); } else @@ -5647,6 +5742,10 @@ void AuraEffect::HandleProcTriggerSpellWithValueAuraProc(AuraApplication* aurApp if (SpellInfo const* triggeredSpellInfo = sSpellMgr->GetSpellInfo(triggerSpellId)) { + //npcbot: override spellInfo + triggeredSpellInfo = triggeredSpellInfo->TryGetSpellInfoOverride(aurApp->GetBase()->GetCaster()); + //end npcbot + CastSpellExtraArgs args(this); args.AddSpellMod(SPELLVALUE_BASE_POINT0, GetAmount()); triggerCaster->CastSpell(triggerTarget, triggerSpellId, args); diff --git a/src/server/game/Spells/Auras/SpellAuras.cpp b/src/server/game/Spells/Auras/SpellAuras.cpp index 7458e62ec..f6d7f274b 100644 --- a/src/server/game/Spells/Auras/SpellAuras.cpp +++ b/src/server/game/Spells/Auras/SpellAuras.cpp @@ -38,6 +38,10 @@ #include "World.h" #include "WorldPacket.h" +//npcbot +#include "botspell.h" +//end npcbot + AuraCreateInfo::AuraCreateInfo(SpellInfo const* spellInfo, uint8 auraEffMask, WorldObject* owner) : _spellInfo(spellInfo), _auraEffectMask(auraEffMask), _owner(owner) { @@ -568,6 +572,10 @@ void Aura::_ApplyForTarget(Unit* target, Unit* caster, AuraApplication* auraApp) caster->GetSpellHistory()->StartCooldown(m_spellInfo, castItem ? castItem->GetEntry() : 0, nullptr, true); } } + //npcbot: infinity cd for bots + if (caster && m_spellInfo->IsCooldownStartedOnEvent() && caster->IsNPCBot()) + caster->ToCreature()->AddBotSpellCooldown(m_spellInfo->Id, std::numeric_limits::max()); + //end npcbot } void Aura::_UnapplyForTarget(Unit* target, Unit* caster, AuraApplication* auraApp) @@ -596,6 +604,11 @@ void Aura::_UnapplyForTarget(Unit* target, Unit* caster, AuraApplication* auraAp if (caster && GetSpellInfo()->IsCooldownStartedOnEvent()) // note: item based cooldowns and cooldown spell mods with charges ignored (unknown existed cases) caster->GetSpellHistory()->SendCooldownEvent(GetSpellInfo()); + + //npcbot: release cd state for bots + if (caster && m_spellInfo->IsCooldownStartedOnEvent() && caster->IsNPCBot()) + caster->ToCreature()->ReleaseBotSpellCooldown(m_spellInfo->Id); + //end npcbot } // removes aura from all targets @@ -947,6 +960,14 @@ uint8 Aura::CalcMaxCharges(Unit* caster) const if (SpellProcEntry const* procEntry = sSpellMgr->GetSpellProcEntry(GetId())) maxProcCharges = procEntry->Charges; + //npcbot: override spell proc + if (caster && caster->IsNPCBot()) + { + if (SpellProcEntry const* procOverride = GetBotSpellProceEntryOverride(GetId())) + maxProcCharges = procOverride->Charges; + } + //end npcbot + if (caster) if (Player* modOwner = caster->GetSpellModOwner()) modOwner->ApplySpellMod(GetId(), SPELLMOD_CHARGES, maxProcCharges); @@ -1659,6 +1680,20 @@ void Aura::HandleAuraSpecificMods(AuraApplication const* aurApp, Unit* caster, b case 47788: // Guardian Spirit if (removeMode != AURA_REMOVE_BY_EXPIRE) break; + + //npcbot: handle Glyph of Guardian Spirit proc for bots + if (Creature* bot = caster->ToCreature()) + { + if (bot->IsNPCBot() && bot->HasSpellCooldown(47788)) + { + bot->AddBotSpellCooldown(47788, 60000); + bot->GetSpellHistory()->ResetCooldown(GetSpellInfo()->Id, true); + bot->GetSpellHistory()->AddCooldown(GetSpellInfo()->Id, 0, std::chrono::seconds(60)); + break; + } + } + //end npcbot + if (caster->GetTypeId() != TYPEID_PLAYER) break; @@ -1983,6 +2018,16 @@ void Aura::PrepareProcToTrigger(AuraApplication* aurApp, ProcEventInfo& eventInf } SpellProcEntry const* procEntry = sSpellMgr->GetSpellProcEntry(GetId()); + + //npcbot: override spell proc + Unit const* caster = aurApp && aurApp->GetBase()->GetCasterGUID().IsCreature() ? aurApp->GetBase()->GetCaster() : nullptr; + if (caster && caster->IsNPCBot()) + { + if (SpellProcEntry const* procOverride = GetBotSpellProceEntryOverride(GetId())) + procEntry = procOverride; + } + //end npcbot + ASSERT(procEntry); // cooldowns should be added to the whole aura (see 51698 area aura) @@ -1992,6 +2037,16 @@ void Aura::PrepareProcToTrigger(AuraApplication* aurApp, ProcEventInfo& eventInf uint8 Aura::GetProcEffectMask(AuraApplication* aurApp, ProcEventInfo& eventInfo, TimePoint now) const { SpellProcEntry const* procEntry = sSpellMgr->GetSpellProcEntry(GetId()); + + //npcbot: override spell proc + Unit const* caster = aurApp && aurApp->GetBase()->GetCasterGUID().IsCreature() ? aurApp->GetBase()->GetCaster() : nullptr; + if (caster && caster->IsNPCBot()) + { + if (SpellProcEntry const* procOverride = GetBotSpellProceEntryOverride(GetId())) + procEntry = procOverride; + } + //end npcbot + // only auras with spell proc entry can trigger proc if (!procEntry) return 0; diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index e4a923f51..f356aa513 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -65,6 +65,10 @@ #include "WorldPacket.h" #include "WorldSession.h" +//npcbot +#include "botmgr.h" +//end npcbot + extern SpellEffectHandlerFn SpellEffectHandlers[TOTAL_SPELL_EFFECTS]; SpellDestination::SpellDestination() @@ -506,7 +510,12 @@ protected: }; Spell::Spell(WorldObject* caster, SpellInfo const* info, TriggerCastFlags triggerFlags, ObjectGuid originalCasterGUID) : +//npcbot: override spellInfo +/* m_spellInfo(sSpellMgr->GetSpellForDifficultyFromSpell(info, caster)), +*/ +m_spellInfo((caster->IsNPCBot() ? info : sSpellMgr->GetSpellForDifficultyFromSpell(info, caster))->TryGetSpellInfoOverride(caster)), +//end npcbot m_caster((info->HasAttribute(SPELL_ATTR6_CAST_BY_CHARMER) && caster->GetCharmerOrOwner()) ? caster->GetCharmerOrOwner() : caster) , m_spellValue(new SpellValue(m_spellInfo)), _spellEvent(nullptr) { @@ -539,6 +548,15 @@ m_caster((info->HasAttribute(SPELL_ATTR6_CAST_BY_CHARMER) && caster->GetCharmerO m_spellSchoolMask = SpellSchoolMask(1 << pItem->GetTemplate()->Damage[0].DamageType); } + //npcbot: ranged weapon dmg school + if (m_attackType == RANGED_ATTACK && m_caster->IsNPCBot() && + ((1<<(m_caster->ToCreature()->GetBotClass()-1)) & CLASSMASK_WAND_USERS)) + { + if (Item const* pItem = m_caster->ToCreature()->GetBotEquips(2)) + m_spellSchoolMask = SpellSchoolMask(1 << pItem->GetTemplate()->Damage[0].DamageType); + } + //end npcbot + if (originalCasterGUID) m_originalCasterGUID = originalCasterGUID; else @@ -1236,6 +1254,12 @@ void Spell::SelectImplicitConeTargets(SpellEffectInfo const& spellEffectInfo, Sp { if (Unit* unitCaster = m_caster->ToUnit()) maxTargets += unitCaster->GetTotalAuraModifierByAffectMask(SPELL_AURA_MOD_MAX_AFFECTED_TARGETS, m_spellInfo); + + //npcbot - apply bot spell max targets mods + if (m_caster->IsNPCBot()) + m_caster->ToCreature()->ApplyCreatureSpellMaxTargetsMods(m_spellInfo, maxTargets); + //end npcbot + Trinity::Containers::RandomResize(targets, maxTargets); } @@ -1542,6 +1566,10 @@ void Spell::SelectImplicitCasterObjectTargets(SpellEffectInfo const& spellEffect case TARGET_UNIT_PET: if (Unit* unitCaster = m_caster->ToUnit()) target = unitCaster->GetGuardianPet(); + //npcbot: allow bot pet as target + if (!target && m_caster->IsNPCBot()) + target = m_caster->ToCreature()->GetBotsPet(); + //end npcbot break; case TARGET_UNIT_SUMMONER: if (Unit* unitCaster = m_caster->ToUnit()) @@ -1609,6 +1637,11 @@ void Spell::SelectImplicitChainTargets(SpellEffectInfo const& spellEffectInfo, S if (Player* modOwner = m_caster->GetSpellModOwner()) modOwner->ApplySpellMod(m_spellInfo->Id, SPELLMOD_JUMP_TARGETS, maxTargets, this); + //npcbot - apply bot spell max targets mods + if (m_caster->IsNPCBot()) + m_caster->ToCreature()->ApplyCreatureSpellMaxTargetsMods(m_spellInfo, maxTargets); + //end npcbot + if (maxTargets > 1) { // mark damage multipliers as used @@ -1673,7 +1706,13 @@ void Spell::SelectImplicitTrajTargets(SpellEffectInfo const& spellEffectInfo, Sp // limit max range to 300 yards, sometimes triggered spells can have 50000yds float bestDist = m_spellInfo->GetMaxRange(false); if (SpellInfo const* triggerSpellInfo = sSpellMgr->GetSpellInfo(spellEffectInfo.TriggerSpell)) + { + //npcbot: override spellInfo + triggerSpellInfo = triggerSpellInfo->TryGetSpellInfoOverride(GetCaster()); + //end npcbot + bestDist = std::min(std::max(bestDist, triggerSpellInfo->GetMaxRange(false)), std::min(dist2d, 300.0f)); + } // GameObjects don't cast traj Unit* unitCaster = ASSERT_NOTNULL(m_caster->ToUnit()); @@ -1851,7 +1890,14 @@ uint32 Spell::GetSearcherTypeMask(SpellTargetObjectTypes objType, ConditionConta } if (m_spellInfo->HasAttribute(SPELL_ATTR3_ONLY_TARGET_PLAYERS)) + { + //npcbot: do not exclude creatures, see WorldObjectSpellNearbyTargetCheck, WorldObjectSpellAreaTargetCheck + if (retMask & GRID_MAP_TYPE_MASK_CREATURE) + retMask &= GRID_MAP_TYPE_MASK_CORPSE | GRID_MAP_TYPE_MASK_PLAYER | GRID_MAP_TYPE_MASK_CREATURE; + else + //end npcbot retMask &= GRID_MAP_TYPE_MASK_CORPSE | GRID_MAP_TYPE_MASK_PLAYER; + } if (m_spellInfo->HasAttribute(SPELL_ATTR3_ONLY_TARGET_GHOSTS)) retMask &= GRID_MAP_TYPE_MASK_PLAYER; @@ -2387,6 +2433,10 @@ void Spell::TargetInfo::PreprocessTarget(Spell* spell) // but respect current pvp rules (buffing/healing npcs flagged for pvp only flags you if they are in combat) if (unit->IsPvP() && (unit->IsInCombat() || unit->IsCharmedOwnedByPlayerOrPlayer()) && spell->m_caster->GetTypeId() == TYPEID_PLAYER) _enablePVP = true; // Decide on PvP flagging now, but act on it later. + //npcbot + else if (unit->IsPvP() && (unit->IsInCombat() || unit->IsNPCBotOrPet()) && spell->m_caster->GetTypeId() == TYPEID_PLAYER) + _enablePVP = true; // Decide on PvP flagging now, but act on it later. + //end npcbot SpellMissInfo missInfo = spell->PreprocessSpellHit(_spellHitTarget, ScaleAura, *this); if (missInfo != SPELL_MISS_NONE) @@ -2617,6 +2667,13 @@ void Spell::TargetInfo::DoDamageAndTriggers(Spell* spell) if (!spell->m_spellInfo->HasAttribute(SPELL_ATTR0_STOP_ATTACK_TARGET) && !spell->m_spellInfo->HasAttribute(SPELL_ATTR4_CANT_TRIGGER_ITEM_SPELLS)) caster->ToPlayer()->CastItemCombatSpell(*spellDamageInfo); } + + //npcbot + if (caster->IsNPCBot() && (procSpellType & (PROC_SPELL_TYPE_DAMAGE | PROC_SPELL_TYPE_NO_DMG_HEAL)) && + !(spell->m_spellInfo->Attributes & SPELL_ATTR0_STOP_ATTACK_TARGET) && !spell->m_spellInfo->HasAttribute(SPELL_ATTR4_CANT_TRIGGER_ITEM_SPELLS) && + (spell->m_spellInfo->DmgClass == SPELL_DAMAGE_CLASS_MELEE || spell->m_spellInfo->DmgClass == SPELL_DAMAGE_CLASS_RANGED)) + caster->ToCreature()->CastCreatureItemCombatSpell(*spellDamageInfo); + //end npcbot } // set hitmask for finish procs @@ -2653,6 +2710,15 @@ void Spell::TargetInfo::DoDamageAndTriggers(Spell* spell) else if (spell->m_caster->GetTypeId() == TYPEID_GAMEOBJECT && spell->m_caster->ToGameObject()->AI()) spell->m_caster->ToGameObject()->AI()->SpellHitTarget(_spellHitTarget, spell->m_spellInfo); + //npcbot: vehicle spell hits + if (spell->m_caster->GetTypeId() == TYPEID_UNIT && spell->m_caster->ToCreature()->IsVehicle() && spell->m_caster->ToCreature()->GetCharmerGUID().IsCreature()) + { + Unit const* bot = spell->m_caster->ToCreature()->GetCharmer(); + if (bot && bot->IsNPCBot()) + bot->ToCreature()->AI()->SpellHitTarget(_spellHitTarget, spell->m_spellInfo); + } + //end npcbot + if (HitAura) { if (AuraApplication* aurApp = HitAura->GetApplicationOfTarget(_spellHitTarget->GetGUID())) @@ -2771,6 +2837,27 @@ SpellMissInfo Spell::PreprocessSpellHit(Unit* unit, bool scaleAura, TargetInfo& // assisting case, healing and resurrection if (unit->HasUnitState(UNIT_STATE_ATTACK_PLAYER)) { + //npcbot: bot assist case + if (m_caster->IsNPCBotOrPet() && (unit->IsNPCBotOrPet() || unit->IsPlayer())) + { + if (m_caster->ToCreature()->IsFreeBot()) + { + Unit const* bot = m_caster->IsNPCBotPet() ? m_caster->ToUnit()->GetCreator() : m_caster->ToUnit(); + if (bot && bot->IsNPCBot()) + BotMgr::SetBotContestedPvP(bot->ToCreature()); + } + else + { + if (Player const* pOwner = m_caster->ToUnit()->GetCreator() ? m_caster->ToUnit()->GetCreator()->ToPlayer() : nullptr) + { + Unit* bot = m_caster->IsNPCBotPet() ? static_cast(pOwner->GetBotMgr()->GetBot(m_caster->GetOwnerGUID())) : m_caster->ToUnit(); + if (bot && bot->IsNPCBot()) + BotMgr::SetBotContestedPvP(bot->ToCreature()); + } + } + } + else + //end npcbot if (Player* playerOwner = m_caster->GetCharmerOrOwnerPlayerOrPlayerItself()) { playerOwner->SetContestedPvP(); @@ -2782,6 +2869,10 @@ SpellMissInfo Spell::PreprocessSpellHit(Unit* unit, bool scaleAura, TargetInfo& { if (m_originalCaster->HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED)) // only do explicit combat forwarding for PvP enabled units m_originalCaster->GetCombatManager().InheritCombatStatesFrom(unit); // for creature v creature combat, the threat forward does it for us + //npcbot + else if (m_originalCaster->IsNPCBotOrPet()) + m_originalCaster->GetCombatManager().InheritCombatStatesFrom(unit); + //end npcbot unit->GetThreatManager().ForwardThreatForAssistingMe(m_originalCaster, 0.0f, nullptr, true); } } @@ -3000,6 +3091,11 @@ bool Spell::UpdateChanneledTargetList() if (Player* modOwner = m_caster->GetSpellModOwner()) modOwner->ApplySpellMod(m_spellInfo->Id, SPELLMOD_RANGE, range, this); + //npcbot: apply range mods + if (m_caster->IsNPCBot()) + m_caster->ToCreature()->ApplyCreatureSpellRangeMods(m_spellInfo, range); + //end npcbot + // add little tolerance level range += std::min(MAX_SPELL_RANGE_TOLERANCE, range*0.1f); // 10% but no more than MAX_SPELL_RANGE_TOLERANCE } @@ -3359,6 +3455,11 @@ void Spell::_cast(bool skipCheck) SendCastResult(res, p1, p2); SendInterrupted(res); + //npcbot - hook for spellcast finish (unsuccessful) + if (m_caster->IsNPCBotOrPet()) + BotMgr::OnBotSpellGo(m_caster->ToCreature(), this, false); + //end npcbot + if (modOwner) modOwner->SetSpellModTakingSpell(this, false); @@ -3514,6 +3615,17 @@ void Spell::_cast(bool skipCheck) handle_immediate(); } + //npcbot - hook for spellcast finish + if (m_caster->GetTypeId() == TYPEID_UNIT && m_caster->ToCreature()->IsNPCBotOrPet()) + BotMgr::OnBotSpellGo(m_caster->ToCreature(), this); + //npcbot - hook for master's spellcast finish + else if (m_caster->GetTypeId() == TYPEID_PLAYER && m_caster->ToPlayer()->HaveBot()) + BotMgr::OnBotOwnerSpellGo(m_caster->ToPlayer(), this); + //npcbot - hook for master's vehicle spellcast finish + else if (m_caster->ToUnit() && m_caster->ToUnit()->IsVehicle()) + BotMgr::OnVehicleSpellGo(m_caster->ToUnit(), this); + //end npcbot + CallScriptAfterCastHandlers(); if (std::vector const* spell_triggered = sSpellMgr->GetSpellLinked(m_spellInfo->Id)) @@ -3793,6 +3905,11 @@ void Spell::SendSpellCooldown() if (m_caster->GetTypeId() == TYPEID_GAMEOBJECT) return; + //npcbot: handled by AI + if (m_caster->IsNPCBotOrPet()) + return; + //end npcbot + m_caster->ToUnit()->GetSpellHistory()->HandleCooldowns(m_spellInfo, m_CastItem, this); } @@ -3868,6 +3985,10 @@ void Spell::update(uint32 difftime) if (Creature* creatureCaster = m_caster->ToCreature()) if (creatureCaster->IsAIEnabled()) creatureCaster->AI()->OnChannelFinished(m_spellInfo); + //npcbot: signal channel finish to botmgr + if (m_caster->IsNPCBot()) + BotMgr::OnBotChannelFinish(m_caster->ToUnit(), this); + //end npcbot } break; } @@ -3908,6 +4029,11 @@ void Spell::finish(bool ok) if (Creature* creatureCaster = unitCaster->ToCreature()) creatureCaster->ReleaseSpellFocus(this); + //npcbot + if (!ok && unitCaster->IsNPCBotOrPet()) + BotMgr::OnBotSpellGo(unitCaster, this, false); + //end npcbot + if (!ok) return; @@ -3958,6 +4084,9 @@ void Spell::finish(bool ok) // Stop Attack for some spells if (m_spellInfo->HasAttribute(SPELL_ATTR0_STOP_ATTACK_TARGET)) + //npcbot: disable for npcbots + if (!unitCaster->IsNPCBot()) + //end npcbot unitCaster->AttackStop(); } @@ -4812,9 +4941,39 @@ void Spell::TakePower() } } + //npcbot: handle SPELLMOD_SPELL_COST_REFUND_ON_FAIL (druid Primal Precision) + if (m_caster->IsNPCBot() && m_caster->ToCreature()->GetBotClass() == CLASS_DRUID) + { + if (powerType == POWER_ENERGY/* || powerType == POWER_RAGE || powerType == POWER_RUNE*/) + { + if (ObjectGuid targetGUID = m_targets.GetUnitTargetGUID()) + { + //auto ihit = std::find_if(std::being()); + for (std::vector::iterator ihit= m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) + { + if (ihit->TargetGUID == targetGUID && ihit->MissCondition != SPELL_MISS_NONE) + { + hit = false; + //Primal Precision: 80% refund + if ((m_spellInfo->SpellFamilyFlags[0] & 0x800000) || (m_spellInfo->SpellFamilyFlags[1] & 0x10000080)) + m_powerCost = m_powerCost / 5; + } + break; + } + } + } + } + //end npcbot + if (powerType == POWER_RUNE) { TakeRunePower(hit); + + //npcbot: spend runes (pass hit result) + if (m_caster->IsNPCBot() && m_caster->ToCreature()->GetBotClass() == CLASS_DEATH_KNIGHT) + m_caster->ToCreature()->SpendBotRunes(m_spellInfo, hit); + //end npcbot + return; } @@ -5171,6 +5330,17 @@ SpellCastResult Spell::CheckCast(bool strict, uint32* param1 /*= nullptr*/, uint else return SPELL_FAILED_NOT_READY; } + + //npcbot + if (m_caster->IsNPCBot() && m_caster->ToCreature()->HasSpellCooldown(m_spellInfo->Id) && !IsIgnoringCooldowns()) + { + //TC_LOG_ERROR("spells", "{} has cd of {} on {}", m_caster->GetName().c_str(), m_caster->ToCreature()->GetCreatureSpellCooldownDelay(m_spellInfo->Id), m_spellInfo->SpellName[0]); + if (m_triggeredByAuraSpell) + return SPELL_FAILED_DONT_REPORT; + //else + // return SPELL_FAILED_NOT_READY; + } + //end npcbot } if (m_spellInfo->HasAttribute(SPELL_ATTR7_IS_CHEAT_SPELL) && m_caster->IsUnit() && !m_caster->ToUnit()->HasUnitFlag2(UNIT_FLAG2_ALLOW_CHEAT_SPELLS)) @@ -5373,6 +5543,11 @@ SpellCastResult Spell::CheckCast(bool strict, uint32* param1 /*= nullptr*/, uint { if (spellEffectInfo.TargetA.GetTarget() == TARGET_UNIT_PET) { + //npcbot: allow bot pet as target + if (m_caster->IsNPCBot() && m_caster->ToCreature()->GetBotsPet()) + break; + else + //end npcbot if (!unitCaster->GetGuardianPet()) { if (m_triggeredByAuraSpell) // not report pet not existence for triggered spells @@ -5400,6 +5575,11 @@ SpellCastResult Spell::CheckCast(bool strict, uint32* param1 /*= nullptr*/, uint return SPELL_FAILED_NOT_IN_ARENA; // zone check + //npcbot: do not check location for bots (to avoid crash introduced in TC rev. 5cb8409f1ee57e8d) + if (m_caster->IsNPCBot()) + {} + else + //end npcbot if (m_caster->GetTypeId() != TYPEID_PLAYER || !m_caster->ToPlayer()->IsGameMaster()) { uint32 zone, area; @@ -5683,6 +5863,15 @@ SpellCastResult Spell::CheckCast(bool strict, uint32* param1 /*= nullptr*/, uint spellEffectInfo.TargetA.GetTarget() != TARGET_GAMEOBJECT_ITEM_TARGET) break; + //npcbot + if (m_caster->IsNPCBot()) + { + if (spellEffectInfo.TargetA.GetTarget() == TARGET_GAMEOBJECT_TARGET && !m_targets.GetGOTarget()) + return SPELL_FAILED_BAD_TARGETS; + break; + } + //end npcbot + if (m_caster->GetTypeId() != TYPEID_PLAYER // only players can open locks, gather etc. // we need a go target in case of TARGET_GAMEOBJECT_TARGET || (spellEffectInfo.TargetA.GetTarget() == TARGET_GAMEOBJECT_TARGET && !m_targets.GetGOTarget())) @@ -6024,6 +6213,13 @@ SpellCastResult Spell::CheckCast(bool strict, uint32* param1 /*= nullptr*/, uint if (target->GetCharmerGUID()) return SPELL_FAILED_CANT_BE_CHARMED; + + //npcbot: do not allow to charm owned npcbots + if (target->GetCreator() && target->GetCreator()->IsPlayer()) + return SPELL_FAILED_TARGET_IS_PLAYER_CONTROLLED; + else if (target->IsNPCBotOrPet()) + return SPELL_FAILED_CANT_BE_CHARMED; + //end npcbot if (target->GetOwner() && target->GetOwner()->GetTypeId() == TYPEID_PLAYER) return SPELL_FAILED_TARGET_IS_PLAYER_CONTROLLED; @@ -6138,6 +6334,14 @@ SpellCastResult Spell::CheckCast(bool strict, uint32* param1 /*= nullptr*/, uint { if (Unit* unitCaster = m_caster->ToUnit()) { + //npcbot + if (unitCaster->IsNPCBot()) + { + if (!unitCaster->ToCreature()->GetCreatureComboPoints()) + return SPELL_FAILED_NO_COMBO_POINTS; + } + else + //end npcbot if (m_spellInfo->NeedsExplicitUnitTarget()) { if (!unitCaster->GetComboPoints(m_targets.GetUnitTarget())) @@ -6487,6 +6691,11 @@ SpellCastResult Spell::CheckRange(bool strict) const float minRange, maxRange; std::tie(minRange, maxRange) = GetMinMaxRange(strict); + //npcbot: apply range mods + if (m_caster->IsNPCBot()) + m_caster->ToCreature()->ApplyCreatureSpellRangeMods(m_spellInfo, maxRange); + //end npcbot + // dont check max_range to strictly after cast if (m_spellInfo->RangeEntry && m_spellInfo->RangeEntry->Flags != SPELL_RANGE_MELEE && !strict) maxRange += std::min(MAX_SPELL_RANGE_TOLERANCE, maxRange*0.1f); // 10% but no more than MAX_SPELL_RANGE_TOLERANCE @@ -6511,6 +6720,14 @@ SpellCastResult Spell::CheckRange(bool strict) const if (GameObject* goTarget = m_targets.GetGOTarget()) { + //npcbot + if (!m_caster->IsPlayer()) + { + if (!goTarget->IsAtInteractDistance(*m_caster, m_spellInfo->GetMaxRange(m_spellInfo->IsPositive()))) + return SPELL_FAILED_OUT_OF_RANGE; + } + else + //end npcbot if (!goTarget->IsAtInteractDistance(m_caster->ToPlayer(), m_spellInfo)) return SPELL_FAILED_OUT_OF_RANGE; } @@ -7798,6 +8015,13 @@ SpellCastResult Spell::CanOpenLock(SpellEffectInfo const& spellEffectInfo, uint3 skillValue = m_CastItem || m_caster->GetTypeId() != TYPEID_PLAYER ? 0 : m_caster->ToPlayer()->GetSkillValue(skillId); + //npcbot: use bot skill if cast through gossip + if (m_originalCasterGUID) + if (Unit const* unit = ObjectAccessor::GetUnit(*m_caster, m_originalCasterGUID)) + if (unit->GetTypeId() == TYPEID_UNIT && unit->ToCreature()->GetBotClass() == CLASS_ROGUE) + skillValue = std::max(skillValue, int32(unit->GetLevel() * 5)); + //end npcbot + // skill bonus provided by casting spell (mostly item spells) // add the effect base points modifier from the spell cast (cheat lock / skeleton key etc.) if (spellEffectInfo.TargetA.GetTarget() == TARGET_GAMEOBJECT_ITEM_TARGET || spellEffectInfo.TargetB.GetTarget() == TARGET_GAMEOBJECT_ITEM_TARGET) @@ -8370,6 +8594,11 @@ WorldObjectSpellNearbyTargetCheck::WorldObjectSpellNearbyTargetCheck(float range bool WorldObjectSpellNearbyTargetCheck::operator()(WorldObject* target) { + //npcbot: custom check 1 for targeting bots by spells with SPELL_ATTR3_ONLY_TARGET_PLAYERS + if (_spellInfo->HasAttribute(SPELL_ATTR3_ONLY_TARGET_PLAYERS) && target->GetTypeId() == TYPEID_UNIT && !target->IsNPCBot()) + return false; + //end npcbot + float dist = target->GetDistance(*_position); if (dist < _range && WorldObjectSpellTargetCheck::operator ()(target)) { @@ -8394,6 +8623,11 @@ bool WorldObjectSpellAreaTargetCheck::operator()(WorldObject* target) const } else { + //npcbot: custom check 2 for targeting bots by spells with SPELL_ATTR3_ONLY_TARGET_PLAYERS + if (_spellInfo->HasAttribute(SPELL_ATTR3_ONLY_TARGET_PLAYERS) && target->GetTypeId() == TYPEID_UNIT && !target->IsNPCBot()) + return false; + //end npcbot + bool isInsideCylinder = target->IsWithinDist2d(_position, _range) && std::abs(target->GetPositionZ() - _position->GetPositionZ()) <= _range; if (!isInsideCylinder) return false; diff --git a/src/server/game/Spells/Spell.h b/src/server/game/Spells/Spell.h index 7abeb67b1..bf6650660 100644 --- a/src/server/game/Spells/Spell.h +++ b/src/server/game/Spells/Spell.h @@ -416,6 +416,9 @@ class TC_GAME_API Spell UsedSpellMods m_appliedMods; + //npcbot + int32 GetTimer() const { return m_timer; } + //end npcbot int32 GetCastTime() const { return m_casttime; } bool IsAutoRepeat() const { return m_autoRepeat; } void SetAutoRepeat(bool rep) { m_autoRepeat = rep; } diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp index 423883689..b98cdc31c 100644 --- a/src/server/game/Spells/SpellEffects.cpp +++ b/src/server/game/Spells/SpellEffects.cpp @@ -498,6 +498,17 @@ void Spell::EffectSchoolDMG() damage += int32(energy * multiple); damage += int32(CalculatePct(unitCaster->ToPlayer()->GetComboPoints() * ap, 7)); } + //npcbot: Ferocious Bite support + else if (unitCaster->IsNPCBot() && (m_spellInfo->SpellFamilyFlags[0] & 0x800000) && m_spellInfo->SpellVisual[0] == 6587) + { + // converts each extra point of energy into ($f1+$AP/410) additional damage + float ap = unitCaster->GetTotalAttackPowerValue(BASE_ATTACK); + float multiple = ap / 410 + effectInfo->DamageMultiplier; + int32 energy = -(unitCaster->ModifyPower(POWER_ENERGY, -30)); + damage += int32(energy * multiple); + damage += int32(CalculatePct(unitCaster->ToCreature()->GetCreatureComboPoints() * ap, 7)); + } + //end npcbot // Wrath else if (m_spellInfo->SpellFamilyFlags[0] & 0x00000001) { @@ -560,6 +571,52 @@ void Spell::EffectSchoolDMG() damage += combo * 40; } } + //npcbot: Envenom support + else if (unitCaster->IsNPCBot()) + { + // consume from stack dozes not more that have combo-points + if (uint8 combo = unitCaster->ToCreature()->GetCreatureComboPoints()) + { + // Lookup for Deadly poison (only attacker applied) + if (AuraEffect const* aurEff = unitTarget->GetAuraEffect(SPELL_AURA_PERIODIC_DAMAGE, SPELLFAMILY_ROGUE, 0x00010000, 0, 0, unitCaster->GetGUID())) + { + // count consumed deadly poison doses at target + bool needConsume = true; + uint32 spellId = aurEff->GetId(); + + uint32 doses = aurEff->GetBase()->GetStackAmount(); + if (doses > combo) + doses = combo; + + // Master Poisoner + Unit::AuraEffectList const& auraList = unitCaster->GetAuraEffectsByType(SPELL_AURA_MOD_AURA_DURATION_BY_DISPEL_NOT_STACK); + for (Unit::AuraEffectList::const_iterator iter = auraList.begin(); iter != auraList.end(); ++iter) + { + if ((*iter)->GetSpellInfo()->SpellFamilyName == SPELLFAMILY_ROGUE && (*iter)->GetSpellInfo()->SpellIconID == 1960) + { + uint32 chance = (*iter)->GetSpellInfo()->GetEffect(EFFECT_2).CalcValue(unitCaster); + + if (chance && roll_chance_i(chance)) + needConsume = false; + + break; + } + } + + if (needConsume) + for (uint32 i = 0; i < doses; ++i) + unitTarget->RemoveAuraFromStack(spellId, unitCaster->GetGUID()); + + damage *= doses; + damage += int32(unitCaster->GetTotalAttackPowerValue(BASE_ATTACK) * 0.09f * combo); + } + + // Eviscerate and Envenom Bonus Damage (item set effect) + if (unitCaster->HasAura(37169)) + damage += combo * 40; + } + } + //end npcbot } // Eviscerate else if (m_spellInfo->SpellFamilyFlags[0] & 0x00020000) @@ -576,6 +633,20 @@ void Spell::EffectSchoolDMG() damage += combo*40; } } + //npcbot: Eviscerate support + else if (unitCaster->IsNPCBot()) + { + if (uint32 combo = unitCaster->ToCreature()->GetCreatureComboPoints()) + { + float ap = unitCaster->GetTotalAttackPowerValue(BASE_ATTACK); + damage += std::lroundf(ap * combo * 0.07f); + + // Eviscerate and Envenom Bonus Damage (item set effect) + if (unitCaster->HasAura(37169)) + damage += combo*40; + } + } + //end npcbot } break; } @@ -626,6 +697,27 @@ void Spell::EffectSchoolDMG() damage += irand(int32(dmg_min), int32(dmg_max)); damage += int32(caster->GetAmmoDPS() * caster->GetAttackTime(RANGED_ATTACK) * 0.001f); } + //npcbot: calculate bot weapon damage + if (unitCaster->IsNPCBot()) + { + if (Item* item = unitCaster->ToCreature()->GetBotEquips(2/*BOT_SLOT_RANGED*/)) + { + ItemTemplate const* weaponTemplate = item->GetTemplate(); + float dmg_min = 0.f; + float dmg_max = 0.f; + for (uint8 i = 0; i < MAX_ITEM_PROTO_DAMAGES; ++i) + { + dmg_min += weaponTemplate->Damage[i].DamageMin; + dmg_max += weaponTemplate->Damage[i].DamageMax; + } + if (dmg_max == 0.0f && dmg_min > dmg_max) + damage += int32(dmg_min); + else + damage += irand(int32(dmg_min), int32(dmg_max)); + damage += int32(unitCaster->ToCreature()->GetCreatureAmmoDPS() * weaponTemplate->Delay * 0.001f); + } + } + //end npcbot } break; } @@ -731,6 +823,60 @@ void Spell::EffectTriggerSpell() // special cases switch (triggered_spell_id) { + //npcbot: triggered heal/energize calculation (effect) + // Quest - Self Healing from resurrect (invisible in log) + case 25155: + { + switch (m_spellInfo->Id) + { + //Replenish Life (Regenerating Aura) + case 34756: + { + //cannot target self + if (unitCaster == unitTarget) + return; + + // % of max health + int32 basepoints0 = 0.01f * unitTarget->GetMaxHealth() * effectInfo->BasePoints; + //TC_LOG_ERROR("entities.unit", "TriggerSpell(%u from %u): %s on %s base val %i,", + // triggered_spell_id, m_spellInfo->Id, m_caster->GetName().c_str(), unitTarget->GetName().c_str(), int32(basepoints0)); + CastSpellExtraArgs args(true); + args.AddSpellBP0(basepoints0); + unitTarget->CastSpell(unitTarget, triggered_spell_id, args); + return; + } + default: + break; + } + break; + } + // Energize (invisible in log) + case 60628: + { + switch (m_spellInfo->Id) + { + //Replenish Mana + case 33394: + { + //cannot target self + if (unitCaster == unitTarget) + return; + + // % of max mana + int32 basepoints0 = effectInfo->BasePoints; + //TC_LOG_ERROR("entities.unit", "TriggerSpell(%u from %u): %s on %s base val %i,", + // triggered_spell_id, m_spellInfo->Id, m_caster->GetName().c_str(), unitTarget->GetName().c_str(), int32(basepoints0)); + CastSpellExtraArgs args(true); + args.AddSpellBP0(basepoints0); + unitTarget->CastSpell(unitTarget, triggered_spell_id, args); + return; + } + default: + break; + } + break; + } + //end npcbot // Mirror Image case 58832: { @@ -794,6 +940,10 @@ void Spell::EffectTriggerSpell() return; } + //npcbot: override spellInfo + spellInfo = spellInfo->TryGetSpellInfoOverride(GetCaster()); + //end npcbot + SpellCastTargets targets; if (effectHandleMode == SPELL_EFFECT_HANDLE_LAUNCH_TARGET) { @@ -851,6 +1001,10 @@ void Spell::EffectTriggerMissileSpell() return; } + //npcbot: override spellInfo + spellInfo = spellInfo->TryGetSpellInfoOverride(GetCaster()); + //end npcbot + SpellCastTargets targets; if (effectHandleMode == SPELL_EFFECT_HANDLE_HIT_TARGET) { @@ -1125,6 +1279,11 @@ void Spell::EffectPowerDrain() if (powerType == POWER_MANA) power -= unitTarget->GetSpellCritDamageReduction(power); + //npcbot: handle Obsidian Destroyer's Drain Mana (target is friendly, amount is only limited by caster's max mana) + if (unitCaster->GetTypeId() == TYPEID_UNIT && unitCaster->ToCreature()->GetBotClass() == 13 && powerType == POWER_MANA) + power = unitCaster->GetMaxPower(powerType); + //end npcbot + int32 newDamage = -(unitTarget->ModifyPower(powerType, -int32(power))); // Don't restore from self drain @@ -1775,6 +1934,48 @@ void Spell::EffectOpenLock() if (effectHandleMode != SPELL_EFFECT_HANDLE_HIT_TARGET) return; + //npcbot + if (m_caster->IsNPCBot() && gameObjTarget) + { + GameObjectTemplate const* botGoInfo = gameObjTarget->GetGOInfo(); + Creature* bot = m_caster->ToCreature(); + + if (botGoInfo->CannotBeUsedUnderImmunity() && bot->HasUnitFlag(UNIT_FLAG_IMMUNE)) + return; + + // Arathi Basin banner opening. /// @todo Verify correctness of this check + if ((botGoInfo->type == GAMEOBJECT_TYPE_BUTTON && botGoInfo->button.noDamageImmune) || + (botGoInfo->type == GAMEOBJECT_TYPE_GOOBER && botGoInfo->goober.losOK)) + { + //CanUseBattlegroundObject() already called in CheckCast() + // in battleground check + if (Battleground* bg = bot->GetBotBG()) + { + bg->EventBotClickedOnFlag(bot, gameObjTarget); + return; + } + } + else if (botGoInfo->type == GAMEOBJECT_TYPE_FLAGSTAND) + { + //CanUseBattlegroundObject() already called in CheckCast() + // in battleground check + if (Battleground* bg = bot->GetBotBG()) + { + if (bg->GetTypeID(true) == BATTLEGROUND_EY) + bg->EventBotClickedOnFlag(bot, gameObjTarget); + return; + } + } + else if (botGoInfo->type == GAMEOBJECT_TYPE_TRAP) + { + gameObjTarget->SetLootState(GO_ACTIVATED); + return; + } + + return; + } + //end npcbot + if (m_caster->GetTypeId() != TYPEID_PLAYER) { TC_LOG_DEBUG("spells", "WORLD: Open Lock - No Player Caster!"); @@ -3066,6 +3267,14 @@ void Spell::EffectWeaponDmg() if (Item* item = unitCaster->ToPlayer()->GetWeaponForAttack(m_attackType, true)) if (item->GetTemplate()->SubClass == ITEM_SUBCLASS_WEAPON_DAGGER) totalDamagePercentMod *= 1.5f; + + //npcbot: handle bot weapons + // 50% more damage with daggers + if (unitCaster->IsNPCBot()) + if (Item const* weapon = unitCaster->ToCreature()->GetBotEquips(m_attackType)) + if (weapon->GetTemplate()->SubClass == ITEM_SUBCLASS_WEAPON_DAGGER) + totalDamagePercentMod *= 1.5f; + //end npcbot } // Mutilate (for each hand) else if (m_spellInfo->SpellFamilyFlags[1] & 0x6) @@ -3409,6 +3618,14 @@ void Spell::EffectSummonObjectWild() if (Battleground* bg = player->GetBattleground()) bg->SetDroppedFlagGUID(pGameObj->GetGUID(), player->GetTeam() == ALLIANCE ? TEAM_HORDE: TEAM_ALLIANCE); + //npcbot + if (m_caster->IsNPCBot() && pGameObj->GetGoType() == GAMEOBJECT_TYPE_FLAGDROP) + { + if (Battleground* bg = m_caster->ToCreature()->GetBotBG()) + bg->SetDroppedFlagGUID(pGameObj->GetGUID(), bg->GetOtherTeamId(bg->GetBotTeamId(m_caster->GetGUID()))); + } + //end npcbot + if (GameObject* linkedTrap = pGameObj->GetLinkedTrap()) { linkedTrap->SetRespawnTime(duration > 0 ? duration / IN_MILLISECONDS : 0); diff --git a/src/server/game/Spells/SpellInfo.cpp b/src/server/game/Spells/SpellInfo.cpp index d308b1ff0..7fd2abbe3 100644 --- a/src/server/game/Spells/SpellInfo.cpp +++ b/src/server/game/Spells/SpellInfo.cpp @@ -32,6 +32,11 @@ #include "SpellMgr.h" #include "Vehicle.h" +//npcbot +#include "botmgr.h" +#include "botspell.h" +//end npcbot + uint32 GetTargetFlagMask(SpellTargetObjectTypes objType) { switch (objType) @@ -474,9 +479,38 @@ int32 SpellEffectInfo::CalcValue(WorldObject const* caster /*= nullptr*/, int32 // random damage if (casterUnit) { + //npcbot: Life Burst heal tempfix 2013 + float pointsPerComboPoint = PointsPerComboPoint; + if (_spellInfo->Id == 57143 && EffectIndex == EFFECT_1) + { + basePoints = 2500; + value = float(basePoints); + pointsPerComboPoint = 2500.f; + } + //npcbot: bonus amount from combo points and specific mods + if (casterUnit->IsNPCBot()) + { + if (uint8 comboPoints = casterUnit->ToCreature()->GetCreatureComboPoints()) + value += pointsPerComboPoint * comboPoints; + } + //npcbot: bonus amount from combo points (vehicle) + else if (casterUnit->IsVehicle() && casterUnit->GetTypeId() == TYPEID_UNIT && casterUnit->GetCharmerGUID().IsCreature() && + PointsPerComboPoint) + { + Unit const* bot = casterUnit->GetCharmer(); + if (bot && bot->IsNPCBot()) + if (uint8 comboPoints = bot->ToCreature()->GetCreatureComboPoints()) + value += pointsPerComboPoint * comboPoints; + } + //npcbot: bonus amount from combo points + else if (uint8 comboPoints = casterUnit->GetComboPoints()) + value += pointsPerComboPoint * comboPoints; + //end npcbot + /* // bonus amount from combo points if (uint8 comboPoints = casterUnit->GetComboPoints()) value += PointsPerComboPoint * comboPoints; + */ } if (caster) @@ -554,6 +588,11 @@ float SpellEffectInfo::CalcValueMultiplier(WorldObject* caster, Spell* spell /*= if (Player* modOwner = (caster ? caster->GetSpellModOwner() : nullptr)) modOwner->ApplySpellMod(_spellInfo->Id, SPELLMOD_VALUE_MULTIPLIER, multiplier, spell); + //npcbot - apply bot spell effect value mult mods + if (caster && caster->IsNPCBot()) + BotMgr::ApplyBotEffectValueMultiplierMods(caster->ToCreature(), _spellInfo, EffectIndex, multiplier); + //end npcbot + return multiplier; } @@ -586,6 +625,11 @@ float SpellEffectInfo::CalcRadius(WorldObject* caster /*= nullptr*/, Spell* spel if (Player* modOwner = caster->GetSpellModOwner()) modOwner->ApplySpellMod(_spellInfo->Id, SPELLMOD_RADIUS, radius, spell); + + //npcbot - apply bot spell radius mods + if (caster->GetTypeId() == TYPEID_UNIT && caster->ToCreature()->IsNPCBotOrPet()) + caster->ToCreature()->ApplyCreatureSpellRadiusMods(_spellInfo, radius); + //end npcbot } return radius; @@ -892,6 +936,12 @@ SpellInfo::~SpellInfo() _UnloadImplicitTargetConditionLists(); } +SpellInfo const* SpellInfo::TryGetSpellInfoOverride(WorldObject const* caster) const +{ + SpellInfo const* spellInfoOverride = (caster && caster->IsNPCBotOrPet()) ? GetBotSpellInfoOverride(Id) : nullptr; + return spellInfoOverride ? spellInfoOverride : this; +} + uint32 SpellInfo::GetCategory() const { return CategoryEntry ? CategoryEntry->ID : 0; @@ -1717,6 +1767,9 @@ SpellCastResult SpellInfo::CheckTarget(WorldObject const* caster, WorldObject co // corpseOwner and unit specific target checks if (HasAttribute(SPELL_ATTR3_ONLY_TARGET_PLAYERS) && unitTarget->GetTypeId() != TYPEID_PLAYER) + //npcbot: allow to target bots + if (!unitTarget->IsNPCBot()) + //end npcbot return SPELL_FAILED_TARGET_NOT_PLAYER; if (!IsAllowingDeadTarget() && !unitTarget->IsAlive()) @@ -1818,6 +1871,13 @@ SpellCastResult SpellInfo::CheckExplicitTarget(WorldObject const* caster, WorldO return SPELL_CAST_OK; return SPELL_FAILED_BAD_TARGETS; } + //npcbot + else if ((neededTargets & TARGET_FLAG_CORPSE_ALLY) && unitTarget->IsNPCBot()) + { + if (!caster->IsValidAssistTarget(unitTarget, this)) + return SPELL_FAILED_BAD_TARGETS; + } + //end npcbot } return SPELL_CAST_OK; } @@ -3310,6 +3370,11 @@ int32 SpellInfo::CalcPowerCost(WorldObject const* caster, SpellSchoolMask school } } + //npcbot - apply bot spell cost mods + if (powerCost > 0 && caster->IsNPCBot()) + caster->ToCreature()->ApplyCreatureSpellCostMods(this, powerCost); + //end npcbot + // PCT mod from user auras by school powerCost = int32(powerCost * (1.0f + unitCaster->GetFloatValue(UNIT_FIELD_POWER_COST_MULTIPLIER + AsUnderlyingType(school)))); if (powerCost < 0) diff --git a/src/server/game/Spells/SpellInfo.h b/src/server/game/Spells/SpellInfo.h index e736581fe..2591026e1 100644 --- a/src/server/game/Spells/SpellInfo.h +++ b/src/server/game/Spells/SpellInfo.h @@ -234,12 +234,30 @@ public: SpellEffectInfo(); explicit SpellEffectInfo(SpellEntry const* spellEntry, SpellInfo const* spellInfo, uint8 effIndex); + //npcbot + /* + //end npcbot SpellEffectInfo(SpellEffectInfo const&) = delete; + //npcbot + */ + SpellEffectInfo(SpellEffectInfo const&) = default; + //end npcbot SpellEffectInfo(SpellEffectInfo&&) noexcept; + //npcbot + /* + //end npcbot SpellEffectInfo& operator=(SpellEffectInfo const&) = delete; + //npcbot + */ + SpellEffectInfo& operator=(SpellEffectInfo const&) = default; + //end npcbot SpellEffectInfo& operator=(SpellEffectInfo&&) noexcept; ~SpellEffectInfo(); + //npcbot + void OverrideSpellInfo(SpellInfo const* spellInfo) { ASSERT_NOTNULL(spellInfo); _spellInfo = spellInfo; } + //end npcbot + bool IsEffect() const; bool IsEffect(SpellEffects effectName) const; bool IsAura() const; @@ -273,7 +291,14 @@ private: }; static std::array _data; + //npcbot + /* + //end npcbot std::unique_ptr _immunityInfo; + //npcbot + */ + std::shared_ptr _immunityInfo; + //end npcbot }; struct TC_GAME_API SpellDiminishInfo @@ -500,6 +525,10 @@ class TC_GAME_API SpellInfo uint32 GetMechanicImmunityMask(Unit* caster) const; + //npcbot + SpellInfo const* TryGetSpellInfoOverride(WorldObject const* caster) const; + //end npcbot + private: // loading helpers void _InitializeExplicitTargetMask(); diff --git a/src/server/game/Spells/SpellMgr.cpp b/src/server/game/Spells/SpellMgr.cpp index a78983627..f8a26af98 100644 --- a/src/server/game/Spells/SpellMgr.cpp +++ b/src/server/game/Spells/SpellMgr.cpp @@ -2958,6 +2958,18 @@ void SpellMgr::LoadSpellInfoCorrections() { uint32 oldMSTime = getMSTime(); + //npcbot: corrections for Gunship Battle Shoot: should be able to target creatures (Hurl Axe can) + ApplySpellFix({ + 70162, // Shoot 10N + 72566, // Shoot 25N + 72567, // Shoot 10H + 72568 // Shoot 25H + }, [](SpellInfo* spellInfo) + { + spellInfo->AttributesEx3 &= ~SPELL_ATTR3_ONLY_TARGET_PLAYERS; + spellInfo->TargetAuraSpell = 0; + }); + // Some spells have no amplitude set { ApplySpellFix({ diff --git a/src/server/scripts/Commands/cs_npc.cpp b/src/server/scripts/Commands/cs_npc.cpp index 9e8529f1c..f8b72893a 100644 --- a/src/server/scripts/Commands/cs_npc.cpp +++ b/src/server/scripts/Commands/cs_npc.cpp @@ -119,6 +119,16 @@ public: if (!sObjectMgr->GetCreatureTemplate(id)) return false; + //npcbot + CreatureTemplate const* cinfo = sObjectMgr->GetCreatureTemplate(id); + if (cinfo && cinfo->IsNPCBotOrPet()) + { + handler->PSendSysMessage("You tried to spawn creature %u, which is part of NPCBots mod. To spawn bots use '.npcbot spawn' instead.", id); + handler->SetSentErrorMessage(true); + return false; + } + //end npcbot + Player* chr = handler->GetSession()->GetPlayer(); Map* map = chr->GetMap(); @@ -301,6 +311,16 @@ public: handler->SetSentErrorMessage(true); return false; } + + //npcbot + if (creature->IsNPCBotOrPet()) + { + handler->SendSysMessage("Selected creature has botAI assigned, use '.npcbot delete' instead"); + handler->SetSentErrorMessage(true); + return false; + } + //end npcbot + if (TempSummon* summon = creature->ToTempSummon()) { summon->UnSummon(); @@ -601,6 +621,17 @@ public: return false; } + //npcbot + CreatureTemplate const* ct = sObjectMgr->GetCreatureTemplate(data->id); + ASSERT(ct); + if (ct->IsNPCBotOrPet()) + { + handler->PSendSysMessage("creature %u (id %u) is a part of NPCBots mod. Use '.npcbot move' instead", lowguid, data->id); + handler->SetSentErrorMessage(true); + return false; + } + //end npcbot + // update position in memory sObjectMgr->RemoveCreatureFromGrid(lowguid, data); const_cast(data)->spawnPoint.Relocate(*player); diff --git a/src/server/scripts/Kalimdor/CavernsOfTime/BattleForMountHyjal/hyjalAI.cpp b/src/server/scripts/Kalimdor/CavernsOfTime/BattleForMountHyjal/hyjalAI.cpp index b60826b64..cf9d5a7b9 100644 --- a/src/server/scripts/Kalimdor/CavernsOfTime/BattleForMountHyjal/hyjalAI.cpp +++ b/src/server/scripts/Kalimdor/CavernsOfTime/BattleForMountHyjal/hyjalAI.cpp @@ -994,6 +994,11 @@ void hyjalAI::DoOverrun(uint32 faction, const uint32 diff) { for (std::list::const_iterator itr = creatures.begin(); itr != creatures.end(); ++itr) { + //npcbot: prevent bots from getting UNIT_FLAG_NON_ATTACKABLE + if ((*itr)->IsNPCBotOrPet()) + continue; + //end npcbot + if ((*itr) && (*itr)->IsAlive()) { (*itr)->CastSpell(*itr, SPELL_TELEPORT_VISUAL, true); diff --git a/src/server/scripts/Kalimdor/CavernsOfTime/BattleForMountHyjal/hyjal_trash.cpp b/src/server/scripts/Kalimdor/CavernsOfTime/BattleForMountHyjal/hyjal_trash.cpp index 38511e9ea..0baa1fd48 100644 --- a/src/server/scripts/Kalimdor/CavernsOfTime/BattleForMountHyjal/hyjal_trash.cpp +++ b/src/server/scripts/Kalimdor/CavernsOfTime/BattleForMountHyjal/hyjal_trash.cpp @@ -200,6 +200,14 @@ hyjal_trashAI::hyjal_trashAI(Creature* creature) : EscortAI(creature) void hyjal_trashAI::DamageTaken(Unit* done_by, uint32& damage, DamageEffectType /*damageType*/, SpellInfo const* /*spellInfo = nullptr*/) { + //npcbot: + if (done_by && done_by->GetTypeId() == TYPEID_UNIT && done_by->ToCreature()->IsNPCBotOrPet()) + { + damageTaken += damage; + instance->SetData(DATA_RAIDDAMAGE, damage); + } + else + //end npcbot if (!done_by || done_by->GetTypeId() == TYPEID_PLAYER || done_by->IsPet()) { damageTaken += damage; diff --git a/src/server/scripts/Northrend/IcecrownCitadel/boss_icecrown_gunship_battle.cpp b/src/server/scripts/Northrend/IcecrownCitadel/boss_icecrown_gunship_battle.cpp index 24ce5a81e..c85e6c837 100644 --- a/src/server/scripts/Northrend/IcecrownCitadel/boss_icecrown_gunship_battle.cpp +++ b/src/server/scripts/Northrend/IcecrownCitadel/boss_icecrown_gunship_battle.cpp @@ -38,6 +38,10 @@ #include "Vehicle.h" #include +//npcbot +#include "botmgr.h" +//end npcbot + enum Texts { // High Overlord Saurfang @@ -614,6 +618,10 @@ struct gunship_npc_AI : public ScriptedAI { if (Instance->GetBossState(DATA_ICECROWN_GUNSHIP_BATTLE) != IN_PROGRESS) return false; + //npcbot: allow to attack bots whereever they are + if (target->GetTypeId() == TYPEID_UNIT && target->ToCreature()->IsNPCBotOrPet()) + return true; + //end npcbot return target->HasAura(Instance->GetData(DATA_TEAM_IN_INSTANCE) == HORDE ? SPELL_ON_ORGRIMS_HAMMER_DECK : SPELL_ON_SKYBREAKER_DECK); } @@ -744,6 +752,15 @@ class npc_gunship : public CreatureScript hull->CastSpell(hull, explosionSpell, TRIGGERED_FULL_MASK); } + //npcbot: kill bots + Transport::PassengerSet const& allpassengers = me->GetTransport()->GetPassengers(); + for (Transport::PassengerSet::const_iterator citr = allpassengers.begin(); citr != allpassengers.end(); ++citr) + { + if ((*citr)->GetTypeId() == TYPEID_PLAYER && (*citr)->ToPlayer()->HaveBot()) + (*citr)->ToPlayer()->GetBotMgr()->KillAllBots(); + } + //end npcbot + creatures.clear(); GetCreatureListWithEntryInGrid(creatures, me, _teamInInstance == HORDE ? NPC_HORDE_GUNSHIP_CANNON : NPC_ALLIANCE_GUNSHIP_CANNON, 200.0f); for (std::list::iterator itr = creatures.begin(); itr != creatures.end(); ++itr) @@ -1511,6 +1528,17 @@ struct npc_gunship_boarding_addAI : public gunship_npc_AI return !me->_IsTargetAcceptable(player) || !me->CanStartAttack(player, true); }); + //npcbot: allow bots as targets + auto npcbot_check = [this](Creature const* creature) { + return creature->IsNPCBotOrPet() && me->_IsTargetAcceptable(creature) && me->CanStartAttack(creature, true); + }; + Creature* anybot = nullptr; + Trinity::CreatureSearcher botsearcher(me, anybot, npcbot_check); + Cell::VisitWorldObjects(me, botsearcher, 70.0f); + if (anybot) + return true; + //end npcbot + return !players.empty(); } diff --git a/src/server/scripts/Northrend/IcecrownCitadel/instance_icecrown_citadel.cpp b/src/server/scripts/Northrend/IcecrownCitadel/instance_icecrown_citadel.cpp index 339406ae8..038a945b6 100644 --- a/src/server/scripts/Northrend/IcecrownCitadel/instance_icecrown_citadel.cpp +++ b/src/server/scripts/Northrend/IcecrownCitadel/instance_icecrown_citadel.cpp @@ -203,6 +203,19 @@ class instance_icecrown_citadel : public InstanceMapScript DoRemoveAurasDueToSpellOnPlayer(player, TeamInInstance == ALLIANCE ? SPELL_STRENGHT_OF_WRYNN : SPELL_HELLSCREAMS_WARSONG, true, true); } + //npcbot: handle bot map transfer + void OnNPCBotEnter(Creature* bot) override + { + if (IsFactionBuffActive) + DoCastSpellOnNPCBot(bot, TeamInInstance == ALLIANCE ? SPELL_STRENGHT_OF_WRYNN : SPELL_HELLSCREAMS_WARSONG); + } + + void OnNPCBotLeave(Creature* bot) override + { + DoRemoveAurasDueToSpellOnNPCBot(bot, TeamInInstance == ALLIANCE ? SPELL_STRENGHT_OF_WRYNN : SPELL_HELLSCREAMS_WARSONG); + } + //end npcbot + void OnCreatureCreate(Creature* creature) override { if (creature->IsGuardian() && creature->GetOwnerGUID().IsPlayer()) @@ -211,6 +224,14 @@ class instance_icecrown_citadel : public InstanceMapScript creature->CastSpell(creature, TeamInInstance == ALLIANCE ? SPELL_STRENGHT_OF_WRYNN : SPELL_HELLSCREAMS_WARSONG, true); } + //npcbot: handle bot pets + if (creature->IsNPCBotPet()) + { + if (IsFactionBuffActive) + creature->CastSpell(creature, TeamInInstance == ALLIANCE ? SPELL_STRENGHT_OF_WRYNN : SPELL_HELLSCREAMS_WARSONG, true); + } + //end npcbot + switch (creature->GetEntry()) { case NPC_NERUBAR_BROODKEEPER: diff --git a/src/server/scripts/Northrend/Naxxramas/boss_four_horsemen.cpp b/src/server/scripts/Northrend/Naxxramas/boss_four_horsemen.cpp index 6bf76c954..ef2370e5e 100644 --- a/src/server/scripts/Northrend/Naxxramas/boss_four_horsemen.cpp +++ b/src/server/scripts/Northrend/Naxxramas/boss_four_horsemen.cpp @@ -565,7 +565,7 @@ struct boss_four_horsemen_lady : public boss_four_horsemen_baseAI if (me->HasUnitState(UNIT_STATE_CASTING)) return; - if (Unit* target = SelectTarget(SelectTargetMethod::MinDistance, 0, 45.0f, true)) + if (Unit* target = SelectTarget(SelectTargetMethod::MinDistance, 0, 45.0f)) DoCast(target, SPELL_SHADOW_BOLT); else { @@ -624,7 +624,7 @@ struct boss_four_horsemen_sir : public boss_four_horsemen_baseAI if (me->HasUnitState(UNIT_STATE_CASTING)) return; - if (Unit* target = SelectTarget(SelectTargetMethod::MinDistance, 0, 45.0f, true)) + if (Unit* target = SelectTarget(SelectTargetMethod::MinDistance, 0, 45.0f)) DoCast(target, SPELL_HOLY_BOLT); else { diff --git a/src/server/scripts/Outland/Auchindoun/AuchenaiCrypts/boss_shirrak_the_dead_watcher.cpp b/src/server/scripts/Outland/Auchindoun/AuchenaiCrypts/boss_shirrak_the_dead_watcher.cpp index 14458a22c..20ca16a77 100644 --- a/src/server/scripts/Outland/Auchindoun/AuchenaiCrypts/boss_shirrak_the_dead_watcher.cpp +++ b/src/server/scripts/Outland/Auchindoun/AuchenaiCrypts/boss_shirrak_the_dead_watcher.cpp @@ -141,7 +141,14 @@ struct boss_shirrak_the_dead_watcher : public BossAI { // Summon Focus Fire & Emote Unit* target = SelectTarget(SelectTargetMethod::Random, 1); + //npcbot + /* + //end npcbot if (target && target->GetTypeId() == TYPEID_PLAYER && target->IsAlive()) + //npcbot + */ + if (target && target->IsAlive()) + //end npcbot { FocusedTargetGUID = target->GetGUID(); me->SummonCreature(NPC_FOCUS_FIRE, target->GetPositionX(), target->GetPositionY(), target->GetPositionZ(), 0, TEMPSUMMON_TIMED_DESPAWN, 5500ms); diff --git a/src/server/scripts/Outland/BlackTemple/boss_warlord_najentus.cpp b/src/server/scripts/Outland/BlackTemple/boss_warlord_najentus.cpp index 0b59044f9..9b6a553d1 100644 --- a/src/server/scripts/Outland/BlackTemple/boss_warlord_najentus.cpp +++ b/src/server/scripts/Outland/BlackTemple/boss_warlord_najentus.cpp @@ -158,6 +158,22 @@ struct boss_najentus : public BossAI target->SummonGameObject(GO_NAJENTUS_SPINE, *target, QuaternionData(), 30s); Talk(SAY_NEEDLE); } + //npcbot: try selecting npcbot + else if (Unit* bottarget = SelectTarget(SelectTargetMethod::Random, 1, [this](Unit const* target) -> bool { + if (!target || !target->IsNPCBot() || target->ToCreature()->IsFreeBot() || + !me->IsWithinCombatRange(target, 200.0f)) + return false; + + return true; + })) + { + DoCast(bottarget, SPELL_IMPALING_SPINE, true); + _spineTargetGUID = bottarget->GetGUID(); + //must let target summon, otherwise you cannot click the spine + bottarget->SummonGameObject(GO_NAJENTUS_SPINE, *bottarget, QuaternionData(), 30s); + Talk(SAY_NEEDLE); + } + //end npcbot events.Repeat(Seconds(20), Seconds(25)); break; case EVENT_NEEDLE: diff --git a/src/server/scripts/Spells/spell_dk.cpp b/src/server/scripts/Spells/spell_dk.cpp index 71eb6b58f..fda09a36f 100644 --- a/src/server/scripts/Spells/spell_dk.cpp +++ b/src/server/scripts/Spells/spell_dk.cpp @@ -360,6 +360,13 @@ private: return; amount = talentSpell->GetEffect(EFFECT_0).CalcValue(owner); + //npcbot: take bot attack power into account + if (Creature const* bot = owner->ToCreature()) + { + if (bot->IsNPCBot()) + amount += int32(2 * bot->GetTotalAttackPowerValue(BASE_ATTACK)); + } + //end npcbot if (Player* player = owner->ToPlayer()) amount += int32(2 * player->GetTotalAttackPowerValue(BASE_ATTACK)); } @@ -1848,6 +1855,16 @@ private: void Absorb(AuraEffect* /*aurEff*/, DamageInfo & dmgInfo, uint32 & absorbAmount) { // You have a chance equal to your Parry chance + //npcbot handle creature case (and prevent crashes) + Unit* target = GetTarget(); + if (target->GetTypeId() == TYPEID_UNIT) + { + if (dmgInfo.GetDamageType() == SPELL_DIRECT_DAMAGE && + roll_chance_f(target->ToCreature()->GetCreatureParryChance())) + absorbAmount = CalculatePct(dmgInfo.GetDamage(), absorbPct); + } + else + //end npcbot if ((dmgInfo.GetDamageType() == SPELL_DIRECT_DAMAGE) && roll_chance_f(GetTarget()->GetFloatValue(PLAYER_PARRY_PERCENTAGE))) absorbAmount = CalculatePct(dmgInfo.GetDamage(), absorbPct); } diff --git a/src/server/scripts/Spells/spell_druid.cpp b/src/server/scripts/Spells/spell_druid.cpp index 9efa58725..fa1bc2ff7 100644 --- a/src/server/scripts/Spells/spell_druid.cpp +++ b/src/server/scripts/Spells/spell_druid.cpp @@ -31,6 +31,11 @@ #include "SpellMgr.h" #include "SpellScript.h" +//npcbot +#include "Creature.h" +#include "Group.h" +//end npcbot + enum DruidSpells { SPELL_DRUID_BEAR_FORM_PASSIVE = 1178, @@ -1195,6 +1200,10 @@ class spell_dru_rip : public AuraScript bool Load() override { Unit* caster = GetCaster(); + //npcbot + if (caster && caster->IsNPCBot()) + return true; + //end npcbot return caster && GetCaster()->GetTypeId() == TYPEID_PLAYER; } @@ -1204,6 +1213,22 @@ class spell_dru_rip : public AuraScript if (Unit* caster = GetCaster()) { + //npcbot + if (caster && caster->IsNPCBot()) + { + uint8 botcp = caster->ToCreature()->GetCreatureComboPoints(); + // Idol of Feral Shadows. Can't be handled as SpellMod due its dependency from CPs + if (AuraEffect const* auraEffIdolOfFeralShadows = caster->GetAuraEffect(SPELL_DRUID_IDOL_OF_FERAL_SHADOWS, EFFECT_0)) + amount += botcp * auraEffIdolOfFeralShadows->GetAmount(); + // Idol of Worship. Can't be handled as SpellMod due its dependency from CPs + else if (AuraEffect const* auraEffIdolOfWorship = caster->GetAuraEffect(SPELL_DRUID_IDOL_OF_WORSHIP, EFFECT_0)) + amount += botcp * auraEffIdolOfWorship->GetAmount(); + + amount += int32(CalculatePct(caster->GetTotalAttackPowerValue(BASE_ATTACK), botcp)); + return; + } + //end npcbot + // 0.01 * $AP * cp uint8 cp = caster->ToPlayer()->GetComboPoints(); @@ -1752,6 +1777,41 @@ class spell_dru_t10_restoration_4p_bonus : public SpellScript void FilterTargets(std::list& targets) { + //npcbot + if (Creature* bot = GetCaster()->ToCreature()) + { + if (bot->IsFreeBot()) + { + targets.clear(); + targets.push_back(bot); + return; + } + + targets.remove(GetExplTargetUnit()); + std::list tempTargets; + Group const* gr = bot->GetBotOwner()->GetGroup(); + if (gr && !gr->IsMember(bot->GetGUID())) + gr = nullptr; + + if (gr) + for (std::list::const_iterator itr = targets.begin(); itr != targets.end(); ++itr) + if (gr->IsMember((*itr)->GetGUID())) + tempTargets.push_back((*itr)->ToUnit()); + + if (tempTargets.empty()) + { + targets.clear(); + FinishCast(SPELL_FAILED_DONT_REPORT); + return; + } + + Unit* target = Trinity::Containers::SelectRandomContainerElement(tempTargets); + targets.clear(); + targets.push_back(target); + return; + } + //end npcbot + if (!GetCaster()->ToPlayer()->GetGroup()) { targets.clear(); @@ -1803,6 +1863,11 @@ class spell_dru_t10_restoration_4p_bonus_dummy : public AuraScript if (!healInfo || !healInfo->GetHeal()) return false; + //npcbot: support for Item - Druid T10 Restoration 4P Bonus (Rejuvenation) + if (eventInfo.GetActor()->IsNPCBot()) + return true; + //end npcbot + Player* caster = eventInfo.GetActor()->ToPlayer(); if (!caster) return false; diff --git a/src/server/scripts/Spells/spell_generic.cpp b/src/server/scripts/Spells/spell_generic.cpp index 812645925..4da6b12e6 100644 --- a/src/server/scripts/Spells/spell_generic.cpp +++ b/src/server/scripts/Spells/spell_generic.cpp @@ -248,6 +248,11 @@ class spell_gen_arena_drink : public AuraScript bool Load() override { + //npcbot + if (GetCaster() && GetCaster()->IsNPCBot()) + return true; + //end npcbot + return GetCaster() && GetCaster()->GetTypeId() == TYPEID_PLAYER; } @@ -269,6 +274,14 @@ class spell_gen_arena_drink : public AuraScript if (!regen) return; + //npcbot + if (GetCaster()->GetTypeId() == TYPEID_UNIT) + { + isPeriodic = false; + return; + } + //end npcbot + // default case - not in arena if (!GetCaster()->ToPlayer()->InArena()) isPeriodic = false; @@ -280,6 +293,14 @@ class spell_gen_arena_drink : public AuraScript if (!regen) return; + //npcbot + if (GetCaster()->GetTypeId() == TYPEID_UNIT) + { + regen->ChangeAmount(amount); + return; + } + //end npcbot + // default case - not in arena if (!GetCaster()->ToPlayer()->InArena()) regen->ChangeAmount(amount); @@ -3632,6 +3653,10 @@ class spell_gen_vehicle_scaling : public AuraScript bool Load() override { + //npcbot + if (GetCaster() && GetCaster()->IsNPCBot()) + return true; + //end npcbot return GetCaster() && GetCaster()->GetTypeId() == TYPEID_PLAYER; } @@ -3654,7 +3679,19 @@ class spell_gen_vehicle_scaling : public AuraScript break; } + //npcbot + /* + //end npcbot float avgILvl = caster->ToPlayer()->GetAverageItemLevel(); + //npcbot + */ + float avgILvl; + if (caster->GetTypeId() == TYPEID_PLAYER) + avgILvl = caster->ToPlayer()->GetAverageItemLevel(); + else + avgILvl = caster->ToCreature()->GetBotAverageItemLevel(); + //end npcbot + if (avgILvl < baseItemLevel) return; /// @todo Research possibility of scaling down diff --git a/src/server/scripts/Spells/spell_hunter.cpp b/src/server/scripts/Spells/spell_hunter.cpp index 09b314d7f..b1b94aefd 100644 --- a/src/server/scripts/Spells/spell_hunter.cpp +++ b/src/server/scripts/Spells/spell_hunter.cpp @@ -1122,6 +1122,22 @@ class spell_hun_sniper_training : public AuraScript void HandleUpdatePeriodic(AuraEffect* aurEff) { + //npcbot: handle creatures, remove dead trigger + if (!GetUnitOwner()->IsAlive()) + return; + if (Creature const* bot = GetUnitOwner()->ToCreature()) + { + if (!bot->IsNPCBot()) + return; + + int32 baseAmount = aurEff->GetBaseAmount(); + int32 amount = bot->isMoving() ? + bot->CalculateSpellDamage(aurEff->GetSpellEffectInfo(), &baseAmount) : + aurEff->GetAmount() - 1; + aurEff->SetAmount(amount); + return; + } + //end npcbot if (Player* playerTarget = GetUnitOwner()->ToPlayer()) { int32 baseAmount = aurEff->GetBaseAmount(); diff --git a/src/server/scripts/Spells/spell_item.cpp b/src/server/scripts/Spells/spell_item.cpp index 2cf9d7bc5..3c8405008 100644 --- a/src/server/scripts/Spells/spell_item.cpp +++ b/src/server/scripts/Spells/spell_item.cpp @@ -38,6 +38,10 @@ #include "SpellMgr.h" #include "SpellScript.h" +//npcbot +#include "botmgr.h" +//end npcbot + enum GenericData { SPELL_ARCANITE_DRAGONLING = 19804, @@ -3654,6 +3658,14 @@ class spell_item_death_choice : public AuraScript float str = caster->GetStat(STAT_STRENGTH); float agi = caster->GetStat(STAT_AGILITY); + //npcbot: try get stats + if (caster->IsNPCBot()) + { + str = BotMgr::GetBotStat(caster->ToCreature(), BOT_STAT_MOD_STRENGTH); + agi = BotMgr::GetBotStat(caster->ToCreature(), BOT_STAT_MOD_AGILITY); + } + //end npcbot + switch (aurEff->GetId()) { case SPELL_DEATH_CHOICE_NORMAL_AURA: @@ -3800,6 +3812,17 @@ class spell_item_darkmoon_card_greatness : public AuraScript float agi = caster->GetStat(STAT_AGILITY); float intl = caster->GetStat(STAT_INTELLECT); float spi = caster->GetStat(STAT_SPIRIT); + + //npcbot: try get stats + if (caster->IsNPCBot()) + { + str = BotMgr::GetBotStat(caster->ToCreature(), BOT_STAT_MOD_STRENGTH); + agi = BotMgr::GetBotStat(caster->ToCreature(), BOT_STAT_MOD_AGILITY); + intl = BotMgr::GetBotStat(caster->ToCreature(), BOT_STAT_MOD_INTELLECT); + spi = BotMgr::GetBotStat(caster->ToCreature(), BOT_STAT_MOD_SPIRIT); + } + //end npcbot + float stat = 0.0f; uint32 spellTrigger = SPELL_DARKMOON_CARD_STRENGTH; diff --git a/src/server/scripts/Spells/spell_mage.cpp b/src/server/scripts/Spells/spell_mage.cpp index 545dc8d08..51d730563 100644 --- a/src/server/scripts/Spells/spell_mage.cpp +++ b/src/server/scripts/Spells/spell_mage.cpp @@ -1161,6 +1161,12 @@ class spell_mage_summon_water_elemental : public SpellScript void HandleDummy(SpellEffIndex /*effIndex*/) { Unit* caster = GetCaster(); + + //npcbot: prevent default handler for bots + if (caster->IsNPCBot()) + return; + //end npcbot + // Glyph of Eternal Water if (caster->HasAura(SPELL_MAGE_GLYPH_OF_ETERNAL_WATER)) caster->CastSpell(caster, SPELL_MAGE_SUMMON_WATER_ELEMENTAL_PERMANENT, true); diff --git a/src/server/scripts/Spells/spell_paladin.cpp b/src/server/scripts/Spells/spell_paladin.cpp index 5f7f1303c..3246aa205 100644 --- a/src/server/scripts/Spells/spell_paladin.cpp +++ b/src/server/scripts/Spells/spell_paladin.cpp @@ -32,6 +32,10 @@ #include "SpellMgr.h" #include "SpellScript.h" +//npcbot +#include "Creature.h" +//end npcbot + enum PaladinSpells { SPELL_PALADIN_DIVINE_PLEA = 54428, @@ -164,6 +168,12 @@ class spell_pal_ardent_defender : public AuraScript { _absorbPct = GetEffectInfo(EFFECT_0).CalcValue(); _healPct = GetEffectInfo(EFFECT_1).CalcValue(); + + //npcbot - allow for npcbots + if (GetUnitOwner()->IsNPCBot()) + return true; + //end npcbot + return GetUnitOwner()->GetTypeId() == TYPEID_PLAYER; } @@ -178,6 +188,40 @@ class spell_pal_ardent_defender : public AuraScript Unit* victim = GetTarget(); int32 remainingHealth = victim->GetHealth() - dmgInfo.GetDamage(); uint32 allowedHealth = victim->CountPctFromMaxHealth(35); + + //npcbot - calc for bots + if (victim->GetTypeId() == TYPEID_UNIT/* && victim->ToCreature()->IsNPCBot()*/) + { + if (remainingHealth <= 0 && !victim->GetSpellHistory()->HasCooldown(PAL_SPELL_ARDENT_DEFENDER_HEAL) && + !victim->ToCreature()->HasSpellCooldown(PAL_SPELL_ARDENT_DEFENDER_HEAL)) + { + // Cast healing spell, completely avoid damage + absorbAmount = dmgInfo.GetDamage(); + + float defenseSkillValue = victim->GetDefenseSkillValue(); + // Max heal when defense skill denies critical hits from raid bosses + // Formula: max defense at level + 140 (rating from gear) + float reqDefForMaxHeal = victim->GetMaxSkillValueForLevel() + 140.0f; + float defenseFactor = std::min(1.0f, defenseSkillValue / reqDefForMaxHeal); + + CastSpellExtraArgs args(aurEff); + args.AddSpellBP0(victim->CountPctFromMaxHealth(lroundf(_healPct * defenseFactor))); + victim->CastSpell(victim, PAL_SPELL_ARDENT_DEFENDER_HEAL, args); + victim->GetSpellHistory()->AddCooldown(PAL_SPELL_ARDENT_DEFENDER_HEAL, 0, std::chrono::minutes(2)); + } + else if (remainingHealth < int32(allowedHealth)) + { + // Reduce damage that brings us under 35% (or full damage if we are already under 35%) by x% + uint32 damageToReduce = (victim->GetHealth() < allowedHealth) + ? dmgInfo.GetDamage() + : allowedHealth - remainingHealth; + absorbAmount = CalculatePct(damageToReduce, _absorbPct); + } + + return; + } + //end npcbot + // If damage kills us if (remainingHealth <= 0 && !victim->GetSpellHistory()->HasCooldown(PAL_SPELL_ARDENT_DEFENDER_HEAL)) { @@ -458,6 +502,23 @@ class spell_pal_divine_sacrifice : public AuraScript { if (Unit* caster = GetCaster()) { + //npcbot: handle for bots + if (caster->IsNPCBot()) + { + Player const* owner = caster->ToCreature()->GetBotOwner(); + if (!owner || owner->GetTypeId() != TYPEID_PLAYER) + return false; + + if (owner->GetGroup()) + groupSize = owner->GetGroup()->GetMembersCount(); + else + groupSize = 1 + owner->GetNpcBotsCount(); + + remainingAmount = (caster->CountPctFromMaxHealth(GetEffectInfo(EFFECT_2).CalcValue(caster)) * groupSize); + minHpPct = GetEffectInfo(EFFECT_1).CalcValue(caster); + return true; + } + //end npcbot if (caster->GetTypeId() == TYPEID_PLAYER) { if (caster->ToPlayer()->GetGroup()) @@ -1473,6 +1534,9 @@ class spell_pal_righteous_defense : public SpellScript { Unit* caster = GetCaster(); if (caster->GetTypeId() != TYPEID_PLAYER) + //npcbot: this player check makes no sense + if (!caster->IsNPCBot()) + //end npcbot return SPELL_FAILED_DONT_REPORT; if (Unit* target = GetExplTargetUnit()) diff --git a/src/server/scripts/Spells/spell_priest.cpp b/src/server/scripts/Spells/spell_priest.cpp index ba55c1cbc..59d451090 100644 --- a/src/server/scripts/Spells/spell_priest.cpp +++ b/src/server/scripts/Spells/spell_priest.cpp @@ -787,6 +787,10 @@ class spell_pri_penance : public SpellScript bool Load() override { + //npcbot + if (GetCaster() && GetCaster()->IsNPCBot()) + return true; + //end npcbot return GetCaster()->GetTypeId() == TYPEID_PLAYER; } @@ -829,6 +833,10 @@ class spell_pri_penance : public SpellScript SpellCastResult CheckCast() { Player* caster = GetCaster()->ToPlayer(); + //npcbot: check for player makes no sense + if (!caster && GetCaster()->IsNPCBot()) + caster = (Player*)GetCaster(); + //end npcbot if (Unit* target = GetExplTargetUnit()) if (!caster->IsFriendlyTo(target)) { @@ -1001,6 +1009,10 @@ class spell_pri_renew : public AuraScript bool Load() override { + //npcbot + if (GetCaster() && GetCaster()->IsNPCBot()) + return true; + //end npcbot return GetCaster() && GetCaster()->GetTypeId() == TYPEID_PLAYER; } diff --git a/src/server/scripts/Spells/spell_rogue.cpp b/src/server/scripts/Spells/spell_rogue.cpp index 9a60d1816..2a8a387e8 100644 --- a/src/server/scripts/Spells/spell_rogue.cpp +++ b/src/server/scripts/Spells/spell_rogue.cpp @@ -33,6 +33,10 @@ #include "SpellMgr.h" #include "SpellScript.h" +//npcbot +#include "Creature.h" +//end npcbot + enum RogueSpells { SPELL_ROGUE_BLADE_FLURRY_EXTRA_ATTACK = 22482, @@ -110,6 +114,10 @@ class spell_rog_cheat_death : public AuraScript bool Load() override { absorbChance = GetEffectInfo(EFFECT_0).CalcValue(); + //npcbot + if (GetUnitOwner()->IsNPCBot()) + return true; + //end npcbot return GetUnitOwner()->GetTypeId() == TYPEID_PLAYER; } @@ -121,6 +129,27 @@ class spell_rog_cheat_death : public AuraScript void Absorb(AuraEffect* /*aurEff*/, DamageInfo & dmgInfo, uint32 & absorbAmount) { + //npcbot + if (Creature* bot = GetTarget()->ToCreature()) + { + if (dmgInfo.GetDamage() < bot->GetHealth() || bot->HasSpellCooldown(SPELL_ROGUE_CHEAT_DEATH_COOLDOWN) || + bot->GetSpellHistory()->HasCooldown(SPELL_ROGUE_CHEAT_DEATH_COOLDOWN) || !roll_chance_i(absorbChance)) + return; + + bot->CastSpell(bot, SPELL_ROGUE_CHEAT_DEATH_COOLDOWN, true); + bot->GetSpellHistory()->AddCooldown(SPELL_ROGUE_CHEAT_DEATH_COOLDOWN, 0, std::chrono::minutes(1)); + + uint32 health10 = bot->CountPctFromMaxHealth(10); + + if (bot->GetHealth() > health10) + absorbAmount = dmgInfo.GetDamage() - bot->GetHealth() + health10; + else + absorbAmount = dmgInfo.GetDamage(); + + return; + } + //end npcbot + Player* target = GetTarget()->ToPlayer(); if (dmgInfo.GetDamage() < target->GetHealth() || target->GetSpellHistory()->HasCooldown(SPELL_ROGUE_CHEAT_DEATH_COOLDOWN) || !roll_chance_i(absorbChance)) return; @@ -574,6 +603,10 @@ class spell_rog_rupture : public SpellScriptLoader { Unit* caster = GetCaster(); BonusDuration = 0; + //npcbot + if (caster && caster->IsNPCBot()) + return true; + //end npcbot return caster && caster->GetTypeId() == TYPEID_PLAYER; } @@ -593,6 +626,18 @@ class spell_rog_rupture : public SpellScriptLoader 0.0375f // 5 points: ${($m1 + $b1*5 + 0.0375 * $AP) * 8} damage over 16 secs }; + //npcbot + if (caster->GetTypeId() == TYPEID_UNIT) + { + uint8 cp = caster->ToCreature()->GetCreatureComboPoints(); + if (cp > 5) + cp = 5; + + amount += int32(caster->GetTotalAttackPowerValue(BASE_ATTACK) * attackpowerPerCombo[cp]); + return; + } + //end npcbot + uint8 cp = caster->ToPlayer()->GetComboPoints(); if (cp > 5) cp = 5; @@ -713,6 +758,12 @@ class spell_rog_setup : public AuraScript if (eventInfo.GetActor() == target->GetSelectedUnit()) return true; + //npcbot + if (Creature* creature = GetTarget()->ToCreature()) + if (creature->IsNPCBot()) + return true; + //end npcbot + return false; } @@ -913,6 +964,11 @@ class spell_rog_honor_among_thieves_proc_aura : public AuraScript if (!caster) return; + //npcbot + if (Creature* bot = caster->ToCreature()) + bot->CastSpell(nullptr, SPELL_ROGUE_HONOR_AMONG_THIEVES_2, true); + //end npcbot + if (Player* player = caster->ToPlayer()) player->CastSpell(nullptr, SPELL_ROGUE_HONOR_AMONG_THIEVES_2, true); } diff --git a/src/server/scripts/Spells/spell_shaman.cpp b/src/server/scripts/Spells/spell_shaman.cpp index 74bed44d1..e486c1dd2 100644 --- a/src/server/scripts/Spells/spell_shaman.cpp +++ b/src/server/scripts/Spells/spell_shaman.cpp @@ -407,6 +407,16 @@ class spell_sha_earthbind_totem : public AuraScript { if (!GetCaster()) return; + + //npcbot: workaround for bots + if (ObjectGuid creatorGuid = GetCaster()->GetCreatorGUID()) + if (!creatorGuid.IsPlayer()) + if (Creature const* bot = ObjectAccessor::GetCreature(*GetCaster(), creatorGuid)) + if (AuraEffect const* aur = bot->GetDummyAuraEffect(SPELLFAMILY_SHAMAN, 2289, 0)) + if (roll_chance_i(aur->GetBaseAmount())) + GetTarget()->CastSpell(nullptr, SPELL_SHAMAN_TOTEM_EARTHEN_POWER, true); + //end npcbot + if (Player* owner = GetCaster()->GetCharmerOrOwnerPlayerOrPlayerItself()) if (AuraEffect* aur = owner->GetDummyAuraEffect(SPELLFAMILY_SHAMAN, 2289, 0)) if (roll_chance_i(aur->GetBaseAmount())) @@ -590,6 +600,23 @@ class spell_sha_flametongue_weapon : public AuraScript bool CheckProc(ProcEventInfo& eventInfo) { + //npcbot: handle bot enchant check + Creature* bot = eventInfo.GetActor()->ToCreature(); + if (bot && bot->IsNPCBot()) + { + Item* item = bot->GetBotEquipsByGuid(GetAura()->GetCastItemGUID()); + if (!item) + return false; + + WeaponAttackType attType = bot->GetBotEquips(0/*BOT_SLOT_MAINHAND*/) == item ? BASE_ATTACK : OFF_ATTACK; + if ((attType == BASE_ATTACK && !(eventInfo.GetTypeMask() & PROC_FLAG_DONE_MAINHAND_ATTACK)) || + (attType == OFF_ATTACK && !(eventInfo.GetTypeMask() & PROC_FLAG_DONE_OFFHAND_ATTACK))) + return false; + + return true; + } + //end npcbot + Player* player = eventInfo.GetActor()->ToPlayer(); if (!player) return false; @@ -613,6 +640,42 @@ class spell_sha_flametongue_weapon : public AuraScript { PreventDefaultAction(); + //npcbot: handle bot enchant proc + Creature* bot = eventInfo.GetActor()->ToCreature(); + if (bot && bot->IsNPCBot()) + { + Unit* target = eventInfo.GetProcTarget(); + WeaponAttackType attType; + if (eventInfo.GetTypeMask() & PROC_FLAG_DONE_MAINHAND_ATTACK) + attType = BASE_ATTACK; + else + attType = OFF_ATTACK; + + Item* item = ASSERT_NOTNULL(bot->GetBotEquipsByGuid(GetAura()->GetCastItemGUID())); + + float basePoints = aurEff->GetSpellEffectInfo().CalcValue(); + + float attackSpeed = bot->GetAttackTime(attType) / 1000.f; + float fireDamage = basePoints / 100.0f; + fireDamage *= attackSpeed; + + RoundToInterval(fireDamage, basePoints / 77.0f, basePoints / 25.0f); + float spellPowerBonus = bot->SpellBaseDamageBonusDone(SPELL_SCHOOL_MASK_FIRE); + spellPowerBonus += target->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_DAMAGE_TAKEN, SPELL_SCHOOL_MASK_FIRE); + + float const factorMod = bot->CalculateSpellpowerCoefficientLevelPenalty(GetSpellInfo()); + float const spCoeff = 0.03811f; + spellPowerBonus *= spCoeff * attackSpeed * factorMod; + + CastSpellExtraArgs args(aurEff); + args + .SetCastItem(item) + .AddSpellBP0(fireDamage + spellPowerBonus); + bot->CastSpell(target, SPELL_SHAMAN_FLAMETONGUE_ATTACK, args); + return; + } + //end npcbot + Player* player = eventInfo.GetActor()->ToPlayer(); Unit* target = eventInfo.GetProcTarget(); WeaponAttackType attType = BASE_ATTACK; @@ -1834,6 +1897,23 @@ class spell_sha_windfury_weapon : public AuraScript bool CheckProc(ProcEventInfo& eventInfo) { + //npcbot: handle bot enchant check + Creature* bot = eventInfo.GetActor()->ToCreature(); + if (bot && bot->IsNPCBot()) + { + Item* item = bot->GetBotEquipsByGuid(GetAura()->GetCastItemGUID()); + if (!item) + return false; + + WeaponAttackType attType = bot->GetBotEquips(0/*BOT_SLOT_MAINHAND*/) == item ? BASE_ATTACK : OFF_ATTACK; + if ((attType == BASE_ATTACK && !(eventInfo.GetTypeMask() & PROC_FLAG_DONE_MAINHAND_ATTACK)) || + (attType == OFF_ATTACK && !(eventInfo.GetTypeMask() & PROC_FLAG_DONE_OFFHAND_ATTACK))) + return false; + + return true; + } + //end npcbot + Player* player = eventInfo.GetActor()->ToPlayer(); if (!player) return false; @@ -1857,6 +1937,53 @@ class spell_sha_windfury_weapon : public AuraScript { PreventDefaultAction(); + //npcbot: handle bot enchant proc + Creature* bot = eventInfo.GetActor()->ToCreature(); + if (bot && bot->IsNPCBot()) + { + uint32 spellId = 0; + WeaponAttackType attType; + if (eventInfo.GetTypeMask() & PROC_FLAG_DONE_MAINHAND_ATTACK) + { + spellId = SPELL_SHAMAN_WINDFURY_ATTACK_MH; + attType = BASE_ATTACK; + } + else + { + spellId = SPELL_SHAMAN_WINDFURY_ATTACK_OH; + attType = OFF_ATTACK; + } + + Item* item = ASSERT_NOTNULL(bot->GetBotEquipsByGuid(GetAura()->GetCastItemGUID())); + + int32 enchantId = static_cast(item->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT)); + int32 extraAttackPower = 0; + SpellInfo const* spellInfo = sSpellMgr->AssertSpellInfo(SPELL_SHAMAN_WINDFURY_WEAPON_R1); + while (spellInfo) + { + if (spellInfo->GetEffect(EFFECT_0).MiscValue == enchantId) + { + extraAttackPower = spellInfo->GetEffect(EFFECT_1).CalcValue(bot); + break; + } + spellInfo = spellInfo->GetNextRankSpell(); + } + + if (!extraAttackPower) + return; + + int32 amount = static_cast(extraAttackPower / 14.f * bot->GetAttackTime(attType) / 1000.f); + + CastSpellExtraArgs args(aurEff); + args.AddSpellBP0(amount); + // Attack twice + for (uint8 i = 0; i < 2; ++i) + bot->CastSpell(eventInfo.GetProcTarget(), spellId, args); + + return; + } + //end npcbot + Player* player = eventInfo.GetActor()->ToPlayer(); uint32 spellId = 0; diff --git a/src/server/scripts/Spells/spell_warrior.cpp b/src/server/scripts/Spells/spell_warrior.cpp index 1ea3ca520..a94b19461 100644 --- a/src/server/scripts/Spells/spell_warrior.cpp +++ b/src/server/scripts/Spells/spell_warrior.cpp @@ -31,6 +31,10 @@ #include "SpellMgr.h" #include "SpellScript.h" +//npcbot +#include "Creature.h" +//end npcbot + enum WarriorSpells { SPELL_WARRIOR_BLADESTORM_PERIODIC_WHIRLWIND = 50622, @@ -265,6 +269,11 @@ class spell_warr_deep_wounds_aura : public AuraScript if (!damageInfo) return false; + //npcbot: allow for bots + if (eventInfo.GetActor()->IsNPCBot()) + return true; + //end npcbot + return eventInfo.GetActor()->GetTypeId() == TYPEID_PLAYER; } diff --git a/src/server/shared/DataStores/DBCStructure.h b/src/server/shared/DataStores/DBCStructure.h index e2c12efee..88804e73c 100644 --- a/src/server/shared/DataStores/DBCStructure.h +++ b/src/server/shared/DataStores/DBCStructure.h @@ -233,9 +233,11 @@ struct AreaPOIEntry uint32 ContinentID; // 15 //uint32 Flags; // 16 uint32 AreaID; // 17 - //char const* Name[16]; // 18-33 + char const* Name; // 18 + //char const* Name[15]; // 19-33 //uint32 Name_lang_mask; // 34 - //char const* Description[16]; // 35-50 + char const* Description; // 35 + //char const* Description[15]; // 36-50 //uint32 Description_lang_mask; // 51 uint32 WorldStateID; // 52 //uint32 WorldMapLink; // 53 @@ -896,13 +898,14 @@ struct ItemBagFamilyEntry //uint32 Name_lang_mask; // 17 }; -/* struct ItemDisplayInfoEntry { - uint32 ID; // 0 +/* + */uint32 ID;/* // 0 char const* ModelName[2]; // 1-2 char const* ModelTexture[2]; // 3-4 - char const* InventoryIcon[2]; // 5-6 + */char const* InventoryIcon;/* // 5 + char const* InventoryIcon2; // 6 uint32 GeosetGroup[3]; // 7-9 uint32 Flags; // 10 uint32 SpellVisualID; // 11 @@ -911,8 +914,8 @@ struct ItemDisplayInfoEntry char const* Texture[8]; // 15-22 int32 ItemVisual; // 23 uint32 ParticleColorID; // 24 -}; */ +}; /* struct ItemCondExtCostsEntry diff --git a/src/server/shared/DataStores/DBCfmt.h b/src/server/shared/DataStores/DBCfmt.h index 69d4e5f8d..dd56cf44a 100644 --- a/src/server/shared/DataStores/DBCfmt.h +++ b/src/server/shared/DataStores/DBCfmt.h @@ -24,7 +24,7 @@ char constexpr CustomAchievementIndex[] = "ID"; char constexpr AchievementCriteriafmt[] = "niiiiiiiixxxxxxxxxxxxxxxxxiiiix"; char constexpr AreaTableEntryfmt[] = "niiiixxxxxissssssssssssssssxiiiiixxx"; char constexpr AreaGroupEntryfmt[] = "niiiiiii"; -char constexpr AreaPOIEntryfmt[] = "niiiiiiiiiiifffixixxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxix"; +char constexpr AreaPOIEntryfmt[] = "niiiiiiiiiiifffixisxxxxxxxxxxxxxxxxsxxxxxxxxxxxxxxxxix"; char constexpr AreaTriggerEntryfmt[] = "niffffffff"; char constexpr AuctionHouseEntryfmt[] = "niiixxxxxxxxxxxxxxxxx"; char constexpr BankBagSlotPricesEntryfmt[] = "ni"; @@ -76,7 +76,7 @@ char constexpr GtRegenMPPerSptfmt[] = "f"; char constexpr Holidaysfmt[] = "niiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiixxsiix"; char constexpr Itemfmt[] = "niiiiiii"; char constexpr ItemBagFamilyfmt[] = "nxxxxxxxxxxxxxxxxx"; -//char constexpr ItemDisplayTemplateEntryfmt[] = "nxxxxxxxxxxixxxxxxxxxxx"; +char constexpr ItemDisplayTemplateEntryfmt[] = "nxxxxsxxxxxxxxxxxxxxxxxxx"; //char constexpr ItemCondExtCostsEntryfmt[] = "xiii"; char constexpr ItemExtendedCostEntryfmt[] = "niiiiiiiiiiiiiix"; char constexpr ItemLimitCategoryEntryfmt[] = "nxxxxxxxxxxxxxxxxxii"; diff --git a/src/server/shared/SharedDefines.h b/src/server/shared/SharedDefines.h index 2ae3043b7..96d562476 100644 --- a/src/server/shared/SharedDefines.h +++ b/src/server/shared/SharedDefines.h @@ -194,7 +194,14 @@ enum UnitClass UNIT_CLASS_MAGE = 8 }; +//npcbot +/* +//end npcbot #define CLASSMASK_ALL_CREATURES ((1<<(UNIT_CLASS_WARRIOR-1)) | (1<<(UNIT_CLASS_PALADIN-1)) | (1<<(UNIT_CLASS_ROGUE-1)) | (1<<(UNIT_CLASS_MAGE-1))) +//npcbot +*/ +#define CLASSMASK_ALL_CREATURES CLASSMASK_ALL_PLAYABLE +//end npcbot #define CLASSMASK_WAND_USERS ((1<<(CLASS_PRIEST-1))|(1<<(CLASS_MAGE-1))|(1<<(CLASS_WARLOCK-1))) diff --git a/src/server/worldserver/worldserver.conf.dist b/src/server/worldserver/worldserver.conf.dist index 90ad17d31..d8708270b 100644 --- a/src/server/worldserver/worldserver.conf.dist +++ b/src/server/worldserver/worldserver.conf.dist @@ -3933,6 +3933,7 @@ Appender.Console=1,3,0 Appender.Server=2,2,0,Server.log,w Appender.GM=2,2,1,GM.log Appender.DBErrors=2,2,0,DBErrors.log +Appender.NpcBots=1,2,0 # Logger config values: Given a logger "name" # Logger.name @@ -3959,6 +3960,7 @@ Logger.scripts.hotswap=3,Console Server Logger.sql.sql=5,Console DBErrors Logger.sql.updates=3,Console Server Logger.mmaps=3,Server +Logger.npcbots=2,NpcBots Server #Logger.achievement=3,Console Server #Logger.addon=3,Console Server @@ -4147,3 +4149,620 @@ Metric.OverallStatusInterval = 1 # ################################################################################################### + +################################################################################################### +# NPCBOT CONFIGURATION +# +# NpcBot.Enable +# Description: Enables NpcBot system. +# Default: 1 - enable +# 0 - disable + +NpcBot.Enable = 1 + +# +# NpcBot.LogToDB +# Description: Log bot events debug info to DB. Records will be kept up to 30 days +# Default: 1 - (Enable) +# 0 - (Disable) + +NpcBot.LogToDB = 1 + +# +# NpcBot.MaxBots +# Description: Maximum number of bots player can hire per level bracket: +# 0-9, 10-19, 20,29, ... , 70-79, 80+. +# Default: 1,1,1,1,1,1,1,1,1 - (1 for all levels) +# 0,1,2,3,4,5,6,7,8 - (1 per 10 levels) +# Max: 39 + +NpcBot.MaxBots = 1,1,1,1,1,1,1,1,1 + +# +# NpcBot.MaxBotsPerClass +# Description: Maximum number of bots player can hire per bot class. +# Default: 0 - no limit + +NpcBot.MaxBotsPerClass = 0 + +# +# NpcBot.MaxBotsPerAccount +# Description: Maximum total number of bots players can hire per account. +# Default: 0 - no limit +# 9 - up to 9 bots total per account + +NpcBot.MaxBotsPerAccount = 0 + +# +# NpcBot.Botgiver.FilterRaces +# Description: Make botgiver only offer bots of player faction (normal classes). +# Default: 0 - disable + +NpcBot.Botgiver.FilterRaces = 0 + +# +# NpcBot.BaseFollowDistance +# Description: Default bot follow distance. +# Note: This parameter determines bots' formation size, distance at which bots +# will chase and attack enemies. +# Note2: This parameter is set for each player at login. +# Default: 30 + +NpcBot.BaseFollowDistance = 30 + +# +# NpcBot.XpReduction.Amount +# NpcBot.XpReduction.StartingNumber +# Description: XP percent penalty for each bot used starting with . +# Example: 3 bots, reduction is 20, start is 2: ((3-(2-1))*20) = 40%, 60% exp gained. +# Note: Maximum overall xp reduction is 90%. +# Default: 0 - (NpcBot.XpReduction.Amount) +# 2 - (NpcBot.XpReduction.StartingNumber) + +NpcBot.XpReduction.Amount = 0 +NpcBot.XpReduction.StartingNumber = 2 + +# +# NpcBot.MountLevel.60 +# NpcBot.MountLevel.100 +# Description: Minimum level for bots to be able to use ground mounts +# Default: 20 - (NpcBot.MountLevel.60) +# 40 - (NpcBot.MountLevel.100) + +NpcBot.MountLevel.60 = 20 +NpcBot.MountLevel.100 = 40 + +# +# NpcBot.HealTargetIconMask +# NpcBot.TankTargetIconMask +# NpcBot.OffTankTargetIconMask +# NpcBot.DPSTargetIconMask +# NpcBot.RangedDPSTargetIconMask +# NpcBot.NoDPSTargetIconMask +# Description: Icon number bitmask which bots use to search for primary attack +# target, (off-)tanking target, target to heal and protect (out of party), +# or target to try to not damage. +# Note: Many creatures cannot accept heal. +# Note2: While icon is active NPCBot ddealers/tanks will ignore any other targets +# and will not break from combat to follow you. +# Note3: If you set multiple targets only one target at a time will be used. +# Example: To check Star, Triangle and Square we need 1 + 8 + 32 = 41. +# Default: 0 (Disable) +# 1 - Star +# 2 - Circle +# 4 - Diamond +# 8 - Triangle +# 16 - Moon +# 32 - Square +# 64 - Cross +# 128 - Skull + +NpcBot.HealTargetIconMask = 0 +NpcBot.TankTargetIconMask = 0 +NpcBot.OffTankTargetIconMask = 0 +NpcBot.DPSTargetIconMask = 0 +NpcBot.RangedDPSTargetIconMask = 0 +NpcBot.NoDPSTargetIconMask = 0 + +# +# NpcBot.Mult.Damage.Physical +# NpcBot.Mult.Damage.Spell +# NpcBot.Mult.Healing +# NpcBot.Mult.HP +# Description: Multipliers for bots' damage, healing, etc. Allows to balance bots vs players. +# Only affects normal spawned bots and generated wandering bot in BGs. +# Default: 1.0 +# Minimum: 0.1 +# Maximum: 10.0 + +NpcBot.Mult.Damage.Physical = 1.0 +NpcBot.Mult.Damage.Spell = 1.0 +NpcBot.Mult.Healing = 1.0 +NpcBot.Mult.HP = 1.0 + +# +# NpcBot.Mult.Wanderer.Damage +# NpcBot.Mult.Wanderer.Healing +# NpcBot.Mult.Wanderer.HP +# NpcBot.Mult.Wanderer.Speed +# Description: Multipliers for wandering bots' damage, healing, hp and speed. +# Only affects wandering bots in open world. +# Default: 1.0 +# Minimum: 0.1 +# Maximum: 10.0 + +NpcBot.Mult.Wanderer.Damage = 1.0 +NpcBot.Mult.Wanderer.Healing = 1.0 +NpcBot.Mult.Wanderer.HP = 1.0 +NpcBot.Mult.Wanderer.Speed = 1.0 + +# +# NpcBot.Mult.Damage. +# Description: Multipliers for bots' damage by class. Allows to balance dps between bot classes. +# Default: 1.0 +# Minimum: 0.1 +# Maximum: 10.0 + +NpcBot.Mult.Damage.Warrior = 1.0 +NpcBot.Mult.Damage.Paladin = 1.0 +NpcBot.Mult.Damage.Hunter = 1.0 +NpcBot.Mult.Damage.Rogue = 1.0 +NpcBot.Mult.Damage.Priest = 1.0 +NpcBot.Mult.Damage.DeathKnight = 1.0 +NpcBot.Mult.Damage.Shaman = 1.0 +NpcBot.Mult.Damage.Mage = 1.0 +NpcBot.Mult.Damage.Warlock = 1.0 +NpcBot.Mult.Damage.Druid = 1.0 +NpcBot.Mult.Damage.Blademaster = 1.0 +NpcBot.Mult.Damage.ObsidianDestroyer = 1.0 +NpcBot.Mult.Damage.Archmage = 1.0 +NpcBot.Mult.Damage.Dreadlord = 1.0 +NpcBot.Mult.Damage.SpellBreaker = 1.0 +NpcBot.Mult.Damage.DarkRanger = 1.0 +NpcBot.Mult.Damage.Necromancer = 1.0 +NpcBot.Mult.Damage.SeaWitch = 1.0 +NpcBot.Mult.Damage.CryptLord = 1.0 + +# +# NpcBot.Mult.Damage.Levels +# Description: Multipliers for bots' damage by level: 0-9, 10-19, 20,29, ... , 70-79, 80+. +# At least 9 values required. +# Default: 1.0 +# Minimum: 0.1 +# Maximum: 10.0 + +NpcBot.Mult.Damage.Levels = 1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0 + +# +# NpcBot.Enable.Dungeon +# NpcBot.Enable.Raid +# NpcBot.Enable.BG +# NpcBot.Enable.Arena +# NpcBot.Enable.DungeonFinder +# Description: Allow bots to enter PvE/PvP areas and Dungeon Finder query. +# Default: 1 - (NpcBot.Enable.Dungeon) +# 0 - (NpcBot.Enable.Raid) +# 0 - (NpcBot.Enable.BG) +# 0 - (NpcBot.Enable.Arena) +# 1 - (NpcBot.Enable.DungeonFinder) + +NpcBot.Enable.Dungeon = 1 +NpcBot.Enable.Raid = 0 +NpcBot.Enable.BG = 0 +NpcBot.Enable.Arena = 0 +NpcBot.Enable.DungeonFinder = 1 + +# +# NpcBot.DisableInstances +# Description: When NpcBot.Enable.Dungeon or/and NpcBot.Enable.Raid options are set to 1 +# excludes certain instance map ids, preventing bots from entering those maps +# Default: "" - (None) +# "509,531" - (Disable AQ20 and AQ40) + +NpcBot.DisableInstances = "" + +# +# NpcBot.Limit.Dungeon +# NpcBot.Limit.Raid +# Description: Enable/Disable instance players limitation rules for bots. +# Default: 1 - (NpcBot.Limit.Dungeon) +# 1 - (NpcBot.Limit.Raid) + +NpcBot.Limit.Dungeon = 1 +NpcBot.Limit.Raid = 1 + +# +# NpcBot.HideSpawns +# Description: Prevent inactive bots from entering world +# Note: Botgiver npc will become the only way to hire bots +# Note2: Only GM's presense will reveal bot's spawn location (temporarily) +# Default: 1 - (Enabled) +# 0 - (Disabled, keep all spawned bots in world) + +NpcBot.HideSpawns = 1 + +# +# NpcBot.Cost +# Description: Bot recruitment cost (in copper). +# Note: This value is for level 80 characters, for lower levels cost is reduced. +# Default: 1000000 (100 gold) + +NpcBot.Cost = 1000000 + +# +# NpcBot.UpdateDelay.Base +# Description: Base delay between bot AI update cycles (in milliseconds). +# Note: This parameter allows you to increase reaction time and slow down bots +# in general. This may be useful if you want to balance bots in PvP. +# Note2: This value is added on top of existing semi-randomized delay. +# Default: 0 (No additional delay) + +NpcBot.UpdateDelay.Base = 0 + +# +# NpcBot.EngageDelay.DPS +# NpcBot.EngageDelay.Heal +# Description: Delay for first healing / non-tank damage after target was engaged +# by tank (in milliseconds). +# Note: This does not affect only bosses - all combat except PvP will be affected. +# Note2: This value affects all owned bots and is set for every player at login. +# Default: 0 - (No delay) +# 1000 - (1 second) + +NpcBot.EngageDelay.DPS = 0 +NpcBot.EngageDelay.Heal = 0 + +# +# NpcBot.PvP +# Description: Allow bots to attack player-controlled units (players, pets, bots, etc.). +# Note: This rule only applies to player-controlled bots. +# Default: 1 + +NpcBot.PvP = 1 + +# +# NpcBot.Movements.InterruptFood +# Description: Remove food and drink auras when starting to move. +# Note: Bots don't use food or drink if not standing still. +# Default: 0 - (Do not interrupt) +# 1 - (Interrupt food and drink) + +NpcBot.Movements.InterruptFood = 0 + +# +# NpcBot.EquipmentDisplay.Enable +# Description: Enable displaying equipment changes other than weapons on bot models. +# Note: Client limits equipment updates to +# 1) relog +# 2) taking a taxi flight +# 3) teleporting between maps +# 4) not seeing this bot for 3 minutes +# 5) bot changing displayId (polymorph, shapeshift, etc.) +# Note2: If client registers (5) of a single bot 3+ times +# said client may crash at exiting game (Error #132) unless (4) happens. +# Note3: Changing this setting requires server restart. +# Note4: Bots having no equipment will not appear naked. +# Default: 1 - (Enabled) +# 0 - (Disabled) + +NpcBot.EquipmentDisplay.Enable = 1 + +# +# NpcBot.EquipmentDisplay.ShowCloak +# NpcBot.EquipmentDisplay.ShowHelm +# Description: Equipment display rules. +# Default: 1 - (NpcBot.EquipmentDisplay.ShowCloak) +# 1 - (NpcBot.EquipmentDisplay.ShowHelm) + +NpcBot.EquipmentDisplay.ShowCloak = 1 +NpcBot.EquipmentDisplay.ShowHelm = 1 + +# +# NpcBot.Transmog.Enable +# Description: Enable transmogrification of bots' equipment. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +NpcBot.Transmog.Enable = 0 + +# +# NpcBot.Transmog.MixArmorClasses +# NpcBot.Transmog.MixWeaponClasses +# NpcBot.Transmog.MixWeaponInventoryTypes +# NpcBot.Transmog.UseEquipmentSlots +# Description: Enable different armor/weapon types to be transmogrified from one to another +# (i.e. plate to leather, axe to mace, dagger to polearm). +# 'NpcBot.Transmog.UseEquipmentSlots' makes transmog affect equipment slots +# instead of item IDs and forces all other options. +# Default: 0 - (NpcBot.Transmog.MixArmorClasses) +# 0 - (NpcBot.Transmog.MixWeaponClasses) +# 0 - (NpcBot.Transmog.MixWeaponInventoryTypes) +# 0 - (NpcBot.Transmog.UseEquipmentSlots) + +NpcBot.Transmog.MixArmorClasses = 0 +NpcBot.Transmog.MixWeaponClasses = 0 +NpcBot.Transmog.MixWeaponInventoryTypes = 0 +NpcBot.Transmog.UseEquipmentSlots = 0 + +# +# NpcBot.Gossip.ShowEquipmentListItems +# Description: Send whisper message with clickable items list in equipment gossip menu. +# Default: 0 - (Disable) +# 1 - (Enable) + +NpcBot.Gossip.ShowEquipmentListItems = 0 + +# +# NpcBot.GearBank.Enable +# Description: Enable unlimited extra gear storage (accessible through gossip menu). +# Note: Unlimited storage creates an 'unlimited bags' exploit, you have been warned. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +NpcBot.GearBank.Enable = 0 + +# +# NpcBot.Classes..Enable +# Description: Allow players to hire bots of certain classes. +# Note: Some non-standard class abilities may be unbalanced or game breaking. +# Default: 1 - (NpcBot.Classes.Warrior.Enable) +# 1 - (NpcBot.Classes.Paladin.Enable) +# 1 - (NpcBot.Classes.Hunter.Enable) +# 1 - (NpcBot.Classes.Rogue.Enable) +# 1 - (NpcBot.Classes.Priest.Enable) +# 1 - (NpcBot.Classes.DeathKnight.Enable) +# 1 - (NpcBot.Classes.Shaman.Enable) +# 1 - (NpcBot.Classes.Mage.Enable) +# 1 - (NpcBot.Classes.Warlock.Enable) +# 1 - (NpcBot.Classes.Druid.Enable) +# 0 - (NpcBot.Classes.Blademaster.Enable) +# 1 - (NpcBot.Classes.ObsidianDestroyer.Enable) +# 1 - (NpcBot.Classes.Archmage.Enable) +# 1 - (NpcBot.Classes.Dreadlord.Enable) +# 1 - (NpcBot.Classes.SpellBreaker.Enable) +# 1 - (NpcBot.Classes.DarkRanger.Enable) +# 1 - (NpcBot.Classes.Necromancer.Enable) +# 1 - (NpcBot.Classes.SeaWitch.Enable) +# 1 - (NpcBot.Classes.CryptLord.Enable) + +NpcBot.Classes.Warrior.Enable = 1 +NpcBot.Classes.Paladin.Enable = 1 +NpcBot.Classes.Hunter.Enable = 1 +NpcBot.Classes.Rogue.Enable = 1 +NpcBot.Classes.Priest.Enable = 1 +NpcBot.Classes.DeathKnight.Enable = 1 +NpcBot.Classes.Shaman.Enable = 1 +NpcBot.Classes.Mage.Enable = 1 +NpcBot.Classes.Warlock.Enable = 1 +NpcBot.Classes.Druid.Enable = 1 +NpcBot.Classes.Blademaster.Enable = 0 +NpcBot.Classes.ObsidianDestroyer.Enable = 1 +NpcBot.Classes.Archmage.Enable = 1 +NpcBot.Classes.Dreadlord.Enable = 1 +NpcBot.Classes.SpellBreaker.Enable = 1 +NpcBot.Classes.DarkRanger.Enable = 1 +NpcBot.Classes.Necromancer.Enable = 1 +NpcBot.Classes.SeaWitch.Enable = 1 +NpcBot.Classes.CryptLord.Enable = 1 + +# +# NpcBot.Stats.Limits.Enable +# Description: Enable or disable stats limits for bots. +# Default: 0 - Disabled + +NpcBot.Stats.Limits.Enable = 0 + +# +# NpcBot.Stats.Limits. +# Description: Set dodge, parry, block and crit chance percentage limit for bots. +# Note: Some bot classes may ignore some of these values. +# Note2: Auras can still increase these stats above limit. +# Default: 95.0 (95%) + +NpcBot.Stats.Limits.Dodge = 95.0 +NpcBot.Stats.Limits.Parry = 95.0 +NpcBot.Stats.Limits.Block = 95.0 +NpcBot.Stats.Limits.Crit = 95.0 + +# +# NpcBot.InfoPacketsLimit +# Description: This is a workaround for known issue #5. +# If more than 100 bots are spawned in a single spot players trying +# to approach them will be kicked by AntiDOS protection due to +# 'flooding' CMSG_GET_MIRRORIMAGE_DATA packets. This setting +# overrides packets limit per tick for that request type. +# Note: If you care about your server stability at all, use this only as a +# temporary solution and spread your bots spawn points properly. +# Default: -1 - (use default limit set by TC devs) +# 500 - (500 bots in one spot won't get you kicked) + +NpcBot.InfoPacketsLimit = -1 + +# +# NpcBot.OwnershipExpireTime +# Description: Time (in seconds) before bot ownership is automatically cancelled. +# Note: NPCBot ownership may only expire after player goes offline. +# Note2: All items will be mailed back to player as usual. +# Default: 0 - (Disabled) +# 604800 - (7 Days) + +NpcBot.OwnershipExpireTime = 0 + +# +# NpcBot.OwnershipExpireMode +# Description: Ownership expiration mode. Determines when the timer starts. +# Default: 0 - (Player goes offline, timer resets if player logs back in) +# 1 - (Player hires the bot, timer never resets) + +NpcBot.OwnershipExpireMode = 0 + +# +# NpcBot.EnrageOnDismiss +# Description: Enable Berserk buff when bot is dismissed and related hostile reaction. +# Default: 1 - (Enabled) +# 0 - (Disabled) + +NpcBot.EnrageOnDismiss = 1 + +# +# NpcBot.WanderingBots.Continents.Count +# Description: Desired number of free wandering bots to generate. Wandering bots roam +# around the map looking for trouble. Names and visuals are cloned from +# existing non-spawned bots. Wandering bots aren't saved in DB. +# Note: Wandering bots keep grids loaded, increasing CPU and memory usage. +# Note2: You must have at least this number of non-spawned bots in creature_template. +# Default: 0 - (Disabled) + +NpcBot.WanderingBots.Continents.Count = 0 + +# +# NpcBot.WanderingBots.Continents.Levels +# Description: Percentage of wandering bots to spawn per level bracket: +# 0-9, 10-19, 20,29, ... , 70-79, 80+. +# Note: Total percentage must be exactly 100. +# Note2: Classes with minimum level requirement may ignore this parameter. +# Default: 25,15,15,10,5,10,20,0,0 + +NpcBot.WanderingBots.Continents.Levels = 25,15,15,10,5,10,20,0,0 + +# +# NpcBot.WanderingBots.Continents.Maps +# Description: Continent map IDs where wandering bots are allowed to spawn. +# Note: Wandering bots will never outlevel the map they are in. +# Default: 0,1,530,571 - (Eastern Kingdoms, Kalimdor, Outland, Northrend) + +NpcBot.WanderingBots.Continents.Maps = 0,1,530,571 + +# +# NpcBot.WanderingBots.Continents.XPGain +# Description: XP gained by wandering bots mutiplier. Directly affects bot leveling rate. +# Default: 1.0 - (100% XP gained) +# 0.1 - (10% XP gained) +# Minimum: 0.0 +# Maximum: 100.0 + +NpcBot.WanderingBots.Continents.XPGain = 1.0 + +# +# NpcBot.WanderingBots.BG.Enable +# Description: Allow wandering bots generation for Battlegrounds. +# Note: Bots are temporarily borrowed from non-spawned bots pool. +# Default: 0 - (Disabled) +# 1 - (Enable) + +NpcBot.WanderingBots.BG.Enable = 0 + +# +# NpcBot.WanderingBots.BG.CapLevel +# Description: Enforce BG bot maximum level limit set by Expansion and MaxPlayerLevel +# config paramter values. +# Default: 0 - (Disabled) +# 1 - (Enable) + +NpcBot.WanderingBots.BG.CapLevel = 0 + +# +# NpcBot.WanderingBots.BG.CapLevelByFirstPlayer +# Description: Prevent setting BG bot level higher than player triggering its generation +# Default: 0 - (Disabled) +# 1 - (Enable) + +NpcBot.WanderingBots.BG.CapLevelByFirstPlayer = 0 + +# +# NpcBot.WanderingBots.BG.TargetTeamPlayersCount. +# Description: Target BG players count per team to aim for when generating BG bots. +# Note: These values can't go beyond BG minimum / maximum players per team bounds. +# Default: 30 - (NpcBot.WanderingBots.BG.TargetTeamPlayersCount.AV) +# 8 - (NpcBot.WanderingBots.BG.TargetTeamPlayersCount.WS) +# 12 - (NpcBot.WanderingBots.BG.TargetTeamPlayersCount.AB) +# 0 - (NpcBot.WanderingBots.BG.TargetTeamPlayersCount.EY) (NYI, Disabled) +# 0 - (NpcBot.WanderingBots.BG.TargetTeamPlayersCount.SA) (NYI, Disabled) +# 0 - (NpcBot.WanderingBots.BG.TargetTeamPlayersCount.IC) (NYI, Disabled) + +NpcBot.WanderingBots.BG.TargetTeamPlayersCount.AV = 30 +NpcBot.WanderingBots.BG.TargetTeamPlayersCount.WS = 8 +NpcBot.WanderingBots.BG.TargetTeamPlayersCount.AB = 12 +NpcBot.WanderingBots.BG.TargetTeamPlayersCount.EY = 0 +NpcBot.WanderingBots.BG.TargetTeamPlayersCount.SA = 0 +NpcBot.WanderingBots.BG.TargetTeamPlayersCount.IC = 0 + +# +# NpcBot.WanderingBots.Classes..Enable +# Description: Allow players to hire bots of certain classes. +# Note: Some non-standard class abilities may be unbalanced or game breaking. +# Default: 1 - (NpcBot.Classes.Warrior.Enable) +# 1 - (NpcBot.Classes.Paladin.Enable) +# 1 - (NpcBot.Classes.Hunter.Enable) +# 1 - (NpcBot.Classes.Rogue.Enable) +# 1 - (NpcBot.Classes.Priest.Enable) +# 1 - (NpcBot.Classes.DeathKnight.Enable) +# 1 - (NpcBot.Classes.Shaman.Enable) +# 1 - (NpcBot.Classes.Mage.Enable) +# 1 - (NpcBot.Classes.Warlock.Enable) +# 1 - (NpcBot.Classes.Druid.Enable) +# 0 - (NpcBot.Classes.Blademaster.Enable) +# 1 - (NpcBot.Classes.ObsidianDestroyer.Enable) +# 1 - (NpcBot.Classes.Archmage.Enable) +# 1 - (NpcBot.Classes.Dreadlord.Enable) +# 1 - (NpcBot.Classes.SpellBreaker.Enable) +# 1 - (NpcBot.Classes.DarkRanger.Enable) +# 1 - (NpcBot.Classes.Necromancer.Enable) +# 1 - (NpcBot.Classes.SeaWitch.Enable) +# 1 - (NpcBot.Classes.CryptLord.Enable) + +NpcBot.WanderingBots.Classes.Warrior.Enable = 1 +NpcBot.WanderingBots.Classes.Paladin.Enable = 1 +NpcBot.WanderingBots.Classes.Hunter.Enable = 1 +NpcBot.WanderingBots.Classes.Rogue.Enable = 1 +NpcBot.WanderingBots.Classes.Priest.Enable = 1 +NpcBot.WanderingBots.Classes.DeathKnight.Enable = 1 +NpcBot.WanderingBots.Classes.Shaman.Enable = 1 +NpcBot.WanderingBots.Classes.Mage.Enable = 1 +NpcBot.WanderingBots.Classes.Warlock.Enable = 1 +NpcBot.WanderingBots.Classes.Druid.Enable = 1 +NpcBot.WanderingBots.Classes.Blademaster.Enable = 0 +NpcBot.WanderingBots.Classes.ObsidianDestroyer.Enable = 1 +NpcBot.WanderingBots.Classes.Archmage.Enable = 1 +NpcBot.WanderingBots.Classes.Dreadlord.Enable = 1 +NpcBot.WanderingBots.Classes.SpellBreaker.Enable = 1 +NpcBot.WanderingBots.Classes.DarkRanger.Enable = 1 +NpcBot.WanderingBots.Classes.Necromancer.Enable = 1 +NpcBot.WanderingBots.Classes.SeaWitch.Enable = 1 +NpcBot.WanderingBots.Classes.CryptLord.Enable = 1 + +# +# NpcBot.HK.Enable +# Description: Count NPCBot kill at honor kill. +# Default: 1 - (Enabled) +# 0 - (Disabled) + +NpcBot.HK.Enable = 1 + +# +# NpcBot.HK.Message.Enable +# Description: Show 'HK: ' message for NPCBot honor kills. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +NpcBot.HK.Message.Enable = 0 + +# +# NpcBot.HK.Achievements.Enable +# Description: Count NPCBot honor kills towards PvP achievements. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +NpcBot.HK.Achievements.Enable = 0 + +# +# NpcBot.HK.Rate.Honor +# Description: Honor gained multiplier for NPCBot honor kills. +# Default: 1.0 +# Minimum: 0.1 +# Maximum: 10.0 + +NpcBot.HK.Rate.Honor = 1.0 + +# +###################################################################################################